#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"


/*****************************************************************************/
/* Important : do we need to worry about umode vs kmode?  For now I think    */
/* this has to be called from a kmode thread, which is inconvenient.         */
/* Actually, might be OK so long as it calls through to the syscalls, which  */
/* it should.                                                                */
/*****************************************************************************/

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


/*****************************************************************************/
/* Dummy functions, if pikey is not present                                  */
/*****************************************************************************/
void putNextCharW_dummy(int ch)
{
	// do nothing
}

void putNextChar_dummy(char ch)
{
	// do nothing
}

int numCharsAvailable_dummy(void)
{
	// pretend nothing is present
	return 0;
}

void flushBuffer_dummy(void)
{
	// do nothing
}

int getNextCharW_dummy(void)
{
	// return null char
	return 0;
}

char getNextChar_dummy(void)
{
	// return null char
	return 0;
}

int peekNChars_dummy(int n, int *buffer)
{
	// return INSUFFICIENT_CHARS
	return PIKEY_ERROR_NOT_ENOUGH_CHARS;
}

void sioPrint_dummy(const char * str)
{
	// do nothing
}

void sioPrintHex_dummy(char ch)
{
	// do nothing
}

void sioPrintWord_dummy(int word)
{
	// do nothing
}

void throwAwayInput_dummy(int n)
{
	// do nothing
}

int waitForModule_dummy(const char *name)
{
	// do nothing, return true
	return 1;
}

int requestExclusive_dummy(void)
{
	// do nothing, return true
	return 1;
}

void releaseExclusive_dummy(void)
{
	// do nothing
}

u32 apiHookByNid_dummy(const char *modname, const char *libname, u32 nid, void *func)
{
	// return failure
	return -1;
}

u32 callApiHook_dummy(u32 hookid)
{
	// do nothing
	return 0;
}

void unHookApi_dummy(u32 hookid)
{
	// do nothing
}

int registerDisplayCallback_dummy(SceUID callback)
{
	// return failure
	return 0;
}

void drawKeyboardInfo_dummy(DRAWINFO *drawinfo)
{
	// do nothing
}

void displayStatusText_dummy(const char *text)
{
	// do nothing
}

int isVshMode_dummy(void)
{
	return 0;
}

void  sendKeyPress_dummy(int a,int b,int c,int d,int e)
{
	// do nothing
}

void  flushKeyPresses_dummy(void )
{
	// do nothing
}

int   setMode_dummy(int a)
{
	return 0;
}

int   getMode_dummy(void)
{
	return 0;
}

int   isKeyPressed_dummy(int a)
{
	return 0;
}

void getMetaKeys_dummy(int *alt, int *ctrl, int *shift)
{
	*alt = 0;
	*ctrl = 0;
	*shift = 0;
}

int   numKeysPressed_dummy(void)
{
	return 0;
}

int configOpen_dummy(const char *filename)
{
	// return failure
	return -1;
}

int configRead_dummy(int cfgHandle, char *token, char *value)
{
	// return failure
	return 0;
}

void configClose_dummy(int cfgHandle)
{
	// do nothing
}

/*****************************************************************************/
/* global variables                                                          */
/*****************************************************************************/
void  (*putNextCharW_func)(int)        = putNextCharW_dummy;
void  (*putNextChar_func)(char)        = putNextChar_dummy;
int   (*numCharsAvailable_func)(void)  = numCharsAvailable_dummy;
void  (*flushBuffer_func)(void)        = flushBuffer_dummy;
int   (*getNextCharW_func)(void)       = getNextCharW_dummy;
char  (*getNextChar_func)(void)        = getNextChar_dummy;
int   (*peekNChars_func)(int, int*)    = peekNChars_dummy;
void  (*sioPrint_func)(const char *)   = sioPrint_dummy;
void  (*sioPrintHex_func)(char)        = sioPrintHex_dummy;
void  (*sioPrintWord_func)(int)        = sioPrintWord_dummy;
void  (*throwAwayInput_func)(int)      = throwAwayInput_dummy;
int   (*waitForModule_func)(const char *) = waitForModule_dummy;
int   (*requestExclusive_func)(void)   = requestExclusive_dummy;
void  (*releaseExclusive_func)(void)   = releaseExclusive_dummy;
u32   (*apiHookByNid_func)(const char *, const char *, u32, void *) = apiHookByNid_dummy;
u32   (*callApiHook_func)(u32)				 = callApiHook_dummy;
void  (*unHookApi_func)(u32)				   = unHookApi_dummy;
int   (*registerDisplayCallback_func)(SceUID) = registerDisplayCallback_dummy;
void  (*drawKeyboardInfo_func)(DRAWINFO *)    = drawKeyboardInfo_dummy;
void  (*displayStatusText_func)(const char *) = displayStatusText_dummy;
int   (*isVshMode_func)(void)          = isVshMode_dummy;
void  (*sendKeyPress_func)(int,int,int,int,int) = sendKeyPress_dummy;
void  (*flushKeyPresses_func)(void )   = flushKeyPresses_dummy;
int   (*setMode_func)(int)             = setMode_dummy;
int   (*getMode_func)(void)            = getMode_dummy;
int   (*isKeyPressed_func)(int)        = isKeyPressed_dummy;
void  (*getMetaKeys_func)(int*,int*,int*) = getMetaKeys_dummy;
int   (*numKeysPressed_func)(void)     = numKeysPressed_dummy;
int   (*configOpen_func)(const char *) = configOpen_dummy;
int   (*configRead_func)(int, char *, char *) = configRead_dummy;
void  (*configClose_func)(int)         = configClose_dummy;

int   gIsPikeyPresent = 0;

/*****************************************************************************/
/* Public call-ins - call either dummy funcs or real pikey funcs, according  */
/* to the status of the func pointers.                                       */
/*****************************************************************************/
void putNextCharW(int ch)
{
	putNextCharW_func(ch);
}

void putNextChar(char ch)
{
	putNextChar_func(ch);
}

int numCharsAvailable(void)
{
	return numCharsAvailable_func();
}

void flushBuffer(void)
{
	flushBuffer_func();
}

int getNextCharW(void)
{
	return getNextCharW_func();
}

char getNextChar(void)
{
	return getNextChar_func();
}

int peekNChars(int n, int *buffer)
{
	return peekNChars_func(n, buffer);
}

void throwAwayInput(int numchars)
{
	throwAwayInput_func(numchars);
}

void sioPrint(const char * str)
{
	sioPrint_func(str);
}

void sioPrintHex(char ch)
{
	sioPrintHex_func(ch);
}

void sioPrintWord(int word)
{
	sioPrintWord_func(word);
}

int waitForModule(const char *name)
{
	return waitForModule_func(name);
}

int requestExclusive(void)
{
	return requestExclusive_func();
}

void releaseExclusive(void)
{
	releaseExclusive_func();
}

u32 apiHookByNid(const char *modname, const char *libname, u32 nid, void *func)
{
	return apiHookByNid_func(modname, libname, nid, func);
}

u32 callApiHook(u32 hookid, ...)
{
	return callApiHook_func(hookid);
}

void unHookApi(u32 hookid)
{
	unHookApi_func(hookid);
}

int registerDisplayCallback(SceUID callback)
{
	return registerDisplayCallback_func(callback);
}

void drawKeyboardInfo(DRAWINFO *drawinfo)
{
	drawKeyboardInfo_func(drawinfo);
}

void displayStatusText(const char *text)
{
	displayStatusText_func(text);
}

int isVshMode()
{
	return isVshMode_func();
}

void  sendKeyPress(int a,int b,int c,int d,int e)
{
	sendKeyPress_func(a,b,c,d,e);
}

void  flushKeyPresses(void )
{
	flushKeyPresses_func();
}

int   setMode(int a)
{
	return setMode_func(a);
}

int   getMode(void)
{
	return getMode_func();
}

int   isKeyPressed(int a)
{
	return isKeyPressed_func(a);
}

void getMetaKeys(int *alt, int *ctrl, int *shift)
{
	getMetaKeys_func(alt, ctrl, shift);
}

int   numKeysPressed(void)
{
	return numKeysPressed_func();
}

int configOpen(const char *filename)
{
	return configOpen_func(filename);
}

int configRead(int cfgHandle, char *token, char *value)
{
	return configRead_func(cfgHandle, token, value);
}

void configClose(int cfgHandle)
{
	configClose_func(cfgHandle);
}

int isPikeyLoaded()
{
	return gIsPikeyPresent;
}

SceUID load_module(const char *path, int flags, int type)
{
	SceKernelLMOption option;
	SceUID mpid;

	/* If the type is 0, then load the module in the kernel partition, otherwise load it
	   in the user partition. */
	if (type == 0) {
		mpid = 1;
	} else {
		mpid = 2;
	}

	memset(&option, 0, sizeof(option));
	option.size = sizeof(option);
	option.mpidtext = mpid;
	option.mpiddata = mpid;
	option.position = 0;
	option.access = 1;

	return sceKernelLoadModule(path, flags, type > 0 ? &option : NULL);
}

void resolveFuncs(SceModule *pMod)
{
	putNextCharW_func      = libsFindExportAddrByNid(pMod->modid, "piKey", 0xD0E7CB6B);
	putNextChar_func       = libsFindExportAddrByNid(pMod->modid, "piKey", 0x6B8271AF);
	numCharsAvailable_func = libsFindExportAddrByNid(pMod->modid, "piKey", 0x9A95A90A);
	peekNChars_func        = libsFindExportAddrByNid(pMod->modid, "piKey", 0x468C4E8E);
	flushBuffer_func       = libsFindExportAddrByNid(pMod->modid, "piKey", 0x5F885EF8);
	getNextCharW_func      = libsFindExportAddrByNid(pMod->modid, "piKey", 0x594E2B81);
	getNextChar_func       = libsFindExportAddrByNid(pMod->modid, "piKey", 0xCAA74642);
	sioPrint_func          = libsFindExportAddrByNid(pMod->modid, "piKey", 0xA8FBA87D);
	sioPrintHex_func       = libsFindExportAddrByNid(pMod->modid, "piKey", 0xC2054929);
	sioPrintWord_func      = libsFindExportAddrByNid(pMod->modid, "piKey", 0x6B0C5C80);
	waitForModule_func     = libsFindExportAddrByNid(pMod->modid, "piKey", 0x1BA7AE4B);
	throwAwayInput_func    = libsFindExportAddrByNid(pMod->modid, "piKey", 0x43C336E4);
	requestExclusive_func  = libsFindExportAddrByNid(pMod->modid, "piKey", 0x938AA16D);
	releaseExclusive_func  = libsFindExportAddrByNid(pMod->modid, "piKey", 0x7111D3E1);
	apiHookByNid_func      = libsFindExportAddrByNid(pMod->modid, "piKey", 0x4ED59445);
	callApiHook_func       = libsFindExportAddrByNid(pMod->modid, "piKey", 0x952E87D9);
	unHookApi_func         = libsFindExportAddrByNid(pMod->modid, "piKey", 0xEA5590DA);
	registerDisplayCallback_func = libsFindExportAddrByNid(pMod->modid, "piKey", 0x6FBCD3B0);
	drawKeyboardInfo_func  = libsFindExportAddrByNid(pMod->modid, "piKey", 0xF3BEC70D);
	displayStatusText_func = libsFindExportAddrByNid(pMod->modid, "piKey", 0x4D030AAE);
	isVshMode_func         = libsFindExportAddrByNid(pMod->modid, "piKey", 0x855C25D3);
  sendKeyPress_func      = libsFindExportAddrByNid(pMod->modid, "piKey", 0x6B4DB528);
  flushKeyPresses_func   = libsFindExportAddrByNid(pMod->modid, "piKey", 0x5C8C7762);
  setMode_func           = libsFindExportAddrByNid(pMod->modid, "piKey", 0x2B458D3E);
  getMode_func           = libsFindExportAddrByNid(pMod->modid, "piKey", 0xEF569072);
  isKeyPressed_func      = libsFindExportAddrByNid(pMod->modid, "piKey", 0xA833D884);
  numKeysPressed_func    = libsFindExportAddrByNid(pMod->modid, "piKey", 0x70615273);
  getMetaKeys_func       = libsFindExportAddrByNid(pMod->modid, "piKey", 0xB77C964A);
  configOpen_func        = libsFindExportAddrByNid(pMod->modid, "piKey", 0x8B851CAA);
  configRead_func        = libsFindExportAddrByNid(pMod->modid, "piKey", 0x8E1D5291);
  configClose_func       = libsFindExportAddrByNid(pMod->modid, "piKey", 0x20C74C84);
}

/*****************************************************************************/
/* Check for Pikey presence and initialise appropriately                     */
/*****************************************************************************/
int initPikey()
{
	gIsPikeyPresent = 0;

	SceModule *pMod = sceKernelFindModuleByName("piKey");
	if (pMod)
	{
		gIsPikeyPresent = 1;

		// resolve the import funcs
		resolveFuncs(pMod);
	}
	else
	{
		// attempt to load it.
		pspSdkInstallNoDeviceCheckPatch();
		pspSdkInstallNoPlainModuleCheckPatch();
		pspSdkInstallKernelLoadModulePatch();

		SceUID modid = load_module("ms0:/seplugins/pikey.prx", 0, 0);
		if (modid)
		{
			int status;
			sceKernelStartModule(modid, 0, NULL, &status, NULL);

			SceModule *pMod = sceKernelFindModuleByName("piKey");
			if (pMod)
			{
				gIsPikeyPresent = 1;

				// resolve the import funcs
				resolveFuncs(pMod);
			}
		}
	}

	return gIsPikeyPresent;
}
