// Game Controller Support
//		  by
// dbox (dwbox@hotmail.com)
// -------------------------------------------------------
// If would like to use this code you are free to do so.
// The only condition placed on its use use is that you
// credit the author.
//
// Last Modified: 04/16/2007


#include "SDL_events.h"
#include "SDL.h"
//#include "malloc.h"

// define starting offsets of each type of joystick keymap in the keymap.cfg file
#define JOYBUTTONKEYMAP 320
#define JOYAXISKEYMAP	340
#define JOYHATKEYMAP	360

#define MOUSELBUTTON -1

struct joyaxis
{
	int value;
	bool min_pressed;
	bool max_pressed;
};

struct joymenuaction
{
	char* label;
	char* action;
	~joymenuaction()
	{
		if (label == NULL)
			delete label;
		if (action == NULL)
			delete action;
	}
};

class joystick
{
private:
	int HAT_POS[9];
	char* HAT_POS_NAME[9];
	
	bool enabled;
	int axis_count;
	int hat_count;
	int button_count;
	int* last_hat_move;
	joyaxis* jmove;
	
	int jxaxis;
	int jyaxis;
	int jmove_xaxis;
	int jmove_yaxis;
	bool jmove_left_pressed;
	bool jmove_right_pressed;
	bool jmove_up_pressed;
	bool jmove_down_pressed;
	
	vector<joymenuaction*> joyactions;
	vector<char*> joysayactions;
	
	int get_hat_pos(int pov);
	void process_hat(int index, int current);
	
	void show_menu();
	void build_action_menu(char* key);
	void add_action_menuitem(char* menuscript, char* key, char* name, char* action, char* curr_action);
	char* get_keyaction_label(char* keyname);
	
	void add_joyaction(char* label, char* action);
	void add_joysayaction(char* msg);
	
public:
	joystick(void);
	~joystick(void);
	
	void init();
	void process_event(SDL_Event* event);
	void move();
};

// look up the joystick variables from the config.cfg file
VARP(joyfovxaxis, 1, 1, 10);
VARP(joyfovyaxis, 1, 2, 10);
VARP(joysensitivity, 1, 8, 10);
VARP(joyaxisminmove, 1, 6, 30);
VARP(joyfovinvert, 0, 1, 1);
VARP(joyshowevents, 0, 0, 1);
VARP(joymousebutton, 0, 0, 100);

joystick::joystick(void)
{
	CCOMMAND(joystick, joyaction, "ss", self->add_joyaction(args[0], args[1]));
	CCOMMAND(joystick, joyactionsay, "s", self->add_joysayaction(args[0]));
	
	enabled = false;
	axis_count = 0;
	jmove = NULL;
	
	hat_count = 0;
	last_hat_move = NULL;
	HAT_POS[0] = SDL_HAT_CENTERED;
	HAT_POS[1] = SDL_HAT_UP;
	HAT_POS[2] = SDL_HAT_RIGHTUP;
	HAT_POS[3] = SDL_HAT_RIGHT;
	HAT_POS[4] = SDL_HAT_RIGHTDOWN;
	HAT_POS[5] = SDL_HAT_DOWN;
	HAT_POS[6] = SDL_HAT_LEFTDOWN;
	HAT_POS[7] = SDL_HAT_LEFT;
	HAT_POS[8] = SDL_HAT_LEFTUP;
	
	HAT_POS_NAME[0] = "CENTER";
	HAT_POS_NAME[1] = "NORTH";
	HAT_POS_NAME[2] = "NE";
	HAT_POS_NAME[3] = "EAST";
	HAT_POS_NAME[4] = "SE";
	HAT_POS_NAME[5] = "SOUTH";
	HAT_POS_NAME[6] = "SW";
	HAT_POS_NAME[7] = "WEST";
	HAT_POS_NAME[8] = "NW";
	
	jxaxis = 0;
	jyaxis = 0;
}

joystick::~joystick(void)
{
	// clean up
	if (jmove != NULL)
		free(jmove);
	if (last_hat_move != NULL)
		free(last_hat_move);
		
	joyactions.deletecontentsp();
	joysayactions.deletecontentsp();
}

void joystick::init()
{
	// initialize joystick support
	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
	{
		enabled = false;
		return;
	}
	
	// check for any joysticks
	if (SDL_NumJoysticks() < 1 )
	{
		enabled = false;
		return;
	}
	
	// open the first registered joystick
	SDL_Joystick* stick = SDL_JoystickOpen(0);
	if (stick == NULL)
	{
		// there was some sort of problem, bail
		enabled = false;
		return;
	}
	
	// how many axes are there?
	axis_count = SDL_JoystickNumAxes(stick);
	if (axis_count < 2)
	{
		// sorry, we need at least one set of axes to control fov
		enabled = false;
		return;
	}
	
	// initialize the array of joyaxis structures used to maintain
	// axis position information
	if (jmove != NULL)
		free(jmove);
	jmove = (joyaxis*)malloc(sizeof(joyaxis)*axis_count);
	
	// initialize any hat controls
	hat_count = SDL_JoystickNumHats(stick);
	if (hat_count > 0)
	{
		if (last_hat_move != NULL)
			free(last_hat_move);
		last_hat_move = (int*)malloc(sizeof(int)*hat_count);
		for (int i=0; i < hat_count; i++)
			last_hat_move[i] = 0;
	}
	
	// how many buttons do we have to work with
	button_count = SDL_JoystickNumButtons(stick);
	
	// if we made it this far, everything must be ok
	enabled = true;
	
	// initialize the joystick settings from the joystick.cfg file
	// exec("data/joystick.cfg"); - see data/packages/fps/defaults.cfg
	
	// initialize the joystick configuration menus
	CCOMMAND(joystick, joymenu, "", self->show_menu());
}

// translate joystick events into keypress events
void joystick::process_event(SDL_Event* event)
{
	if (!enabled)
		return;
		
	int hat_pos;
	
	if ((joymousebutton-1) == event->button.button &&
			(event->type == SDL_JOYBUTTONDOWN || event->type == SDL_JOYBUTTONUP))
	{
		keypress(MOUSELBUTTON, event->button.state!=0, 0);
	}
	
	switch (event->type)
	{
		case SDL_JOYBUTTONDOWN:
		case SDL_JOYBUTTONUP:
		if (joyshowevents)
			conoutf("JOYBUTTON%d: %s", event->button.button+1,
					(event->button.state == SDL_PRESSED) ? "DOWN" : "UP");
		keypress(event->button.button + JOYBUTTONKEYMAP, event->button.state==SDL_PRESSED, 0);
		break;
		
		case SDL_JOYAXISMOTION:
		if (joyshowevents)
			conoutf("JOYAXIS%d: %d", event->jaxis.axis+1, event->jaxis.value);
			
		if (event->jaxis.axis == joyfovxaxis-1)
			jxaxis = event->jaxis.value;
		else if (event->jaxis.axis == joyfovyaxis-1)
			jyaxis = event->jaxis.value;
			
		jmove[event->jaxis.axis].value = event->jaxis.value;
		break;
		
		case SDL_JOYHATMOTION:
		hat_pos = get_hat_pos(event->jhat.value);
		if (joyshowevents && hat_pos)
			conoutf("JOYPAD%d%s", event->jhat.hat+1, HAT_POS_NAME[hat_pos]);
		process_hat(event->jhat.hat, hat_pos);
		break;
	}
}

int joystick::get_hat_pos(int hat)
{
	for (int i=0; i < 9; i++)
		if (hat == HAT_POS[i])
			return i;
	return 0;
}

// translate any hat movements into keypress events
void joystick::process_hat(int index, int current)
{
	if (last_hat_move[index] > 0)
	{
		int key = JOYHATKEYMAP + index*8 + last_hat_move[index] - 1;
		keypress(key, false, 0);
	}
	last_hat_move[index] = current;
	if (current > 0)
	{
		int key = JOYHATKEYMAP + index*8 + last_hat_move[index] - 1;
		keypress(key, true, 0);
	}
}

void joystick::move()
{
	if (!enabled)
		return;
		
	// move the player's field of view
	float dx = jxaxis/3200.0;
	float dy = jyaxis/3200.0;
	
	float jsensitivity = .1f * joysensitivity;
	
	if (dx < 0.0f)
		dx *= dx * jsensitivity * -1;
	else
		dx *= dx * jsensitivity;
		
	if (dy < 0.0f)
		dy *= dy * jsensitivity * -1;
	else
		dy *= dy * jsensitivity;
		
	if (joyfovinvert)
		dy *= -1;
		
	if (fabs(dx) >= 1.0f || fabs(dy) >= 1.0f)
		mousemove((int)dx, (int)dy);
		
		
	// translate any axis movements into keypress events
	for (int i=0; i < axis_count; i++)
	{
		int key = JOYAXISKEYMAP + i * 2;
		if (jmove[i].value > joyaxisminmove * 1000)
		{
			if (jmove[i].min_pressed)
				keypress(key, false, 0);
			jmove[i].min_pressed = false;
			if (!jmove[i].max_pressed)
				keypress(key+1, true, 0);
			jmove[i].max_pressed = true;
		}
		else if (jmove[i].value < joyaxisminmove * -1000)
		{
			if (jmove[i].max_pressed)
				keypress(key+1, false, 0);
			jmove[i].max_pressed = false;
			if (!jmove[i].min_pressed)
				keypress(key, true, 0);
			jmove[i].min_pressed = true;
		}
		else
		{
			if (jmove[i].max_pressed)
				keypress(key+1, false, 0);
			jmove[i].max_pressed = false;
			if (jmove[i].min_pressed)
				keypress(key, false, 0);
			jmove[i].min_pressed = false;
		}
	}
}

// These declarations should probably go in one or more of the
// existing cube header files.  They are defined here for the
// time being to reduce the number of files affected by the
// joystick modifications.
extern int cleargui(int n=0);
extern char* getkeyaction(char* key);


void joystick::show_menu()
{
	cleargui();
	
	char label[200];
	char command[500];
	char item[500];
	char menuscript[5000];
	
	strcpy(menuscript, "newgui joyfovxaxis [\n");
	for (int i=0; i < axis_count; i++)
	{
		if (i+1 == joyfovxaxis)
			sprintf(label, "[axis %d]", i+1);
		else
			sprintf(label, " axis %d", i+1);
		sprintf(command, "joyfovxaxis %d; joymenu", i+1);
		sprintf(item, "guibutton \"%s\" \"%s\"\n", label, command);
		strcat(menuscript, item);
	}
	strcat(menuscript, "]\n");
	execute(menuscript);
	
	strcpy(menuscript, "newgui joyfovyaxis [\n");
	for (int i=0; i < axis_count; i++)
	{
		if (i+1 == joyfovyaxis)
			sprintf(label, "[axis %d]", i+1);
		else
			sprintf(label, " axis %d", i+1);
		sprintf(command, "joyfovyaxis %d; joymenu", i+1);
		sprintf(item, "guibutton \"%s\" \"%s\"\n", label, command);
		strcat(menuscript, item);
	}
	strcat(menuscript, "]\n");
	execute(menuscript);
	
	char buffer[200];
	strcpy(menuscript, "newgui joymousebutton [\n");
	strcat(menuscript, "guititle \"emulate mouse button\"\n");
	if (joymousebutton == 0)
	{
		sprintf(buffer, "guibutton \"[not set]\" \"joymousebutton -1; joymenu\"\n");
		strcat(menuscript, buffer);
	}
	else
		strcat(menuscript, "guibutton \" not set\" \"joymousebutton -1; joymenu\"\n");
	for (int i=0; i < button_count; i++)
	{
		if (i+1 == joymousebutton)
			sprintf(label, "[button %d]", i+1);
		else
			sprintf(label, " button %d ", i+1);
			
		sprintf(command, "joymousebutton %d; joymenu", i+1);
		sprintf(item, "guibutton \"%s\" \"%s\"\n", label, command);
		strcat(menuscript, item);
	}
	strcat(menuscript, "]\n");
	execute(menuscript);
	
	// create the main joystick menu
	strcpy(menuscript, "newgui joystick [\n");
	strcat(menuscript, "guitext \"Joystick Configuration\"\n");
	strcat(menuscript, "guibar \n");
	
	sprintf(buffer, "guibutton \"look left/right\t[axis %d]\" \"showgui joyfovxaxis\"\n", joyfovxaxis);
	strcat(menuscript, buffer);
	sprintf(buffer, "guibutton \"look up/down\t[axis %d]\" \"showgui joyfovyaxis\"\n", joyfovyaxis);
	strcat(menuscript, buffer);
	strcat(menuscript, "guibar \n");
	strcat(menuscript, "guitext \"look sensitivity\"\n");
	strcat(menuscript, "guislider \"joysensitivity\"\n");
	strcat(menuscript, "guibar \n");
	strcat(menuscript, "guicheckbox \"invert look\" \"joyfovinvert\"\n");
	strcat(menuscript, "guicheckbox \"show events\" \"joyshowevents\"\n");
	strcat(menuscript, "guibar \n");
	if (joymousebutton > 0)
	{
		sprintf(buffer, "guibutton \"emulate mouse \t[button %d]\" \"showgui joymousebutton\"\n", joymousebutton);
		strcat(menuscript, buffer);
	}
	else
		strcat(menuscript, "guibutton \"emulate mouse\" \"showgui joymousebutton\"\n");
		
	strcat(menuscript, "guitab \"buttons\"\n");
	char keyname[50];
	for (int i=0; i < button_count; i++)
	{
		sprintf(keyname, "joybutton%d", i+1);
		char* action = get_keyaction_label(keyname);
		if (action != NULL && strlen(action) > 30)
			strcpy(action+25, "... ");
		sprintf(label, "button %d\t\t[%s]", i+1, (action == NULL) ? "" : action);
		sprintf(command, "showgui %s", keyname);
		sprintf(item, "guibutton \"%s\" \"%s\"\n", label, command);
		strcat(menuscript, item);
		build_action_menu(keyname);
		if (action != NULL)
			delete action;
	}
	
	strcat(menuscript, "guitab \"pad\"\n");
	for (int i=0; i < hat_count; i++)
	{
		for (int j=1; j < 9; j++)
		{
			char hatpos[20];
			strcpy(hatpos, HAT_POS_NAME[j]);
			for(char *x = hatpos; *x; x++)
				*x = tolower(*x);
				
			sprintf(keyname, "joyhat%d%s", i+1, hatpos);
			char* action = get_keyaction_label(keyname);
			sprintf(label, "pad %d %s  \t[%s]", i+1, hatpos, (action == NULL) ? "" : action);
			sprintf(command, "showgui %s", keyname);
			sprintf(item, "guibutton \"%s\" \"%s\"\n", label, command);
			strcat(menuscript, item);
			build_action_menu(keyname);
			if (action != NULL)
				delete action;
		}
	}
	
	strcat(menuscript, "guitab \"axis\"\n");
	strcat(menuscript, "guitext \"axis event sensitivity\"\n");
	strcat(menuscript, "guislider \"joyaxisminmove\"\n");
	strcat(menuscript, "guibar \n");
	char* minmax;
	for (int i=0; i < axis_count; i++)
	{
		for (int j=0; j < 2; j++)
		{
			if (j)
				minmax = "max";
			else
				minmax = "min";
			sprintf(keyname, "joyaxis%d%s", i+1, minmax);
			char* action = get_keyaction_label(keyname);
			sprintf(label, "axis %d %s\t[%s]", i+1, minmax, (action == NULL) ? "" : action);
			sprintf(command, "showgui %s", keyname);
			sprintf(item, "guibutton \"%s\" \"%s\"\n", label, command);
			strcat(menuscript, item);
			build_action_menu(keyname);
			if (action != NULL)
				delete action;
		}
	}
	
	strcat(menuscript, "]\n");
	execute(menuscript);
	
	showgui("joystick");
}

// if the returned pointer is not NULL, it should be deleted
// by the calling method
char* joystick::get_keyaction_label(char* keyname)
{
	char* action = getkeyaction(keyname);
	if (action != NULL)
	{
		action = newstring(action);
		if (strlen(action) > 30)
			strcpy(action+25, "... ");
	}
	return action;
}

void joystick::build_action_menu(char* key)
{
	char item[200];
	char menuscript[5000];
	sprintf(menuscript, "newgui %s [\n", key);
	
	char* curr_action = getkeyaction(key);
	if (!curr_action)
		curr_action = "";
		
	char command[50];
	sprintf(command, "bind %s []; joymenu", key);
	if (strcmp(curr_action, "") == 0 || strcmp(curr_action, " ") == 0)
		sprintf(item, "guibutton \"[none]\" \"%s\"\n", command);
	else
		sprintf(item, "guibutton \" none\" \"%s\"\n", command);
	strcat(menuscript, item);
	
	loopv(joyactions)
	add_action_menuitem(menuscript, key, joyactions[i]->label, joyactions[i]->action, curr_action);
	
	/*
	char buffer[1000];
	loopv(joysayactions)
	{
		sprintf(buffer, "[say \"%s\"]", joysayactions[i]);
		add_action_menuitem(menuscript, key, buffer, buffer, curr_action);
	}
	*/
	strcat(menuscript, "]\n");
	execute(menuscript);
}

void joystick::add_action_menuitem(char* menuscript, char* key, char* name, char* action, char* curr_action)
{
	char label[500];
	
	if (strcmp(action, curr_action) == 0)
		sprintf(label, "[%s]", name);
	else
		sprintf(label, " %s", name);
		
	char item[1000];
	sprintf(item, "guibutton \"%s\" [ bind %s [%s]; joymenu ]\n", label, key, action);
	strcat(menuscript, item);
}

void joystick::add_joyaction(char* label, char* action)
{
	joymenuaction*& maction = joyactions.add();
	maction = new joymenuaction();
	maction->label = newstring(label);
	maction->action = newstring(action);
}

void joystick::add_joysayaction(char* msg)
{
	char*& newmsg = joysayactions.add();
	newmsg = newstring(msg);
}
