/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      High level joystick input framework.
 *
 *      By Shawn Hargreaves.
 *
 *      See readme.txt for copyright information.
 */


#include <stdlib.h>
#include <stdio.h>

#include "internal.h"



/* dummy driver for when no joystick is present */
static int nojoy_ret0() { return 0; }
static void nojoy_void() { }


JOYSTICK_DRIVER joystick_none =
{
   JOY_TYPE_NONE,
   "No joystick",
   "No joystick",
   nojoy_ret0,
   nojoy_void,
   nojoy_ret0,
   NULL, NULL,
   NULL, NULL
};


int _joystick_installed = FALSE;

JOYSTICK_DRIVER *joystick_driver = NULL;

int joy_type = JOY_TYPE_NONE;

JOYSTICK_INFO joy[4];
int num_joysticks = 0;

static int joy_loading = FALSE;



/* clear_joystick_vars:
 *  Resets the joystick state variables to their default values.
 */
static void clear_joystick_vars()
{
   char *unused = get_config_text("unused");
   int i, j, k;

   #define ARRAY_SIZE(a)   ((int)sizeof((a)) / (int)sizeof((a)[0]))

   for (i=0; i<ARRAY_SIZE(joy); i++) {
      joy[i].flags = 0;
      joy[i].num_sticks = 0;
      joy[i].num_buttons = 0;

      for (j=0; j<ARRAY_SIZE(joy[i].stick); j++) {
	 joy[i].stick[j].flags = 0;
	 joy[i].stick[j].num_axis = 0;
	 joy[i].stick[j].name = unused;

	 for (k=0; k<ARRAY_SIZE(joy[i].stick[j].axis); k++) {
	    joy[i].stick[j].axis[k].pos = 0;
	    joy[i].stick[j].axis[k].d1 = FALSE;
	    joy[i].stick[j].axis[k].d2 = FALSE;
	    joy[i].stick[j].axis[k].name = unused;
	 }
      }

      for (j=0; j<ARRAY_SIZE(joy[i].button); j++) {
	 joy[i].button[j].b = FALSE;
	 joy[i].button[j].name = unused;
      }
   }

   num_joysticks = 0;
}



/* update_calib:
 *  Updates the calibration flags for the specified joystick structure.
 */
static void update_calib(int n)
{
   int c = FALSE;
   int i;

   for (i=0; i<joy[n].num_sticks; i++) {
      if (joy[n].stick[i].flags & (JOYFLAG_CALIB_DIGITAL | JOYFLAG_CALIB_ANALOGUE)) {
	 joy[n].stick[i].flags |= JOYFLAG_CALIBRATE;
	 c = TRUE;
      }
      else
	 joy[n].stick[i].flags &= ~JOYFLAG_CALIBRATE;
   }

   if (c)
      joy[n].flags |= JOYFLAG_CALIBRATE;
   else
      joy[n].flags &= ~JOYFLAG_CALIBRATE;
}



/* install_joystick:
 *  Initialises the joystick module.
 */
int install_joystick(int type)
{
   int c;

   if (_joystick_installed)
      return 0;

   clear_joystick_vars();

   allegro_error[0] = 0;

   /* search table for a specific driver */
   for (c=0; _joystick_driver_list[c].driver; c++) { 
      if (_joystick_driver_list[c].driver_id == type) {
	 joystick_driver = _joystick_driver_list[c].driver;
	 joy_type = type;
	 if (joystick_driver->init() != 0) {
	    if (!allegro_error[0])
	       sprintf(allegro_error, get_config_text("%s not found"), joystick_driver->name);
	    joystick_driver = NULL; 
	    joy_type = JOY_TYPE_NONE;
	    return -1;
	 }
	 break;
      }
   }

   /* autodetect driver */
   if (!joystick_driver) {
      if (!joy_loading) {
	 if (load_joystick_data(NULL) == 0)
	    return 0;
      }

      for (c=0; _joystick_driver_list[c].driver; c++) {
	 if (_joystick_driver_list[c].autodetect) {
	    joystick_driver = _joystick_driver_list[c].driver;
	    joy_type = _joystick_driver_list[c].driver_id;
	    if (_joystick_driver_list[c].driver->init() == 0)
	       break;
	 }
      }
   }

   for (c=0; c<num_joysticks; c++)
      update_calib(c);

   poll_joystick();

   _add_exit_func(remove_joystick);
   _joystick_installed = TRUE;

   return 0;
}



/* remove_joystick:
 *  Shuts down the joystick module.
 */
void remove_joystick()
{
   if (_joystick_installed) {
      joystick_driver->exit();

      joystick_driver = NULL;
      joy_type = JOY_TYPE_NONE;

      clear_joystick_vars();

      _remove_exit_func(remove_joystick);
      _joystick_installed = FALSE;
   }
}



/* poll_joystick:
 *  Reads the current input state into the joystick status variables.
 */
int poll_joystick()
{
   if ((joystick_driver) && (joystick_driver->poll))
      return joystick_driver->poll();

   return -1; 
}



/* save_joystick_data:
 *  After calibrating a joystick, this function can be used to save the
 *  information into the specified file, from where it can later be 
 *  restored by calling load_joystick_data().
 */
int save_joystick_data(char *filename)
{
   if (filename) {
      push_config_state();
      set_config_file(filename);
   }

   set_config_id("joystick", "joytype", joy_type);

   if ((joystick_driver) && (joystick_driver->save_data))
      joystick_driver->save_data();

   if (filename)
      pop_config_state();

   return 0;
}



/* load_joystick_data:
 *  Restores a set of joystick calibration data previously saved by
 *  save_joystick_data().
 */
int load_joystick_data(char *filename)
{
   int ret, c;

   joy_loading = TRUE;

   if (_joystick_installed)
      remove_joystick();

   if (filename) {
      push_config_state();
      set_config_file(filename);
   }

   joy_type = get_config_id("joystick", "joytype", -1);

   if (joy_type < 0) {
      joy_type = JOY_TYPE_NONE;
      ret = -1;
   }
   else {
      ret = install_joystick(joy_type);

      if (ret == 0) {
	 if (joystick_driver->load_data)
	    ret = joystick_driver->load_data();
      }
   }

   if (filename)
      pop_config_state();

   if (ret == 0) {
      for (c=0; c<num_joysticks; c++)
	 update_calib(c);

      poll_joystick();
   }

   joy_loading = FALSE;

   return ret;
}



/* calibrate_joystick_name:
 *  Returns the name of the next calibration operation to be performed on
 *  the specified stick.
 */
char *calibrate_joystick_name(int n)
{
   if ((!joystick_driver) || (!joystick_driver->calibrate_name) ||
       (!(joy[n].flags & JOYFLAG_CALIBRATE)))
      return NULL;

   return joystick_driver->calibrate_name(n);
}



/* calibrate_joystick:
 *  Performs the next caliabration operation required for the specified stick.
 */
int calibrate_joystick(int n)
{
   int ret;

   if ((!joystick_driver) || (!joystick_driver->calibrate) ||
       (!(joy[n].flags & JOYFLAG_CALIBRATE)))
      return -1;

   ret = joystick_driver->calibrate(n);

   if (ret == 0)
      update_calib(n);

   return ret;
}



/* initialise_joystick:
 *  Bodge function to preserve backward compatibility with the old API.
 */
int initialise_joystick()
{
   int type = joy_type;

   if (_joystick_installed)
      remove_joystick();

   if (type == JOY_TYPE_NONE)
      type = JOY_TYPE_STANDARD;

   return install_joystick(type);
}
