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

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

#define	DRIVERVER	"1"
#define REQUIREDVER	1

#define IDLETHRESHOLD 20

#define DEBUG_OUTPUT 0

SceUID mainthread;

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

/*****************************************************************************/
/* whether osk is active or not                                              */
/*****************************************************************************/
int kbdstate;

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

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

/*****************************************************************************/
/* Automatic Select Config Counts                                            */
/*****************************************************************************/
int gXmbSelNum = 0;
int gBrwSelNum = 0;

/*****************************************************************************/
/* sceUtilityOskGetStatus : dynamically linked.                              */
/*****************************************************************************/
int (*oskGetStatus_func)(void) = NULL;

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, "OSK");
		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;
}

char * standardkeys[4][3] =
{
	{ ".,!?@\"'`:;()<>[]{}1", "abcABC2", "defDEF3" },
	{ "ghiGHI4",              "jklJKL5", "mnoMNO6" },
	{ "pqrsPQRS7",            "tuvTUV8", "wxyzWXYZ9" },
	{ "",                     "/-~=_*\001\001+#\001\001$\001\001\001\001%&\\^| 0", "" }
};

int currx, curry;

// find the position of a given char in the keys array
//  (z is the number of presses required to get that char once in the right set)
void find_coords(char ch, int *x, int *y, int *z)
{
  int lx, ly, lz;

	*x = 0;
	*y = 0;
	*z = 0;

	for (lx = 0; lx < 3; lx++)
	{
		for (ly = 0; ly < 4; ly++)
		{
			for (lz = 0; lz < strlen(standardkeys[ly][lx]); lz++)
			{
				if (standardkeys[ly][lx][lz] == ch)
				{
					*x = lx;
					*y = ly;
					*z = lz;
					return;
				}
			}
		}
	}
}

static struct SceLibraryEntryTable *_libsFindLibrary(SceUID uid, const char *library)
{
	struct SceLibraryEntryTable *entry;
	SceModule *pMod;
	void *entTab;
	int entLen;

	pMod = sceKernelFindModuleByUID(uid);
	if(pMod != NULL)
	{
		int i = 0;

		entTab = pMod->ent_top;
		entLen = pMod->ent_size;
		while(i < entLen)
		{
			entry = (struct SceLibraryEntryTable *) (entTab + i);

			if((entry->libname) && (strcmp(entry->libname, library) == 0))
			{
				return entry;
			}
			else if(!entry->libname && !library)
			{
				return entry;
			}

			i += (entry->len * 4);
		}
	}

	return NULL;
}


static void* libsFindExportAddrByNid(SceUID uid, const char *library, u32 nid)
{
	u32 *addr = NULL;
	SceLibraryEntryTable *entry = _libsFindLibrary(uid, library);

	if(entry)
	{
		int count;
		int total;
		unsigned int *vars;

		total = entry->stubcount + entry->vstubcount;
		vars = entry->entrytable;

		if(entry->stubcount > 0)
		{
			for(count = 0; count < entry->stubcount; count++)
			{
				if(vars[count] == nid)
				{
					return (void*)(*((void**)(&vars[count+total])));
				}
			}
		}
	}

	Kprintf("Warning : failed to find function %08X\n", nid);

	return addr;
}

/*****************************************************************************/
/* We have to take care with OskGetStatus, since it will hang if the OSK     */
/* module is not loaded.                                                     */
/*                                                                           */
/* We also map to a simple 0 (not running) and 1 (running).                  */
/*****************************************************************************/
int getOSKState()
{
	int lkbdstate = 0;

	SceModule *pMod = sceKernelFindModuleByName("sceVshOSK_Module");
	if (pMod)
	{
    /*************************************************************************/
		/* Link the OSK func if we don't have it already.                        */
    /*                                                                       */
    /* This is a hack to work around problems statically linking 						 */
		/* sceutility.prx on 3.40OE.          																	 */
	  /*************************************************************************/
		if (oskGetStatus_func == NULL)
		{
			pMod = sceKernelFindModuleByName("sceUtility_Driver");
			if (pMod == NULL)
			{
#if DEBUG_OUTPUT >= 1
				sioPrint("Failed to find sceUtility module\r\n");
#endif
			}
			else
			{
				oskGetStatus_func = libsFindExportAddrByNid(pMod->modid,
																										"sceUtility",
																										0xF3F76017);
			}
		}

		if (oskGetStatus_func == NULL)
		{
#if DEBUG_OUTPUT >= 1
			sioPrint("Failed to find OSKGetStatus func\r\n");
#endif
			lkbdstate = 0;
		}
		else
		{
			u32 k1 = pspSdkSetK1(0);
			lkbdstate = oskGetStatus_func();
			pspSdkSetK1(k1);
		}
	}

	return (lkbdstate != 0);
}

/*****************************************************************************/
/* return true if the browser is running - since it boots the OSK up in a    */
/* different mode.                                                           */
/*****************************************************************************/
int browserRunning()
{
	return (int)(sceKernelFindModuleByName("sceHVNetfront_Module"));
}

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

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

/*****************************************************************************/
/* Move the OSK cursor to the bottom-left position, so that it is in a known */
/* state.                                                                    */
/*****************************************************************************/
void homeOSK()
{
	currx = 0;
	curry = 3;
	stuff_key(PSP_CTRL_LEFT);
	stuff_key(PSP_CTRL_LEFT);
	stuff_key(PSP_CTRL_LEFT);
	stuff_key(PSP_CTRL_DOWN);
	stuff_key(PSP_CTRL_DOWN);
	stuff_key(PSP_CTRL_DOWN);
	stuff_key(PSP_CTRL_DOWN);
	stuff_key(PSP_CTRL_DOWN);
}

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

int oskoutMain(SceSize args, void *argp) {
	int ch, 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 select counts
	int cfg = configOpen("ms0:/seplugins/pikey/pikeyconfig.txt");
	if (cfg >= 0)
	{
		while (configRead(cfg, cfgtoken, cfgvalue))
		{
			if (strcmp(cfgtoken, "XMB OSK") == 0)
			{
				gXmbSelNum = 0;
				if (cfgvalue[0] >= '1' && cfgvalue[0] <= '9')
					gXmbSelNum = cfgvalue[0] - '0';
				if (cfgvalue[1] >= '0' && cfgvalue[1] <= '9')
					gXmbSelNum = gXmbSelNum * 10 + (cfgvalue[1] - '0');
			}
			else if (strcmp(cfgtoken, "BROWSER OSK") == 0)
			{
				gBrwSelNum = 0;
				if (cfgvalue[0] >= '1' && cfgvalue[0] <= '9')
					gBrwSelNum = cfgvalue[0] - '0';
				if (cfgvalue[1] >= '0' && cfgvalue[1] <= '9')
					gBrwSelNum = gBrwSelNum * 10 + (cfgvalue[1] - '0');
			}
		}
		configClose(cfg);
	}

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

	int oldkbdstate = 0;
	int cancelMode  = 0;	 // is the cancel dialog up?
	int cancelChoice = 0;  // track whether the user has highlighted 'yes' in cancel mode
	int keybuf[4];

	for(;;) {

		kbdstate = getOSKState();

		if (kbdstate != oldkbdstate)
		{
			// a change in OSK status.  If it is a newly-shown OSK, init ourselves
			// and request exclusive buffer access
			if (kbdstate != 0)
			{
				requestExclusive();
				setMode(PIKEY_KEYMODE_VT100STREAM);
				flushBuffer();
				homeOSK();

				if (browserRunning())
				{
					int i;
					for (i=0; i<gBrwSelNum; i++)
						stuff_key(PSP_CTRL_SELECT);
				}
				else
				{
					int i;
					for (i=0; i<gXmbSelNum; i++)
						stuff_key(PSP_CTRL_SELECT);
				}
			}
			else
			{
				releaseExclusive();
			}
		}

		oldkbdstate = kbdstate;

		// don't block if nothing available, it interferes with osk detection
		if (numCharsAvailable() == 0)
		{
			sceKernelDelayThread(5000);
			continue;
		}

		// if OSK is not active, just sleep.
		if (kbdstate == 0)
		{
			sceKernelDelayThread(5000);
			continue;
		}

		// we have at least 1 char available.  if we don't have 3, let's sleep for
		// a moment to allow SIO to transfer all 3 if it was in the middle of  a
		// cursor sequence.	A short delay won't be noticeable to the user.
		// 30ms should be enough for 4800 baud to transfer 3 chars.
		sceKernelDelayThread(30000);

		// check for some character combinations which we may need to handle
		// specially
		int handled = 0;
		if(peekNChars(3, keybuf) == PIKEY_SUCCESS)
		{
			if(keybuf[0] == 0x1b)
			{
				if(keybuf[1] == 0x5b)
				{
					switch(keybuf[2])
					{
						case 0x41:			// UP
							throwAwayInput(3);
							handled = 1;
							// always discarded
							break;
						case 0x42:			// DOWN
							throwAwayInput(3);
							handled = 1;
							// always discarded
							break;
						case 0x43:			// RIGHT
							throwAwayInput(3);
							handled = 1;
							if (cancelMode)
							{
								if (cancelChoice)
								{
									cancelChoice = 0;
									stuff_key(PSP_CTRL_RIGHT);
								}
							}
							else
							{
								// move cursor within the text
								stuff_key(PSP_CTRL_RTRIGGER);
							}
							break;
						case 0x44:			// LEFT
							throwAwayInput(3);
							handled = 1;
							if (cancelMode)
							{
								if (!cancelChoice)
								{
									cancelChoice = 1;
									stuff_key(PSP_CTRL_LEFT);
								}
							}
							else
							{
								// move cursor within the text
								stuff_key(PSP_CTRL_LTRIGGER);
							}
							break;

						default:
							break;
					}
				}
			}
		}

		if (handled)
		{
			continue;
		}

		ch = getNextCharW();

    /*************************************************************************/
		/* Stuff a keypress based on the key pressed.                            */
		/*************************************************************************/
		switch (ch)
		{
			// enter
  		case 0x0D:
  			if (cancelMode)
  			{
  				stuff_key(enter);
					cancelMode = 0;
  			}
  			else
  			{
  				stuff_key(PSP_CTRL_START);
  			}
				break;

			// space
  		case 0x20:
  			stuff_key(PSP_CTRL_TRIANGLE);
  			break;

			// escape
  		case 0x1b:
  			stuff_key(cancel);
				cancelMode = ! cancelMode;
				cancelChoice = 0;
  			break;

			// backspace
  		case '\010':		// CHANGE, AS IT IS 0X08 0X31, NEED 2 CHAR INPUT!!!
  		{
  			stuff_key(PSP_CTRL_SQUARE);
  		}
  		break;

		  default:
			{
				if (cancelMode)  // we just throw away standard keys in cancelmode
				{
					break;
				}

				if (gidlecount > IDLETHRESHOLD) // home the OSK if the user has had
				 																// enough time to move it using the standard buttons
				{
					homeOSK();
				}

				// we got a character to try to type in.
				// work out where it is relative to us.
				int x, y, z;
				find_coords(ch, &x, &y, &z);

				// move to the correct cell
				if ((currx != x) || (curry != y))
				{
					while (currx < x)
					{
						currx++;
						stuff_key(PSP_CTRL_RIGHT);
					}
					while (currx > x)
					{
						currx--;
						stuff_key(PSP_CTRL_LEFT);
					}
					while (curry > y)
					{
						curry--;
						stuff_key(PSP_CTRL_UP);
					}
					while (curry < y)
					{
						curry++;
						stuff_key(PSP_CTRL_DOWN);
					}
				}

				int ii;
				for (ii = 0; ii <= z; ii++)
				{
					stuff_key(enter);
				}

				// finish with RTRIGGER to move the cursor on immediately
				stuff_key(PSP_CTRL_RTRIGGER);
			}
			break;
		}

		sceKernelDelayThread(1000);
	}
	sceKernelExitGame();
	return 0;
}

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

	// create main thread
	mainthread = sceKernelCreateThread("oskout", oskoutMain, 16, 0x800, 0, NULL);
	if (mainthread >= 0)
	{
    sceKernelStartThread(mainthread, 0, NULL);
	}

	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++)
	{
		if (gkeymask != -1)
		{
			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 ++;
		}
	}
	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;
}


