#include <stdio.h>
#include <io.h>
#include <stdlib.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "uplaunch.h"

/*Python vars & functions*/
DECLVAR (int, Py_NoSiteFlag);
DECLVAR (int, Py_VerboseFlag);
#define Py_file_input 257
DECLPROC(int, Py_Initialize, (void));
DECLPROC(int, Py_Finalize, (void));
DECLPROC(int, PyRun_SimpleString, (char *));
DECLPROC(void, Py_SetProgramName, (char *));
DECLPROC(PyObject *, PyImport_ImportModule, (char *));
DECLPROC(int, PyObject_SetAttrString, (PyObject *, char *, PyObject *));
DECLPROC(PyObject*, PyObject_GetAttrString, (PyObject *, char *));
DECLPROC(int, PyObject_DelAttrString, (PyObject *, char *));
DECLPROC(PyObject *, PyList_New, (int));
DECLPROC(char*, PyString_AsString, (PyObject *));
DECLPROC(PyObject*, PyString_FromString, (char*));
DECLPROC(int, PyString_Size, (PyObject *));
DECLPROC(int, PyList_Append, (PyObject *, PyObject *));
DECLPROC(PyObject *, Py_BuildValue, (char *, ...));
DECLPROC(PyObject *, PyFile_FromFile, (FILE *, char *, char *, int));
DECLPROC(PyObject *, PyObject_CallFunction, (PyObject *, char *, ...));
DECLPROC(PyObject *, PyModule_GetDict, (PyObject *));
DECLPROC(PyObject *, PyDict_GetItemString, (PyObject *, char *));
DECLPROC(PyObject *, PyErr_Occurred, () );
DECLPROC(PyObject *, PyErr_Clear, () );
DECLPROC(void, PyErr_Print, () );
DECLPROC(void, Py_SetPythonHome, (char *));
DECLPROC(char *, Py_GetPath, (void));
DECLPROC(PyObject *, PyRun_File, (FILE*,char*,int,PyObject *,PyObject *));
#include "_rl_startup.h"
static	int	pyvers;
static	char homepath[_MAX_PATH+1];

static char* _rl_run_file = "def _rl_run_file():\n\
	import __main__,StringIO,sys\n\
	sys.stderr = StringIO.StringIO()\n\
	try:\n\
		execfile(__main__._rl_embed_arg,__main__.__dict__,__main__.__dict__)\n\
	except:\n\
		import traceback\n\
		traceback.print_exc()\n\
		__main__._rl_uncaught = 1\n\
	__main__._rl_embed_error = sys.stderr.getvalue()\n\
	sys.stderr = sys.__stderr__\n\
_rl_run_file()\n\
del _rl_run_file";

static char* _rl_run_string = "def _rl_run_string():\n\
	import __main__,StringIO,sys\n\
	sys.stderr = StringIO.StringIO()\n\
	try:\n\
		exec __main__._rl_embed_arg+'\\n' in __main__.__dict__,__main__.__dict__\n\
	except:\n\
		import traceback\n\
		traceback.print_exc()\n\
		__main__._rl_uncaught = 1\n\
	__main__._rl_embed_error = sys.stderr.getvalue()\n\
	sys.stderr = sys.__stderr__\n\
_rl_run_string()\n\
del _rl_run_string";

static	HINSTANCE DLL=0;
static	int	initialised=0;
static	int	verbose=0;
typedef struct {
		char*	errorString;
		int		resultStringLength;
		char*	resultString;
		int		uncaughtError;
		} rl_embed_rt;
		
static rl_embed_rt  rlert={NULL,0,NULL,0}; 

static	void write_error(char *msg)
{
	if(!rlert.errorString){
		rlert.errorString = strdup(msg);
		}
	else{
		rlert.errorString = (char*)realloc(rlert.errorString,strlen(rlert.errorString)+strlen(msg)+1);
		strcat(rlert.errorString,msg);
		}
}

HINSTANCE loadPython(int pyvers, char* homepath)
{
	HINSTANCE dll;
	char dllpath[_MAX_PATH+25];
	VS("Loading python\n");
	sprintf(dllpath, "%sDLLs\\python%02d.dll", homepath, pyvers);
	dll = LoadLibraryEx(dllpath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
	if(!dll){
		char* msg="Failed to load python dll\n";
		VS(msg);
		write_error(msg);
		}
	else{
		VS(dllpath);VS("\n");
		/* Thomas Heller's code*/
		VS("Getting entry points\n");
		GETVAR(dll, int, Py_NoSiteFlag);
		GETVAR(dll, int, Py_VerboseFlag);
		GETPROC(dll, int, Py_Initialize, (void));
		GETPROC(dll, int, Py_Finalize, (void));	
		GETPROC(dll, int, PyRun_SimpleString, (char *));
		GETPROC(dll, void, Py_SetProgramName, (char *));
		GETPROC(dll, PyObject *, PyImport_ImportModule, (char *));
		GETPROC(dll, int, PyObject_SetAttrString, (PyObject *, char *, PyObject *));
		GETPROC(dll, PyObject*, PyObject_GetAttrString, (PyObject *, char *));
		GETPROC(dll, PyObject *, PyList_New, (int));
		GETPROC(dll, char*, PyString_AsString, (PyObject *));
		GETPROC(dll, PyObject*, PyString_FromString, (char*));
		GETPROC(dll, int, PyString_Size, (PyObject *));
		GETPROC(dll, int, PyList_Append, (PyObject *, PyObject *));
		GETPROC(dll, PyObject *, Py_BuildValue, (char *, ...));
		GETPROC(dll, PyObject *, PyFile_FromFile, (FILE *, char *, char *, int));
		GETPROC(dll, PyObject *, PyObject_CallFunction, (PyObject *, char *, ...));
		GETPROC(dll, PyObject *, PyModule_GetDict, (PyObject *));
		GETPROC(dll, PyObject *, PyDict_GetItemString, (PyObject *, char *));
		GETPROC(dll, PyObject *, PyErr_Occurred, () );
		GETPROC(dll, PyObject *, PyErr_Clear, () );
		GETPROC(dll, void, PyErr_Print, () );
		GETPROC(dll, void, Py_SetPythonHome, (char *));
		GETPROC(dll, char *, Py_GetPath, (void));
		GETPROC(dll, PyObject *, PyRun_File, (FILE*,char*,int,PyObject*,PyObject*));
		}

	return dll;
}

static int getRegistryInstallPath(HKEY rootKey, char* id, char* homepath)
{
	HKEY	key=0;
	long	bufSize;
	int		n;
	char	*fmt="Software\\ReportLab\\%s";
	char	*regpath = malloc(strlen(id)+strlen(fmt)+1);

	sprintf(regpath, fmt, id);
	bufSize=_MAX_PATH;
	VS1("REGISTRY PATH: %s\n", regpath);
	if((n=RegOpenKey(rootKey,regpath,&key))==ERROR_SUCCESS &&
		(n=RegQueryValueEx(key, "InstallPath",NULL,NULL, homepath, &bufSize))==ERROR_SUCCESS) n = 1;
	else{
		VS2("Can't Find Registry PYTHONHOME key=%8.8x err=%d\n",key,n);
		n = 0; /*error*/
		}
	if(key) RegCloseKey(key);
	if(regpath) free(regpath);
	return n;
}

int get_homepath(char* thisfile, char* homepath, char *project)
{
	char	*p, *q, *s;
	int		n;
	VS("Finding InstallPath\n");
	if(!getRegistryInstallPath(HKEY_LOCAL_MACHINE,project,homepath)
			&& !getRegistryInstallPath(HKEY_CURRENT_USER,project,homepath)){
		VS("Trying EXE for homepath\n");
		strcpy(homepath, thisfile);
		p = homepath+strlen(homepath);
		while(p>=homepath && *p!='/' && *p != '\\') p--;
		if(p>homepath) *++p = 0;
		else {
			FATALERROR("Can't Find PYTHONHOME\n");
			return -1;
			}
		}
	if(homepath[strlen(homepath)-1]!='\\') strcat(homepath,"\\");
	VS1("Found HOMEPATH=%s\n", homepath);
	/*adjust the path to include homepath/DLLs*/
	p = getenv("PATH");
	n = strlen(thisfile)+strlen(homepath)+11;	/*we add DLLs and PATH=*/
	if(p) n += strlen(p)+1;
	s = (char*)malloc(n);
	strcpy(s,"PATH=");
	strcpy(s+5,thisfile);
	q = s+strlen(s);
	while(q>=s && *q!='/' && *q != '\\') q--;
	*q = 0;
	strcat(s,";");
	strcat(s,homepath);
	strcat(s,"DLLs");
	if(p){
		strcat(s,";");
		strcat(s,p);
		}
	VS1("Adjusted PATH=%s\n",s);
	_putenv(s);
	return 0;
}

int openSelf(char* thisfile, char *homepath, char **project, int *pyvers)
{
	FILE*	fp;
	char	*p;
#	define RLE_MAGIC "RLE\07\04\01\013\012"
	typedef struct {
		unsigned short projectlen;
		unsigned short pyvers;
		char magic[8];
		} cookie;
	cookie	c;
	int	cs = sizeof(cookie);
	p = thisfile+strlen(thisfile)-4;
	if (strnicmp(p, ".dll", 4) != 0) strcat(thisfile, ".dll");
	VS1("Opening self=%s\n", thisfile);
	fp = fopen(thisfile, "rb");
	if(fp == NULL){
		FATALERROR("Cannot open self: ");
		FATALERROR(thisfile);
		return -1;
		}
	fseek(fp, 0, SEEK_END);
	if (fseek(fp, -(int)cs, SEEK_END)){
L0:		FATALERROR("executable appears to be invalid");
		return -1;
		}
	fread(&c, sizeof(cookie), 1, fp);
	if (strncmp((char*)&c.magic, RLE_MAGIC, 8)){
#if	defined(_DEBUG) || defined(_RL_DEBUG)
		int i;
		VS("RLE_MAGIC:");
		for(i=0;i<8;i++){
			VS1("%2.2X",RLE_MAGIC[i]);
			}
		VS("\nc.magic:");
		for(i=0;i<8;i++){
			VS1(" %2.2X",c.magic[i],);
			}
		VS("\n");
#endif
		FATALERROR("DLL has bad magic - quitting!");
		return -1;
		}
	if (fseek(fp, -cs-(int)c.projectlen, SEEK_END)) goto L0;
	*project = malloc((size_t)c.projectlen + 1);
	fread(*project, (int)c.projectlen, 1, fp);
	(*project)[c.projectlen] = 0;
	VS2("projectlen=%d project='%s'\n",(int)c.projectlen,*project);
	if (get_homepath(thisfile,homepath,*project)) return -1;
	VS("closing self\n");
	fclose(fp);
	*pyvers = (int)c.pyvers;
	return 0;
}

static int pyStart(void)
{
	char *pypath, *project=NULL;
	char thisfile[_MAX_PATH+1];
	int	i;

	rlert.uncaughtError = 0;
	if(rlert.errorString){
		free(rlert.errorString);
		rlert.errorString = NULL;
		}
	if(rlert.resultString){
		free(rlert.resultString);
		rlert.resultString = NULL;
		}
	rlert.resultStringLength = 0;

	if(initialised){
		if(initialised<0){
			write_error("Python initialisation already failed\n");
			return -1;
			}
		else return 0;
		}

	initialised = -1;
	
	if (!GetModuleFileNameA(DLL, thisfile, _MAX_PATH)) {
		FATALERROR("System error - unable to load!");
		return -1;
		}
	if (openSelf(thisfile,homepath,&project,&pyvers)) return -1;
	if(!loadPython(pyvers,homepath)) return -1;

	/*now launch python ...*/
	pypath = (char*)malloc(strlen(homepath)+13);
	strcpy(pypath,"PYTHONHOME=");
	strcpy(pypath+11,homepath);
	i = strlen(pypath)-1;
	if(pypath[i]=='\\' || pypath[i]=='/') pypath[i]=0;
	VS1("Set %s\n",pypath);
	_putenv( pypath );
	free(pypath);
	VS("Setting Python vars\n");
	*Py_NoSiteFlag = 1;
	VS1("Py_NoSiteFlag=%d\n", *Py_NoSiteFlag);
	if(verbose) *Py_VerboseFlag = 1;
	
	Py_SetProgramName(thisfile);
	VS("Initializing Python\n");	
	Py_Initialize();
	Py_GetPath();
	_putenv( "PYTHONPATH=" );	
	VS("Importing module sys\n");
	PyRun_SimpleString("import sys;sys.path=filter(lambda x: not x or x.find(sys.exec_prefix)==0, sys.path);sys.argv=['rl_embed.dll'];sys.__embedded__=1");

	VS("Installing import hooks\n");
	pypath = (char*)malloc(strlen(_rl_startup)+strlen(homepath)+100);
	sprintf(pypath,_rl_startup,homepath);
	if(PyRun_SimpleString(pypath)){
		VS("Cannot start up importing.\n");
		write_error("Cannot start up importing.\n");
		free(pypath);
		goto L0;
		}
	free(pypath);
	VS("Importing module site\n");
	PyRun_SimpleString("import site;" _RL_RESTORE_PYZ);
	if(PyErr_Occurred()){
		VS("Error occurred during Python DLL initialisation\n");
		write_error("Error occurred during Python DLL initialisation\n");
L0:		if(project) free(project);
		return -1;
		}
	initialised=1;

	if(project) free(project);
	return 0;
}

static int pyFinish(int verbose)
{
	if(initialised>0){
		VS("Finalizing Python\n");
		Py_Finalize();
		}
}

static PyObject* _get__main__(char* val)
{
	PyObject *m, *v;
	m = PyImport_ImportModule("__main__");
	if(!m){
		VS("Can't find __main__\n");
		write_error("Can't find __main__\n");
		}
	else{
		int	err=0;
		PyObject_SetAttrString(m,"_rl_embed_error",NULL);
		PyObject_DelAttrString(m,"_rl_uncaught");
		PyObject_SetAttrString(m,"_rl_embed_result",NULL);
		if(PyErr_Occurred()) PyErr_Clear();
		v = PyString_FromString(val);
		if(!v) err = 1;
		else if (PyObject_SetAttrString(m,"_rl_embed_arg",v)==-1) err = 1;
		if(err){
			write_error("Couldn't set __main__._rl_embed_arg'\n");
			Py_DECREF(m);
			m = NULL;
			}
		if(v) Py_DECREF(v);
		}
	return m;
}

static void _finish_run(PyObject* m)
{
	char	*s;
	PyObject* e;
	int		ucerr = rlert.errorString && rlert.errorString[0];
	e = PyObject_GetAttrString(m,"_rl_embed_error");
	if(e){
		s = PyString_AsString(e);
		if(!s) s = "Error in _rl_embed_error\n";
		if(*s){
			VS(s);
			write_error(s);
			}
		Py_DECREF(e);
		}
	e = PyObject_GetAttrString(m,"_rl_uncaught");
	if(e){
		Py_DECREF(e);
		ucerr |= 2;
		}
	e = PyObject_GetAttrString(m,"_rl_embed_result");
	if(e){
		int n = PyString_Size(e);
		s = rlert.resultString = (char*)malloc(n+1);
		rlert.resultStringLength = n;
		memcpy(s,PyString_AsString(e),n);
		s[n] = 0;
		Py_DECREF(e);
		}
}

static rl_embed_rt _runWrapped(char* val, char* wrapper)
{
	PyObject *m;
	if(!pyStart() && (m=_get__main__(val))){
		int r = PyRun_SimpleString(wrapper);
		VS1("Run completed r=%d\n",r);
		_finish_run(m);
		Py_DECREF(m);
		}
	return rlert;
}

__declspec(dllexport) rl_embed_rt rl_run_string(char* val)
{
	return _runWrapped(val,_rl_run_string);
}

__declspec(dllexport) rl_embed_rt rl_run_file(char* path)
{
	VS1("Script file %s\n",path);
	return _runWrapped(path,_rl_run_file);
}

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	if(dwReason==DLL_PROCESS_ATTACH){
		DLL = hInstance;
		VS2("Attach from thread %8.8x DLL=%8.8x\n", GetCurrentThreadId(),DLL);
		}
	else if(dwReason==DLL_PROCESS_DETACH){
		VS("Process Detach\n");
		}
	return TRUE; 
}
