/*****************************************************************************/
/* 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 "danzeff_out.h"
#include "../sdk/outputdriver.h"
#include "../sdk/outputframework.h"
#include "keymap.h"

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

#define	DRIVERVER	"1"
#define REQUIREDVER	1

// Ctrl-D to toggle the OSK mode on/off
#define ACTIVATION_CHAR  '\004'

#define IDLE_THRESHOLD  20

// how many cycles to hold a stuff_key down for.
// this is tweaked for the slow Danzeff response time in AFKIM.
// maybe it could be controlled by the user
#define KEY_HOLD_LENGTH 3

SceUID mainthread;

/*****************************************************************************/
/* How long the current keymask has been held.                               */
/*****************************************************************************/
int gholdkeycount = 0;

/*****************************************************************************/
/* 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;

/*****************************************************************************/
/* Analog position override                                                  */
/* -1 = no override.                                                         */
/*****************************************************************************/
int analogx = -1;
int analogy = -1;

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, "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, "DANZEFFOUT");
		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;
	gholdkeycount = 0;
	scePowerTick(0);

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

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

void hold_analog(int x, int y)
{
	analogx = x;
	analogy = y;
	scePowerTick(0);
}

void release_analog()
{
	analogx = -1;
	analogy = -1;
}

int thread_main(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");

	// we don't want to do anything in vsh mode.
	if (isVshMode())
	{
		sioPrint("DANZEFF out quitting - not needed in VSH mode\r\n");
		sceKernelSleepThread();
	}

	//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
	  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;
	}

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

	int keybuf[4];
	int oskstate = 0;
	int oldoskstate = 0;

	int alt, ctrl, shift;

	for(;;) {
		// Wait until we are activated with Ctrl-D.
		getMetaKeys(&alt, &ctrl, &shift);
		if (isKeyPressed(KEY_D) && ctrl)
		{
			oskstate = 1 - oskstate;

			if (oskstate)
			{
				sioPrint("Danzeff mode ON\r\n");
				displayStatusText("Danzeff mode ON");
			}
			else
			{
				sioPrint("Danzeff mode OFF\r\n");
				displayStatusText("Danzeff mode OFF");
			}
		}

    /*************************************************************************/
		/* Process activation state transitions.                                 */
		/*************************************************************************/
		if (oldoskstate != oskstate)
		{
			if (oskstate)
			{
				requestExclusive();
				setMode(PIKEY_KEYMODE_VT100STREAM);
				flushBuffer();
			}
			else
			{
				releaseExclusive();
			}
			oldoskstate = oskstate;
		}

		// if OSK is not active, just sleep.
		if (!oskstate)
		{
			sceKernelDelayThread(5000);
			continue;
		}

		// if the buffer is empty, just sleep
		if (numCharsAvailable() == 0)
		{
			sceKernelDelayThread(5000);
			continue;
		}

    /*************************************************************************/
		/* From here on in, we need to translate keypresses into danzeff input.  */
    /*                                                                       */
    /* A keypress is basically:                                              */
    /*   - hold analog one way                                               */
    /*   - press a key                                                       */
    /*   - release analog                                                    */
		/*************************************************************************/


		// - might need to shift mode using R-trig for CAPS, and L-trig for symbols etc.

		// 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;
							stuff_key(PSP_CTRL_UP);
							break;
						case 0x42:			// DOWN
							throwAwayInput(3);
							handled = 1;
							stuff_key(PSP_CTRL_DOWN);
							break;
						case 0x43:			// RIGHT
							throwAwayInput(3);
							handled = 1;
							stuff_key(PSP_CTRL_RIGHT);
							break;
						case 0x44:			// LEFT
							throwAwayInput(3);
							handled = 1;
							stuff_key(PSP_CTRL_LEFT);
							break;

						default:
							break;
					}
				}
			}
		}

		if (handled)
		{
			continue;
		}

		ch = getNextCharW();

#if 0
		// wiggle the analog stick for a couple of frames if it has been idle
		if (gidlecount > IDLE_THRESHOLD)
		{
			hold_analog(LEFT, CENTER);
			sceKernelDelayThread(1000000 / 30);
			hold_analog(RIGHT, BOTTOM);
			sceKernelDelayThread(1000000 / 30);
			release_analog();
		}
#endif

    /*************************************************************************/
		/* Stuff a keypress based on the key pressed.                            */
		/*************************************************************************/
		switch (ch)
		{
			// enter
  		case 0x0D:
  			stuff_key(PSP_CTRL_DOWN);
				break;

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

			// tab
		  case '\011':
			{
				stuff_key(PSP_CTRL_START);
			}
			break;

			case ACTIVATION_CHAR:   // Our active toggle character
			{
				oskstate = 1 - oskstate;
				if (oskstate)
				{
					displayStatusText("Danzeff mode ON");
				}
				else
				{
					displayStatusText("Danzeff mode OFF");
				}
			}
			break;

		  default:
			{
				// we got a character to try to type in.
				if ((ch >= 32) && (ch <= 127))
				{
					CHARMAP *lmap = &(charmap[ch - 32]);

					// XXX : todo: map the rest of the keymaps.

					int lhold = 0;

					if (lmap->ltrig)
					{
						stuff_key(PSP_CTRL_LTRIGGER);
					}

					if (lmap->rtrig)
					{
						lhold |= PSP_CTRL_RTRIGGER;
					}

					hold_key(lhold);
					hold_analog(lmap->x, lmap->y);

					stuff_key(lmap->buttonpress);

					// slow down to be sure danzeff is keeping up with us.
					sceKernelDelayThread(5000);

					hold_key(0);
					release_analog();

					if (lmap->ltrig)
					{
						stuff_key(PSP_CTRL_LTRIGGER);
					}
				}
			}
			break;
		}

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

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

	// create main thread
	mainthread = sceKernelCreateThread("danzeffout", thread_main, 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++)
	{
		// 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
			{
				if (gholdkeycount++ >= KEY_HOLD_LENGTH)
				{
					gkeymask = 0;
				}
			}

			gidlecount = 0;
		}
		else
		{
			gidlecount ++;
		}

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

/*****************************************************************************/
/* button hooks start here                                                   */
/*****************************************************************************/
int sceCtrlReadBufferPositiveFake(SceCtrlData *pad_data, int count)
{
	u32 k1 = pspSdkSetK1(0);
	int ret = callApiHook(mainHookSave[0].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[1].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[2].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[3].hookid,pad_data, count);
  if(ret <= 0)
  {
    return ret;
  }

  add_values(pad_data, ret, 1);

	pspSdkSetK1(k1);

  return ret;
}


