/* Bob */

#include <string.h>

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



/* Dynamic list of pointers to the control structures. Whenever a control
   structure is created, its pointer is stored here. This is used so that the
   functions in this module can activate the controls - see below.
*/
CONTROL **ControlList = NULL;
int NumControls = 0;
static int ArraySize = 0;


#define PRESS_KEY(ctrl, c, bit) ctrl->key_time[c][ctrl->fifo_end[c]] = (true_game_time + 1) | ((bit) << 31)


static volatile int control_in_interrupt = 0;


/* update_keyboard(): during the game, this function is installed in
   Allegro's keyboard_lowlevel_callback variable. That means it is called
   every time a key is pressed or released. Bits 0 to 6 of the parameter are
   a KEY_* constant, while bit 7 is set for a release or clear for a press.

   This function will cycle through the ControlList array. For each keyboard
   controller, it will check whether the key pressed or released has been
   chosen as one of the player's keys. If so, it will store a press or
   release in the FIFO. It must be registered at time 'true_game_time',
   defined in timeloop.h. Releases are marked by setting the top bit.

   THIS FUNCTION AND ALL YOUR MEMORY IT USES ARE MUST BE LOCKED !!!
*/
void update_keyboard(int k) {

	int i, j;
	control_in_interrupt = 1;

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

		CONTROL *ctrl = ControlList[i];
		if (ctrl && ctrl->type == CONTROL_TYPE_KEYBOARD) {

			C_KEYBOARD *kb = &(ctrl->dev.keyboard);
			for (j = 0; j < CONTROL_NUM_KEYS; j++) {

				/* FIFO full? */
				if (ctrl->fifo_end[j] == ctrl->fifo_start[j])
					continue;

				/* Add key to FIFO */
				if (kb->key_id[j] == (k & 127)) {
					//LOG("Added %i event to key %i for %i in %i\n", (k & 128) >> 7, j, true_game_time + 1, ctrl->fifo_end[j]);
					PRESS_KEY(ctrl, j, ((k) & 128) >> 7); /* Schedule for later */				

					ctrl->fifo_end[j]++;
					if (ctrl->fifo_end[j] >= CONTROL_FIFO_SIZE)
						ctrl->fifo_end[j] = 0;
					if (ctrl->fifo_start[j] == -1)
						ctrl->fifo_start[j] = 0;
				}
			}
		}
	}
	control_in_interrupt = 0;
}
END_OF_FUNCTION(update_keyboard);



/* Unlike the keyboard, the joystick is not interrupt-driven. It must be
   regularly updated by calls to poll_joystick(). Hence there is little to be
   gained from recording the state of the joystick in an interrupt function.

   Instead, we use an interrupt function, set_joystick_flag(), to, duh, set
   joystick_flag. This function does nothing but set the variable
   'joystick_flag' to 1. It is installed as a timer interrupt to be called at
   a rate of 40 Hz.

   joystick_flag and set_joystick_flag() must both be locked in memory!

   update_joystick() is then responsible for reading the joystick. It need
   not be locked. It must be called as often as possible within reason. To
   that end, it will be called at several points during the draw_game()
   function in game.h. It will also be called once at the beginning of
   update_game() in game.h, to make sure the joysticks are up to date before
   we try to read their FIFOs.

   If joystick_flag is 0, this function returns without doing anything. That
   way we do not call poll_joystick() more often than necessary, since it is
   slow on some operating systems, coughwindowscough.

   Otherwise, update_joystick() first checks the ControlList array to see if
   any joysticks are in use.

   If joysticks are found to be in use, this function calls poll_joystick(),
   after which it iterates through the ControlList array for a second time.
   If any simulated key has changed, it stores a record in the FIFO, using
   'true_game_time' as the time at which it occurred.

   Finally it clears joystick_flag and returns.
*/
int joystick_flag = 0;    /* LOCK THIS! */

void set_joystick_flag(void) {
	joystick_flag = 1;
}
END_OF_FUNCTION(set_joystick_flag);



static int prev_key_press(CONTROL *ctrl, int k) {

	int j = ctrl->fifo_end[k] - 1;

	if (ctrl->fifo_start[k] == -1)
		return 0;
	
	if (j < 0)
		j = CONTROL_FIFO_SIZE - 1;

	return !(ctrl->key_time[k][j] >> 31);
}


void update_joystick() {
	int i, j, p;

    int joy_polled = 0;

    if (joystick_flag == 0) return;

    joystick_flag = 0;

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

		CONTROL *ctrl = ControlList[i];
		if (ctrl && (ctrl->type == CONTROL_TYPE_JOYSTICK)) {

			C_JOYSTICK *joys = &(ctrl->dev.joystick);

            if (joy_polled == 0) {
                poll_joystick();
                joy_polled = 1;
            }

			for (j = 0; j < CONTROL_NUM_KEYS; j++) {
				p = 0;

				/* FIFO full? */
				if (ctrl->fifo_end[j] == ctrl->fifo_start[j])
					continue;

				/* Add key to FIFO */
				if (joys->button[j] == -1) {

					int jn = joys->joy_num[j],
						an = joys->axis[j],
						sn = joys->stick[j];

					if (an < 0) {
						if (joy[jn].stick[sn].axis[-an-1].d2 && (!prev_key_press(ctrl, j))) {
							PRESS_KEY(ctrl, j, 0);
							p++;
						}
						else if ((!joy[jn].stick[sn].axis[-an-1].d2) && prev_key_press(ctrl, j)) {
							PRESS_KEY(ctrl, j, 1);
							p++;
						}
					}
					else {
						if (joy[jn].stick[sn].axis[an].d1 && (!prev_key_press(ctrl, j))) {
							PRESS_KEY(ctrl, j, 0);
							p++;
						}
						else if ((!joy[jn].stick[sn].axis[an].d1) && prev_key_press(ctrl, j)) {
							PRESS_KEY(ctrl, j, 1);
							p++;
						}
					}
				}
				else {
					int jn = joys->joy_num[j],
						bn = joys->button[j];

					if (joy[jn].button[bn].b && (!prev_key_press(ctrl, j))) {
						PRESS_KEY(ctrl, j, 0);
						p++;
					}
					else if (!joy[jn].button[bn].b && prev_key_press(ctrl, j)) {
						PRESS_KEY(ctrl, j, 1);
						p++;
					}
				}


				if (p) {
					ctrl->fifo_end[j]++;
					if (ctrl->fifo_end[j] >= CONTROL_FIFO_SIZE)
						ctrl->fifo_end[j] = 0;
					if (ctrl->fifo_start[j] == -1)
						ctrl->fifo_start[j] = 0;
				}
			}
		}
	}

	return;
}


/* control_key(): this will return nonzero if the corresponding key is held
   down, or zero otherwise. Note that this function can be used with
   joysticks and Zig AI; these controllers emulate the keyboard. The 'k'
   parameter is one of the CONTROL_KEY_* constants at the top of this file.

   You should call this just once per key per controller per physics frame,
   for reasons of efficiency. If you need its return value more than once,
   store it in a variable.

   This function uses the global game_time variable in timeloop.h. It does
   *NOT* use true_game_time.
*/
int control_key(CONTROL *ctrl, int k) {

	int i, t;

	i = ctrl->fifo_start[k];

	/* No keys */
	if (i < 0)
		return FALSE;

	for (t = 0; t < CONTROL_FIFO_SIZE; t++) {

		/* Look for a 'press' event */
		if (((ctrl->key_time[k][i] & 0x7FFFFFFF) <= (unsigned)game_time) && (!(ctrl->key_time[k][i] >> 31))) {
			return TRUE;
		}

		i++;
		if (i >= CONTROL_FIFO_SIZE)
			i = 0;
		if (i == ctrl->fifo_end[k])
			break;
	}

	return FALSE;
}


/* control_ack(): DESCRIPTION NOT ACCURATE

   Will pass through all control objects and shift the FIFO queue.
   Returns non-zero if there are still keys to be processed for
   this frame. Will remove the oldest keys (that have been processed).
   Returns 0 if there's nothing else to do.

   Usage: NOT ACCURATE - SEE s_player.h FOR CORRECT USAGE

     do {

       for (i = 0; i < num_players; i++)
          left = control_key(player[i]->control, CONTROL_LEFT);

     } while (control_ack());
 */
int control_ack(CONTROL *ctrl) {

	int j, k, ret = 0, t;

	void (*old_handler)(int);

	/* Wait for Keyboard interrupt to end */
	while (control_in_interrupt);
	/* Disable Keyboard interrupt */
	old_handler = keyboard_lowlevel_callback;
	keyboard_lowlevel_callback = NULL;
	while (control_in_interrupt);
	

	for (k = 0; k < CONTROL_NUM_KEYS; k++) {
		unsigned int press = 0xFFFFFFFF, release = 0xFFFFFFFF;

		j = ctrl->fifo_start[k];

		/* Empty queue */
		if (j == -1)
			continue;

		if (ctrl->fifo_start[k] == ctrl->fifo_end[k]) {
			int i;
			LOG("FIFO is full!! @ %i -- dumping\n", ctrl->fifo_end[k]);

			for (i = 0; i < CONTROL_FIFO_SIZE; i++)
				LOG(" %i ", ctrl->key_time[k][i]);
			LOG("\n\n");

			/* Hack to get the game going... */
			ctrl->fifo_end[k] = 0;
			ctrl->fifo_start[k] = -1;
		}

		/* Step 1, find 'press' event */
		for (t = 0; t < CONTROL_FIFO_SIZE; t++) {

			/* Look for a 'press' event */
			if ((ctrl->key_time[k][j] != 0xFFFFFFFF) && ((ctrl->key_time[k][j] & 0x7FFFFFFF) <= (unsigned)game_time) && (!(ctrl->key_time[k][j] >> 31))) {
				press = j;
				break;
			}
	
			j++;
			if (j >= CONTROL_FIFO_SIZE)
				j = 0;
			if (j == ctrl->fifo_end[k])
				break;
		}

		/* If there were no past events, then nothing to do */
		if (press == 0xFFFFFFFF)
			continue;

		/* Find associated 'release' event */
		for (t = 0; t < CONTROL_FIFO_SIZE; t++) {

			/* Look for a 'release' event */
			if ((ctrl->key_time[k][j] != 0xFFFFFFFF) && ((ctrl->key_time[k][j] & 0x7FFFFFFF) <= (unsigned)game_time) && ((ctrl->key_time[k][j] >> 31))) {
				release = j;
				break;
			}
		
			j++;
			if (j >= CONTROL_FIFO_SIZE)
				j = 0;
			if (j == ctrl->fifo_end[k])
				break;
		}

		/* There was no release event */
		if (release == 0xFFFFFFFF) {
			/* Move up press event for next frame */
			ctrl->key_time[k][press]++;
		}
		/* There was a release event */
		else {
			/* Dump them both */
			ctrl->key_time[k][press] = 0xFFFFFFFF;
			ctrl->key_time[k][release] = 0xFFFFFFFF;

			/* Advance the FIFO start marker */
			for (t = 0; t < CONTROL_FIFO_SIZE; t++) {
				if (ctrl->key_time[k][ctrl->fifo_start[k]] == 0xFFFFFFFF)
					ctrl->fifo_start[k]++;
				else
					break;

				if (ctrl->fifo_start[k] >= CONTROL_FIFO_SIZE)
					ctrl->fifo_start[k] = 0;
				if (ctrl->fifo_start[k] == ctrl->fifo_end[k]) {
					ctrl->fifo_start[k] = 0xFFFFFFFF;
					ctrl->fifo_end[k] = 0;
				}
			}
		}

		j = ctrl->fifo_start[k];

		if (j < 0)
			continue;

		/* Step 4, find left-over 'press' events */
		for (t = 0; t < CONTROL_FIFO_SIZE; t++) {

			/* Look for a 'press' event */
			if ((ctrl->key_time[k][j] != 0xFFFFFFFF) && ((ctrl->key_time[k][j] & 0x7FFFFFFF) <= (unsigned)game_time) && (!(ctrl->key_time[k][j] >> 31))) {
				ret |= 1;
				break;
			}
	
			j++;
			if (j >= CONTROL_FIFO_SIZE)
				j = 0;
			if (j == ctrl->fifo_end[k])
				break;
		}
	}

	/* Restore keyboard interrupt */
	keyboard_lowlevel_callback = old_handler;

	return ret;
}


static void add_control(CONTROL *c) {

	if ((ArraySize - NumControls) <= 1) {
		CONTROL **cl;

		if (ControlList) {
			UNLOCK_DATA(ControlList, sizeof(CONTROL*) * ArraySize);
		}
		cl = realloc(ControlList, sizeof(CONTROL*) * (ArraySize + 16));

		if (!cl) {
			LOG("--== Error ==-- Control::add_particle: Ran out of memory while allocating %i bytes.\n", sizeof(CONTROL*) * (ArraySize + 16));
			return;
		}

		ControlList = cl;
		ArraySize += 16;

		LOCK_DATA(ControlList, sizeof(CONTROL*) * ArraySize);
	}

	ControlList[NumControls] = c;

	NumControls++;

	return;
}

static void remove_control(CONTROL *c) {

	int i;

	for (i = 0; i < NumControls; i++) {
		if (ControlList[i] == c) {		
			ControlList[i] = ControlList[NumControls - 1];
			NumControls--;
			break;
		}
	}

	if (!NumControls) {
		if (ControlList) {
			UNLOCK_DATA(ControlList, sizeof(CONTROL*) * ArraySize);
			free(ControlList);
		}
		ControlList = NULL;
		ArraySize = 0;
	}
	else if ((ArraySize - NumControls) > 16) {
		CONTROL **cl;
		UNLOCK_DATA(ControlList, sizeof(CONTROL*) * ArraySize);
		ArraySize -= 16;
		ArraySize = MAX(16, ArraySize);		
		cl = realloc(ControlList, sizeof(CONTROL*) * ArraySize);

		if (!cl)
			LOG("--== Error ==-- Control::remove_control: Unable to resize block to %i bytes.\n", sizeof(CONTROL*) * ArraySize);
		else
			ControlList = cl;
	}
	return;
}



/* Constructor/Destructor */
CONTROL *create_control(int type) {

	CONTROL *ret;
	int i;

	ret = malloc(sizeof(CONTROL));

	if (!ret)
		return NULL;

	LOCK_DATA(ret, sizeof(CONTROL));
	memset(ret, 0, sizeof(CONTROL));

	for (i = 0; i < CONTROL_NUM_KEYS; i++) {
		int j;

		ret->fifo_start[i] = -1;
		ret->fifo_end[i] = 0;

		for (j = 0; j < CONTROL_FIFO_SIZE; j++)
			ret->key_time[i][j] = -1;
	}

	ret->type = type;

	add_control(ret);


	return ret;
}

void destroy_control(CONTROL *ctrl) {

	remove_control(ctrl);

	UNLOCK_DATA(ctrl, sizeof(CONTROL));
	free(ctrl);

	return;
}


void activate_control() {
	install_int_ex(set_joystick_flag, BPS_TO_TIMER(40));
	keyboard_lowlevel_callback = &update_keyboard;
}


void deactivate_control() {
	keyboard_lowlevel_callback = 0;
	remove_int(set_joystick_flag);
}


static int control_initialised = 0;

void control_identify_devices() {

	int i;

	if (ControlDeviceName[0])
		free(ControlDeviceName[0]);

	ControlDeviceName[0] = malloc(32 * MAX_DEVICES);
    if (!ControlDeviceName[0]) exit(37);
	for (i = 1; i < MAX_DEVICES; i++)
		ControlDeviceName[i] = ControlDeviceName[i - 1] + 32;

	/* Device 0 is 'off' */
	ustrcpy(ControlDeviceName[0], translate("cOff"));
	ControlDeviceType[0] = CONTROL_TYPE_OFF;
	ControlDeviceID[0] = -1;

	/* Device 1 is 'keyboard' */
	ustrcpy(ControlDeviceName[1], translate("Keyboard"));
	ControlDeviceType[1] = CONTROL_TYPE_KEYBOARD;
	ControlDeviceID[1] = -1;

	/* Devices 2+ are joysticks */
	for (i = 0; i < num_joysticks; i++) {
		usprintf(ControlDeviceName[2 + i], "%s %i", translate("Joystick"), i + 1);
		ControlDeviceType[2 + i] = CONTROL_TYPE_JOYSTICK;
		ControlDeviceID[2 + i] = i;
	}

	/* Final device is AI */
	i = 2 + num_joysticks;
	ustrcpy(ControlDeviceName[i], translate("ZigAI"));
	ControlDeviceType[i] = CONTROL_TYPE_ZIG_AI;
	ControlDeviceID[i] = -1;
	NumControlDevices = i + 1;

	human_device = 1;
	zig_ai_device = i;	

	return;
}

/* initialise_control(): this locks in memory all functions and variables in
   this module that will be used in an interrupt context. It also autodetects
   the control devices.

   NumControlDevices is the number of control devices detected (including
   "Off" and "Zig AI").

   ControlDeviceName[] is initialised with the names of the controllers, e.g.
   "Keyboard", "Joystick 2", "Zig AI".

   ControlDeviceType[] is initialised with CONTROL_TYPE_* constants.

   ControlDeviceID[] is initialised with a unique identifier where two
   controllers have the same type (e.g. two joysticks).
   It is called by the main module.
*/

void initialise_control() {

	if (control_initialised)
		return;

	/* Lock all memory */
	LOCK_FUNCTION(update_keyboard);
	LOCK_FUNCTION(set_joystick_flag);
	LOCK_VARIABLE(joystick_flag);
	LOCK_VARIABLE(control_in_interrupt);

    initialise_control_2();

	/* Begin device Identification */
	control_identify_devices();

	return;
}


void shut_down_control() {

	int i;

	deactivate_control();

	/* Wait for IRQ to end */
	while (control_in_interrupt);

	/* Forget installed devices */
	if (ControlDeviceName[0])
		free(ControlDeviceName[0]);

	for (i = 0; i < MAX_DEVICES; i++)
		ControlDeviceName[i] = NULL;

	NumControlDevices = 0;

	/* Get rid of all controls */
	while (NumControls)
		remove_control(ControlList[NumControls-1]);

	return;
}




