/*****************************************************************************/
/* with thanks to Danzel for remapsp source.                                 */
/*****************************************************************************/
#include <pspkernel.h>
#include <pspdebug.h>
#include <pspdisplay.h>
#include <pspctrl.h>
#include <pspsdk.h>
#include <psppower.h>
#include <stdio.h>
#include <string.h>
#include <pspreg.h>
#include "../sdk/outputdriver.h"
#include "../sdk/outputframework.h"
#include "blit.c"

PSP_MODULE_INFO("piKeyCtrlout", 0x1000, 1, 1);

#define	DRIVERVER	"1"
#define REQUIREDVER	1

#define IDLETHRESHOLD 20

/*****************************************************************************/
/* Write debug info to SIO?                                                  */
/*                                                                           */
/* 0 : off                                                                   */
/* 1 : major events                                                          */
/* 2 : debug events                                                          */
/*****************************************************************************/
#define DEBUG_OUTPUT 0

SceUID mainthread;

/*****************************************************************************/
/* Mask to apply to next key read                                            */
/*****************************************************************************/
int gkeymask;

/*****************************************************************************/
/* Mask to apply to all key reads                                            */
/*****************************************************************************/
int gholdmask;

/*****************************************************************************/
/* number of cycles since a keystuff                                         */
/*****************************************************************************/
int gidlecount = 0;

/*****************************************************************************/
/* Whether vshmode is active or not                                          */
/*****************************************************************************/
int gvshmode = 0;

/*****************************************************************************/
/* Key mapping table                                                         */
/*****************************************************************************/
typedef struct {
	int scancode;
	int buttoncode;
} KEYMAP_ENTRY;

#define ENTER_BUTTON  0xFFFF0000
#define CANCEL_BUTTON	0x0000FFFF
#define HELP_BUTTON   0x00FFFF00
#define ANALOG_UP     0xF0000001
#define ANALOG_DOWN   0xF0000002
#define ANALOG_LEFT   0xF0000003
#define ANALOG_RIGHT  0xF0000004

#define MAX_KEYTABLE  24

/*****************************************************************************/
/* Analog stick constants                                                    */
/*****************************************************************************/
#define LEFT    0
#define CENTER  127
#define RIGHT   250
#define TOP     0
#define BOTTOM  250

unsigned char analogx = CENTER;
unsigned char analogy = CENTER;

/*****************************************************************************/
/* This is the default keymap table.                                         */
/* Space is reserved for MAX_KEYTABLE entries.                               */
/*****************************************************************************/
KEYMAP_ENTRY keytable[MAX_KEYTABLE] =
{
	{ KEY_UP,          PSP_CTRL_UP },
	{ KEY_DOWN,        PSP_CTRL_DOWN },
	{ KEY_LEFT,        PSP_CTRL_LEFT },
	{ KEY_RIGHT,       PSP_CTRL_RIGHT },
	{ KEY_ENTER,       ENTER_BUTTON },
	{ KEY_ESC,         CANCEL_BUTTON },
	{ KEY_INSERT,      PSP_CTRL_TRIANGLE },
	{ KEY_RIGHTBRACE,  PSP_CTRL_TRIANGLE },
	{ KEY_DELETE,      PSP_CTRL_SQUARE },
	{ KEY_LEFTBRACE,   PSP_CTRL_SQUARE },
	{ KEY_MINUS,       PSP_CTRL_LTRIGGER },
	{ KEY_EQUAL,       PSP_CTRL_RTRIGGER },
	{ KEY_HOME,        PSP_CTRL_HOME },
	{ KEY_Q,           PSP_CTRL_HOME },
	{ KEY_TAB,         PSP_CTRL_SELECT },
	{ KEY_SPACE,       PSP_CTRL_START },
	{ KEY_F1,          HELP_BUTTON },
	{ KEY_SLASH,       HELP_BUTTON },
	{ 0, 0 }
};

// number of keymaps in the config
int num_keymaps = 18;

#include "keynames.c"

typedef struct MainHook
{
 u32  hookid;
 char modname[32];
 char libname[32];
 u32 nid;
 void *func;
 int vshmode;
} MainHook;

// *** FUNCTIONS DECLARATIONS ***
typedef int (*FUNC_CTRL) (SceCtrlData*, int);
typedef int (*PFNVOID)();
typedef int (*PFNVOIDP)(void *);
int vshCtrlReadBufferPositiveFake(SceCtrlData *pad_data, int count);
int sceCtrlReadBufferPositiveFake(SceCtrlData *pad_data, int count);
int sceCtrlPeekBufferPositiveFake(SceCtrlData *pad_data, int count);
int sceCtrlReadBufferNegativeFake(SceCtrlData *pad_data, int count);
int sceCtrlPeekBufferNegativeFake(SceCtrlData *pad_data, int count);

MainHook mainHookSave[] =
{
	{ 0, "sceVshBridge_Driver",   "sceVshBridge", 0xc6395c03, vshCtrlReadBufferPositiveFake, 1 },
	{ 0, "sceController_Service", "sceCtrl",      0x1F803938, sceCtrlReadBufferPositiveFake, 0 },
	{ 0, "sceController_Service", "sceCtrl",      0x3A622550, sceCtrlPeekBufferPositiveFake, 0 },
	{ 0, "sceController_Service", "sceCtrl",      0x60B81F86, sceCtrlReadBufferNegativeFake, 0 },
	{ 0, "sceController_Service", "sceCtrl",      0xC152080A, sceCtrlPeekBufferNegativeFake, 0 },
	{ 0, "", "", 0, NULL }
};

int getDriverInfo(DRIVERINFO *infostruct)
{
	strcpy(infostruct->driverVersion, DRIVERVER);
	if(infostruct->apiVersion < REQUIREDVER) return PIKEY_ERROR_VERSION;
	else {
		strcpy(infostruct->driverName, "CTRL");
		infostruct->apiVersion = PIKEY_THIS_VERSION;
		infostruct->unicodeSupported = FALSE;
		return PIKEY_SUCCESS;
	}
}

// Registry check
int get_registry_value(const char *dir, const char *name, unsigned int *val) {
	int ret = 0;

	struct RegParam reg;
	REGHANDLE h;

	memset(&reg, 0, sizeof(reg));
	reg.regtype = 1;
	reg.namelen = strlen("/system");
	reg.unk2 = 1;
	reg.unk3 = 1;
	strcpy(reg.name, "/system");
	if(sceRegOpenRegistry(&reg, 2, &h) == 0) {
		REGHANDLE hd;
		if(!sceRegOpenCategory(h, dir, 2, &hd)) {
			REGHANDLE hk;
			unsigned int type, size;

			if(!sceRegGetKeyInfo(hd, name, &hk, &type, &size)) {
				if(!sceRegGetKeyValue(hd, hk, val, 4)) {
					ret = 1;
					sceRegFlushCategory(hd);
				}
			}
			sceRegCloseCategory(hd);
		}
		sceRegFlushRegistry(h);
		sceRegCloseRegistry(h);
	}

	return ret;
}

void stuff_key(int keymask)
{
	gkeymask = keymask;
	scePowerTick(0);

	while (gkeymask != -1)
	{
		sceKernelDelayThread(2000);
	}
}

void hold_key(int holdmask)
{
	gholdmask = holdmask;
	scePowerTick(0);
}

char *helpmsg1[] =
{
      "   piKey Help Screen    ",
			"  Button Configuration  ",
			" ---------------------- ",
    	"                        ",
	    NULL
};

char *helpkeys[MAX_KEYTABLE] =
{
	" UP        =  UP        ",
	" DOWN      =  DOWN      ",
	" LEFT      =  LEFT      ",
	" RIGHT     =  RIGHT     ",
	" ENTER     =  CONFIRM   ",
	" ESC       =  CANCEL    ",
	" INS / ]   =  TRIANGLE  ",
	" END / [   =  SQUARE    ",
	" -         =  LTRIGGER  ",
	" =         =  RTRIGGER  ",
	" HOME / Q  =  HOME      ",
	" TAB       =  SELECT    ",
	" SPACE     =  START     ",
	" F1 / ?    =  HELP      ",
	" aaaaaaaaa =  aaaaaaaa  ",	// names here to avoid string literal sharing
	" bbbbbbbbb =  bbbbbbbb  ",
	" ccccccccc =  cccccccc  ",
	" ddddddddd =  dddddddd  ",
	" eeeeeeeee =  eeeeeeee  ",
	" fffffffff =  ffffffff  ",
	" ggggggggg =  gggggggg  ",
	" hhhhhhhhh =  hhhhhhhh  ",
	" iiiiiiiii =  iiiiiiii  ",
	" jjjjjjjjj =  jjjjjjjj  ",
};

char *helpmsg2[] =
{
    	"                        ",
    	" Ctrl-D    =  DANZEFF   ",
    	"                        ",
			"  Press any button on   ",
			" your keyboard to close ",
	    NULL
};


void displayHelp()
{
	int i;

	flushKeyPresses();

	for(;;) {
		sceKernelDelayThread(100);
		sceDisplayWaitVblankStart();
		font_init();

		int linenum = 1 + ((MAX_KEYTABLE - num_keymaps)/2);

		for(i = 0; helpmsg1[i] != NULL; i++)
		{
			blit_string(332, (linenum * 8), helpmsg1[i], 1);
			linenum++;
		}

		for(i = 0; i < num_keymaps; i++)
		{
			blit_string(332, (linenum * 8), helpkeys[i], 1);
			linenum++;
		}

		for(i = 0; helpmsg2[i] != NULL; i++)
		{
			blit_string(332, (linenum * 8), helpmsg2[i], 1);
			linenum++;
		}

 		if(numKeysPressed() > 0)
 		{
 			break;
 		}
	}

	flushKeyPresses();
}

/*****************************************************************************/
/* Find a key or button's value in the specified keytable.                   */
/*                                                                           */
/* Returns -1 for failure to find a match.                                   */
/*****************************************************************************/
int findKeyValue(KEYNAME_ENTRY *keytable, const char *keyname)
{
	int lreturn = -1;

	while ((lreturn == -1) && (keytable->keycode != 0))
	{
		if (strcmp(keyname, keytable->name) == 0)
		{
			lreturn = keytable->keycode;
		}
		else
		{
			keytable++;
		}
	}

	return lreturn;
}

char cfgtoken[64];
char cfgvalue[128];

int ctrloutMain(SceSize args, void *argp) {
	int enter, cancel, x;
	unsigned int value = 0;
	
	// wait for the libs we care about to be loaded
	waitForModule("sceController_Service");

	gvshmode = isVshMode();

	//Make sure kernel is in analog sampling mode!
	sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);
	
	// Patch syscall table, here the program use jmp func in stub table, so patch not applied here but in all user modules
	for (x=0;mainHookSave[x].func != NULL;x++)
	{
		// only hook funcs appropriate for the current mode
		if (mainHookSave[x].vshmode == gvshmode)
		{
		  mainHookSave[x].hookid = apiHookByNid(mainHookSave[x].modname,
																						mainHookSave[x].libname,
																						mainHookSave[x].nid,
																						mainHookSave[x].func);
			if (mainHookSave[x].hookid == -1)
			{
				sioPrint("ZZ");
				sioPrintHex(x);
			}
		}
	}

	get_registry_value("/CONFIG/SYSTEM/XMB", "button_assign", &value);
	if(value == 0) {
		enter = PSP_CTRL_CIRCLE;
		cancel = PSP_CTRL_CROSS;
	} else {
		enter = PSP_CTRL_CROSS;
		cancel = PSP_CTRL_CIRCLE;
	}

	// Read the config file for user-defined control map
	int cfg = configOpen("ms0:/seplugins/pikey/ctrlmap.txt");
	if (cfg >= 0)
	{
		num_keymaps = 0;
		while (configRead(cfg, cfgtoken, cfgvalue))
		{
#if DEBUG_OUTPUT>=3
			sioPrint(cfgtoken);
			sioPrint("(");
			sioPrintWord(findKeyValue(key_names, cfgtoken));
			sioPrint(") = ");
			sioPrint(cfgvalue);
			sioPrint("(");
			sioPrintWord(findKeyValue(button_names, cfgvalue));
			sioPrint(")\r\n");
#endif

			// write the config into the keymap table
			keytable[num_keymaps].buttoncode = findKeyValue(button_names, cfgvalue);
			keytable[num_keymaps].scancode    = findKeyValue(key_names, cfgtoken);

			// write the names into the help table
			int vallen = strlen(cfgvalue);
			int toklen = strlen(cfgtoken);
			int ii;
			for (ii = 1; ii <= 9; ii++)
			{
				if (ii <= toklen)
				{
					helpkeys[num_keymaps][ii] = cfgtoken[ii-1];
				}
				else
				{
					helpkeys[num_keymaps][ii] = ' ';
				}

				if (ii <= vallen)
				{
					helpkeys[num_keymaps][ii + 12] = cfgvalue[ii-1];
				}
				else
				{
					helpkeys[num_keymaps][ii + 12] = ' ';
				}
			}

			num_keymaps++;

			if (num_keymaps >= (MAX_KEYTABLE - 1))
			{
				sioPrint("!! Too many keymap table entries !!\r\n");
				break;
			}
		}

		// write terminator.
		keytable[num_keymaps].buttoncode = 0;
		keytable[num_keymaps].scancode = 0;

		configClose(cfg);
	}

	sioPrint("CTRL out ready.\r\n");

	for(;;) {	

		// we check for a series of keycodes, changing our keymask as appropriate.
		int ii;
		int keymask = 0;
		analogx = CENTER;
		analogy = CENTER;

		for (ii = 0; keytable[ii].scancode != 0; ii++)
		{
			if (isKeyPressed(keytable[ii].scancode))
			{
				int addmask = keytable[ii].buttoncode;

				if (addmask == ENTER_BUTTON)
				{
					addmask = enter;
				}
				else if (addmask == CANCEL_BUTTON)
		 		{
					addmask = cancel;
				}
				else if (addmask == HELP_BUTTON)
				{
					displayHelp();
					addmask = 0;
				}
				else if (addmask == ANALOG_LEFT)
				{
					analogx = LEFT;
					addmask = 0;
				}
				else if (addmask == ANALOG_RIGHT)
				{
					analogx = RIGHT;
					addmask = 0;
				}
				else if (addmask == ANALOG_UP)
				{
					analogy = TOP;
					addmask = 0;
				}
				else if (addmask == ANALOG_DOWN)
				{
					analogy = BOTTOM;
					addmask = 0;
				}

				keymask |= addmask;
			}
		}

		hold_key(keymask);

		sceKernelDelayThread(1000);
	}

	sceKernelExitGame();
	return 0;
}

int main(void) {
	// initialise some variables.
	gkeymask  = 0;
	gholdmask = 0;

	// create main thread
	mainthread = sceKernelCreateThread("ctrlout", ctrloutMain, 16, 0x800, 0, NULL);
	if (mainthread >= 0)
	{
    sceKernelStartThread(mainthread, 0, NULL);
	}
	else
	{
		return mainthread;
	}

	return 0;
}

void module_stop(void) {
}

void* getModuleInfo(void) {
	return (void *) &module_info;
}

int module_start(SceSize args, void *argp) __attribute__((alias("_start")));

int _start(SceSize args, void *argp)
{
	return main();
}

void add_values(SceCtrlData *pad_data, int count, int neg)
{
	int i;
	int intc;

	intc = pspSdkDisableInterrupts();
	for(i = 0; i < count; i++)
	{
		// process any held keys
		if(neg)
		{
			pad_data[i].Buttons &= ~gholdmask;
		}
		else
		{
			pad_data[i].Buttons |= gholdmask;
		}

		if (gkeymask != -1)
		{
			// and now single presses
			if(neg)
			{
				pad_data[i].Buttons &= ~gkeymask;
			}
			else
			{
				pad_data[i].Buttons |= gkeymask;
			}

			// this forces us to go through a single "no press" cycle
			// between keys.
			if (gkeymask == 0)
			{
				gkeymask = -1;
			}
			else
			{
				gkeymask = 0;
			}
			gidlecount = 0;
		}
		else
		{
			gidlecount ++;
		}

		// handle analog overrides
		if (analogx != CENTER)
		{
			pad_data[i].Lx = analogx;
		}
		if (analogy != CENTER)
		{
			pad_data[i].Ly = analogy;
		}
	}
	pspSdkEnableInterrupts(intc);
}

/*****************************************************************************/
/* button hooks start here                                                   */
/*****************************************************************************/
int vshCtrlReadBufferPositiveFake(SceCtrlData *pad_data, int count)
{
	u32 k1 = pspSdkSetK1(0);

	int res = callApiHook(mainHookSave[0].hookid,pad_data, count);

	add_values(pad_data, res, 0);

	pspSdkSetK1(k1);
	
	return res;
}

int sceCtrlReadBufferPositiveFake(SceCtrlData *pad_data, int count)
{
	u32 k1 = pspSdkSetK1(0);
	int ret = callApiHook(mainHookSave[1].hookid,pad_data, count);
  if(ret <= 0)
  {
    return ret;
  }

  add_values(pad_data, ret, 0);

	pspSdkSetK1(k1);

  return ret;
}

int sceCtrlPeekBufferPositiveFake(SceCtrlData *pad_data, int count)
{
	u32 k1 = pspSdkSetK1(0);
	int ret = callApiHook(mainHookSave[2].hookid,pad_data, count);
  if(ret <= 0)
  {
    return ret;
  }

  add_values(pad_data, ret, 0);

	pspSdkSetK1(k1);

  return ret;
}

int sceCtrlReadBufferNegativeFake(SceCtrlData *pad_data, int count)
{
	u32 k1 = pspSdkSetK1(0);
	int ret = callApiHook(mainHookSave[3].hookid,pad_data, count);
  if(ret <= 0)
  {
    return ret;
  }

  add_values(pad_data, ret, 1);

	pspSdkSetK1(k1);

  return ret;
}

int sceCtrlPeekBufferNegativeFake(SceCtrlData *pad_data, int count)
{
	u32 k1 = pspSdkSetK1(0);
	int ret = callApiHook(mainHookSave[4].hookid,pad_data, count);
  if(ret <= 0)
  {
    return ret;
  }

  add_values(pad_data, ret, 1);

	pspSdkSetK1(k1);

  return ret;
}

