#include <string.h>

#include "allegro.h"
#include "main.h"
#include "zcontrol.h"
#include "timeloop.h"
#include "log.h"

static const char *scancode_name[] = 
{
   "(none)",     "A",          "B",          "C",
   "D",          "E",          "F",          "G",
   "H",          "I",          "J",          "K",
   "L",          "M",          "N",          "O",
   "P",          "Q",          "R",          "S",
   "T",          "U",          "V",          "W",
   "X",          "Y",          "Z",          "0",
   "1",          "2",          "3",          "4",
   "5",          "6",          "7",          "8",
   "9",          "0 (Pad)",    "1 (Pad)",    "2 (Pad)",
   "3 (Pad)",    "4 (Pad)",    "5 (Pad)",    "6 (Pad)",
   "7 (Pad)",    "8 (Pad)",    "9 (Pad)",    "F1",
   "F2",         "F3",         "F4",         "F5",
   "F6",         "F7",         "F8",         "F9",
   "F10",        "F11",        "F12",        "ESC",
   "TILDE",      "MINUS",      "EQUALS",     "BACKSPACE",
   "TAB",        "OPENBRACE",  "CLOSEBRACE", "ENTER",
   "COLON",      "QUOTE",      "BACKSLASH",  "BACKSLASH2",
   "COMMA",      "STOP",       "SLASH",      "SPACE",
   "INSERT",     "DEL",        "HOME",       "END",
   "PGUP",       "PGDN",       "LEFT",       "RIGHT",
   "UP",         "DOWN",       "SLASH_PAD",  "ASTERISK",
   "MINUS_PAD",  "PLUS_PAD",   "DEL_PAD",    "ENTER_PAD",
   "PRTSCR",     "PAUSE",      "ABNT_C1",    "YEN",
   "KANA",       "CONVERT",    "NOCONVERT",  "AT",
   "CIRCUMFLEX", "COLON2",     "KANJI",
   "LSHIFT",     "RSHIFT",     "LCONTROL",   "RCONTROL",
   "ALT",        "ALTGR",      "LWIN",       "RWIN",
   "MENU",       "SCRLOCK",    "NUMLOCK",    "CAPSLOCK",
   "MAX"
};

static const char *control_key_name[] = {
	"Forward", "Backward", "Left", "Right", "Fire", "PowerUp"
};

static const char *default_key[N_CONTROL_TYPES][CONTROL_NUM_KEYS] = {
	/* Unknown */
	{"", "", "", "", "", ""},

	/* Off */
	{"", "", "", "", "", ""},

	/* Keyboard */
	{"UP", "DOWN", "LEFT", "RIGHT", "SPACE", "ENTER"},

	/* Joystick */
	{"Joy0 0 1 -1", "Joy0 0 -2 -1", "Joy0 0 0 -1", "Joy0 0 -1 -1", "Joy0 0 0 0", "Joy0 0 0 1"},

	/* AI */
	{"", "", "", "", "", ""}
};


/* Some useful stuff for menus */

/* Returns the string name of the key or stick */
AL_CONST char *control_get_key_name(CONTROL *ctrl, int k, char *buf, int size) {

	if (size < 13) {
		LOG("--== Error ==-- Control::get_key_name: Buffer length (%i) must be at least 13 bytes long.\n");
		return NULL;
	}

	if (ctrl->type == CONTROL_TYPE_KEYBOARD)
		uszprintf(buf, size, "%s", scancode_name[ctrl->dev.keyboard.key_id[k]]);

	else if (ctrl->type == CONTROL_TYPE_JOYSTICK) {
		AL_CONST char *r;
		int jn = ctrl->dev.joystick.joy_num[k],
			an = ctrl->dev.joystick.axis[k],
			sn = ctrl->dev.joystick.stick[k],
			bn = ctrl->dev.joystick.button[k];


		if (ctrl->dev.joystick.button[k] == -1) /* Stick number */
			r = joy[jn].stick[sn].axis[an < 0 ? -an-1 : an].name;
		else
			r = joy[jn].button[bn].name;

		if (bn == -1) {
			if (ustricmp(r, "Y") == 0)
				uszprintf(buf, size, "(%c%s) %s", (an < 0) ? '-' : '+', r, (an < 0) ? "Down" : "Up");
			else if (ustricmp(r, "X") == 0)
				uszprintf(buf, size, "(%c%s) %s", (an < 0) ? '-' : '+', r, (an < 0) ? "Right" : "Left");
			else
				uszprintf(buf, size, "%c%s", (an < 0) ? '-' : '+', r);
		}
		else
			uszprintf(buf, size, "%s", r);
	}

	return buf;
}

/* Returns the string name of the key or stick in a format that can be saved/loaded easily */
AL_CONST char *control_get_save_name(CONTROL *ctrl, int k, char *buf, int size) {

	if (size < 13) {
		LOG("--== Error ==-- Control::get_key_name: Buffer length (%i) must be at least 13 bytes long.\n");
		return NULL;
	}

	if (ctrl->type == CONTROL_TYPE_KEYBOARD)
		uszprintf(buf, size, "%s", scancode_name[ctrl->dev.keyboard.key_id[k]]);

	else if (ctrl->type == CONTROL_TYPE_JOYSTICK) {
		int jn = ctrl->dev.joystick.joy_num[k],
			an = ctrl->dev.joystick.axis[k],
			sn = ctrl->dev.joystick.stick[k],
			bn = ctrl->dev.joystick.button[k];

		uszprintf(buf, size, "Joy%i %i %i %i", jn, sn, an, bn);
	}

	return buf;
}


void control_set_key(CONTROL *ctrl, int k, const char *new_key) {

	int i;

	if (ctrl->type == CONTROL_TYPE_KEYBOARD) {
		for (i = 0; i < KEY_MAX; i++) {
			if (ustricmp(scancode_name[i], new_key) == 0) {
				ctrl->dev.keyboard.key_id[k] = i;
				break;
			}
		}
	}
	else if (ctrl->type == CONTROL_TYPE_JOYSTICK) {
		int jn, sn, an, bn;
		/* Is it of the minimal form? */
		int n = sscanf(new_key, "Joy%i %i %i %i", &jn, &sn, &an, &bn);

		if (n < 4) {
			/* Look for another way to interpret the name... */
		}
		else {
			ctrl->dev.joystick.joy_num[k] = jn;
			ctrl->dev.joystick.axis[k] = an;
			ctrl->dev.joystick.stick[k] = sn;
			ctrl->dev.joystick.button[k] = bn;
		}
	}

	return;
}



/* Config save/load - will use defaults if necessary */
CONTROL *load_control_config(const char *section, int type) {

	int i;

	CONTROL *ret;
	char buf2[40];
	char *ctype;

	if (type == CONTROL_TYPE_KEYBOARD)
		ctype = "Keyboard";
	else if (type == CONTROL_TYPE_JOYSTICK)
		ctype = "Joystick";
	else
		ctype = "Unknown";

	if (type) {
		const char *new_key;
		ret = create_control(type);

        if (!ret) exit(37);

		for (i = 0; i < CONTROL_NUM_KEYS; i++) {
			uszprintf(buf2, 40, "%s_%s", ctype, control_key_name[i]);
			new_key = get_config_string(section, buf2, default_key[type][i]);
			control_set_key(ret, i, new_key);
		}
	}
	else {
		LOG("--== Warning ==-- Control::load_control_config: Unknown input device (%s). Assuming default key bindings.\n", type);
		ret = create_control(CONTROL_TYPE_KEYBOARD);
        if (!ret) exit(37);
		for (i = 0; i < CONTROL_NUM_KEYS; i++) {
			control_set_key(ret, i, default_key[type][i]);
		}
	}

	return ret;
}


AL_CONST char *control_get_key_binding_name(int k) {

	if (k < 0 || k >= CONTROL_NUM_KEYS)
		return NULL;

	return control_key_name[k];
}


void save_control_config(const char *section, CONTROL *ctrl) {

	int i;
	char buf[15];
	char buf2[40];
	char *type;

	if (!ctrl) {
		LOG("--== Error ==-- Control::save_control_config: Null argument 'ctrl'\n");
		return;
	}

	if (ctrl->type == CONTROL_TYPE_KEYBOARD)
		type = "Keyboard";
	else if (ctrl->type == CONTROL_TYPE_JOYSTICK)
		type = "Joystick";
	else
		type = "Unknown";

	for (i = 0; i < CONTROL_NUM_KEYS; i++) {
		uszprintf(buf2, 40, "%s_%s", type, control_key_name[i]);
		set_config_string(section, buf2, control_get_save_name(ctrl, i, buf, 13));
	}

	return;
}



/* For AI mainly - so they can set keys. Info will be stored in the
   keyboard binding.
*/
void control_press_key(CONTROL *ctrl, int k, int time) {
	
	/* FIFO full */
	if (ctrl->fifo_end[k] == ctrl->fifo_start[k])
		return;

	ctrl->key_time[k][ctrl->fifo_end[k]] = time;

	ctrl->fifo_end[k]++;
	if (ctrl->fifo_end[k] >= CONTROL_FIFO_SIZE)
		ctrl->fifo_end[k] = 0;
	if (ctrl->fifo_start[k] == -1)
		ctrl->fifo_start[k] = 0;

	return;
}
	



void control_release_key(CONTROL *ctrl, int k, int time) {
	
	/* FIFO full */
	if (ctrl->fifo_end[k] == ctrl->fifo_start[k])
		return;

	ctrl->key_time[k][ctrl->fifo_end[k]] = time | (1 << 31);

	ctrl->fifo_end[k]++;
	if (ctrl->fifo_end[k] >= CONTROL_FIFO_SIZE)
		ctrl->fifo_end[k] = 0;
	if (ctrl->fifo_start[k] == -1)
		ctrl->fifo_start[k] = 0;

	return;
}


/* m_ctrl.h uses the following functions and variables to set up the
   controllers.

   Before you try to detect a key, call initiate_choose_key(), passing the
   control device and the CONTROL_KEY_* constant of your choice. You may pass
   either a keyboard or a joystick controller.

   After calling this function, you must ensure that choose_update_joystick()
   is called as often as possible, i.e. at least once in the loop, and
   several times during the drawing code. You need not make this a special
   case for joystick; just call the function anyway, since it will not do
   anything if the controller is a keyboard.

   At some point in your loop, you should call control_choose_key(). If this
   function returns zero, no key has yet been detected. If this function
   returns positive, a key has been detected and accepted. If it returns
   negative, the key detection was cancelled (Esc was pressed).

   When this function returns nonzero, you may consider the key detection
   resolved. The key has been stored in the CONTROL struct, and the key
   detection code has been shut down. The keyboard buffer will have been
   cleared, so you can proceed as normal.
*/
unsigned char choose_key[KEY_MAX];        /* LOCK */
JOYSTICK_INFO choose_joy[MAX_JOYSTICKS];  /* LOCK */


/* choose_control is initialised to null. */
CONTROL *choose_control; /* LOCK THIS! */
int choose_k; /* This need not be locked. */
int chosen_key; /* LOCK THIS! */
int chosen_joy_num, chosen_joy_stick, chosen_joy_axis, chosen_joy_button; /* LOCK */

/* choose_update_keyboard() and choose_update_joystick() work in much the
   same way as update_keyboard() and update_joystick(). However, when a key
   press or release is detected (simulated or otherwise), their behaviour is
   different.

   If a key is released, the corresponding entry in choose_key[] should be
   cleared. If a key is pressed, the key should be accepted only if the
   corresponding entry in the choose_key[] array is clear. This guards
   against detecting keys that were already held down, provided choose_key[]
   is initialised to match the key[] array.

   Note that choose_update_keyboard() will be installed for both keyboard and
   joystick detection, so that it can detect Esc. Therefore,
   choose_update_keyboard() should check that choose_control is a keyboard
   controller before responding to any key except Esc.

   To accept a key, first remove choose_update_keyboard() from Allegro's
   keyboard_lowlevel_callback variable. Then, for a key on the keyboard,
   set key_chosen to the scancode of the key detected and leave it at that.
   For a simulated key on the joystick, write the data on which key was
   pressed directly into choose_control, set choose_control to null, and
   clear the keyboard buffer. Don't clear the keyboard buffer from within
   choose_update_keyboard!

   When accepting a key from the joystick, you should also remove
   set_joystick_flag(), which was installed as a timer interrupt, and set
   'joystick_flag' to 0 ready for the next time. Do this first.

   The choose_update_keyboard() function should respond to Esc for both
   keyboard and joystick controllers. Go through the same procedure as when
   responding to any other key on the keyboard. Store the key, which will be
   KEY_ESC, in key_chosen. If the controller is a joystick, remove
   set_joystick_flag().

   IMPORTANT: don't accept KEY_PAUSE!

   Before doing anything else, choose_update_joystick() should check that
   'joystick_flag' has been set. If this is true, then choose_control is
   guaranteed to point to a joystick CONTROL struct. At this point, call
   poll_joystick(), and use the joy[] array to determine whether a simulated
   key has been pressed.
*/
void choose_update_keyboard(int k) {

	/* If key release */
	if (k >> 7)
		choose_key[k & 127] = 0;

	/* If key pressed, and it's not pause */
	else if ((choose_key[k & 127] == 0) && ((k & 127) != KEY_PAUSE))
		chosen_key = k & 127;

	choose_key[k & 127] = !(k >> 7);

} END_OF_FUNCTION(choose_update_keyboard);


void choose_update_joystick() {

	int i, j, k;

    int joy_polled = 0;

	if (joystick_flag == 0) return;

	poll_joystick();
	joy_polled = 1;
	joystick_flag = 0;

	for (i = 0; i < MAX_JOYSTICKS; i++) {
		for (j = 0; j < MAX_JOYSTICK_STICKS; j++) {
			for (k = 0; k < MAX_JOYSTICK_AXIS; k++) {
				if (joy[i].stick[j].axis[k].d1 && !choose_joy[i].stick[j].axis[k].d1) {
					chosen_joy_num = i;
					chosen_joy_stick = j;
					chosen_joy_axis = k;
					goto end_check;
				}
				if (joy[i].stick[j].axis[k].d2 && !choose_joy[i].stick[j].axis[k].d2) {
					chosen_joy_num = i;
					chosen_joy_stick = j;
					chosen_joy_axis = -k-1;
					goto end_check;
				}
				choose_joy[i].stick[j].axis[k].d2 = joy[i].stick[j].axis[k].d2;
				choose_joy[i].stick[j].axis[k].d1 = joy[i].stick[j].axis[k].d1;
			}
		}
		for (j = 0; j < MAX_JOYSTICK_BUTTONS; j++) {
			if (joy[i].button[j].b && !choose_joy[i].button[j].b) {
				chosen_joy_num = i;
				chosen_joy_button = j;
				goto end_check;
			}			
			choose_joy[i].button[j].b = joy[i].button[j].b;
		}
	}

end_check:

	return;
}



/* initialise_choose_key(). This function will:

   - Initialise choose_key[] so it reflects the current status of key[].
   - Set choose_control to point to the control struct passed.
   - Set choose_k equal to k.
   - Set key_chosen to 0.
   - Install choose_update_keyboard() as Allegro's keyboard_lowlevel_callback
     function.

   If the controller is a joystick, it will then install set_joystick_flag()
   as a timer handler to be called at a rate of 40 Hz.

   Make sure you FIRST deactivated the control before proceding with this function.
*/
void initiate_choose_key(CONTROL *control, int k) {

	int i;

	choose_control = control;
	choose_k = k;
	chosen_key = -1;
	chosen_joy_num = -1;
	chosen_joy_axis = -1;
	chosen_joy_stick = -1;
	chosen_joy_button = -1;

	if (control->type == CONTROL_TYPE_KEYBOARD) {
		/* Init chosen_key array */
		for (i = 0; i < KEY_MAX; i++)
			choose_key[i] = key[i];

		keyboard_lowlevel_callback = choose_update_keyboard;
	}
	else if (control->type == CONTROL_TYPE_JOYSTICK) {
		/* Init chosen joy array */
		memcpy(choose_joy, joy, sizeof(JOYSTICK_INFO) * MAX_JOYSTICKS);

		install_int_ex(set_joystick_flag, BPS_TO_TIMER(40));
	}
	else
		return;

	clear_keybuf();
}

/* control_choose_key(). This function applies the following tests in order:

   If choose_control is null, a key on the joystick was accepted. Nothing to
   do but return 1.

   If key_chosen is zero, no key has yet been detected. Return 0.

   If key_chosen is KEY_ESC, the detection was cancelled. Clear the keyboard
   buffer and return -1.

   Otherwise, a key on the keyboard was accepted. Write the value of
   key_chosen into the choose_control struct for key 'choose_k'. Set
   choose_control to null. Then clear the keyboard buffer, and return 1.
*/
int control_choose_key() {

	if (!choose_control)
		return 1;

	if (choose_control->type == CONTROL_TYPE_KEYBOARD) {

		if (chosen_key == KEY_ESC) {
			clear_keybuf();
			keyboard_lowlevel_callback = NULL;
			key[KEY_ESC] = 0;
			return -1;
		}

		if (chosen_key == -1)
			return 0;

		choose_control->dev.keyboard.key_id[choose_k] = chosen_key;
		LOG("Set key %i to %i\n", choose_k, chosen_key);
		clear_keybuf();
		keyboard_lowlevel_callback = NULL;
		key[chosen_key] = 0;

		return 1;
	}
	else if (choose_control->type == CONTROL_TYPE_JOYSTICK) {

		if (keypressed() && ((readkey() >> 8) == KEY_ESC)) {
			clear_keybuf();
			remove_int(set_joystick_flag);
			return -1;
		}

		if (chosen_joy_num == -1)
			return 0;

		choose_control->dev.joystick.joy_num[choose_k] = chosen_joy_num;
		choose_control->dev.joystick.stick[choose_k] = chosen_joy_stick;
		choose_control->dev.joystick.axis[choose_k] = chosen_joy_axis;
		choose_control->dev.joystick.button[choose_k] = chosen_joy_button;
		remove_int(set_joystick_flag);

		return 1;
	}

	return 1;
}


/* These will be set up by initialise_control(). Devices will be autodetected
   and names will be filled in. Entry 0 is guaranteed to be "Off".
*/
char *ControlDeviceName[MAX_DEVICES];
int ControlDeviceType[MAX_DEVICES];
int ControlDeviceID[MAX_DEVICES];
int NumControlDevices;

/* human_device is set to the keyboard device index, and zig_ai_device is set
   to the Zig AI device's index. These are used if previous controller
   options could not be loaded, or all were set to "Off".
   They are set to -1 if no corresponding devices are found.
*/
int human_device;
int zig_ai_device;


/* control_identify_device(): this takes an id and type and compares it against
   all the entries in ControlDevice*. If a match is found, it returns the
   device number. If no match is found, it returns 0, the index corresponding
   to "Off".
*/
int control_identify_device(int type, int id) {

	int i;

	for (i = 0; i < NumControlDevices; i++) {

		if (ControlDeviceType[i] == type &&
            (ControlDeviceID[i] < 0 || ControlDeviceID[i] == id))
			return i;
	}

	return 0;
}

void initialise_control_2() {

	LOCK_FUNCTION(choose_update_keyboard);
	LOCK_VARIABLE(choose_key);
	LOCK_VARIABLE(choose_joy);
	LOCK_VARIABLE(choose_control);
	LOCK_VARIABLE(chosen_key);
	LOCK_VARIABLE(chosen_joy_num);
	LOCK_VARIABLE(chosen_joy_stick);
	LOCK_VARIABLE(chosen_joy_axis);
	LOCK_VARIABLE(chosen_joy_button);
}
