#include <stdio.h>
#if defined(WIN32)||defined(macintosh)
#       include <io.h>
#endif
#include <stdlib.h>
#include <string.h>
#if defined(WIN32)
#	define WIN32_LEAN_AND_MEAN
#	include <windows.h>
#	if !defined(_CONSOLE)
#		define FATALERROR(x) MessageBox(NULL, x, "Fatal Error!", MB_OK | MB_ICONEXCLAMATION)
#		define OTHERERROR(x) MessageBox(NULL, x, "Error!", MB_OK | MB_ICONWARNING)
#	endif
#	define EXPORT(typ,fnc,arg) __declspec(dllexport) typ fnc arg
#ifdef	BUILD_FREEZE
extern void PyWinFreeze_ExeInit(void);
extern void PyWinFreeze_ExeTerm(void);
extern int PyInitFrozenExtensions(void);
#endif
#endif
#ifndef EXPORT
#define EXPORT(typ, fnc, arg) typ fnc arg
#endif

#ifndef	FATALERROR
#	define FATALERROR(x) fprintf(stderr,x)
#	define OTHERERROR(x) fprintf(stderr,x)
#endif

#if defined(_DEBUG) || defined(_RL_DEBUG)
#	define VS(arg) fprintf(stderr, arg)
#	define VS1(fmt,arg) fprintf(stderr,fmt,arg)
#	define VS2(fmt,arg1,arg2) fprintf(stderr,fmt,arg1,arg2)
#	define VS3(fmt,arg1,arg2,arg3) fprintf(stderr,fmt,arg1,arg2,arg3)
#else
#	define VS(arg)
#	define VS1(fmt,arg)
#	define VS2(fmt,arg1,arg2)
#	define VS3(fmt,arg1,arg2,arg3)
#endif
#ifdef  _RL_VERBOSE
#       undef _RL_VERBOSE
#       define _RL_VERBOSE 1
#else
#       define _RL_VERBOSE 0
#endif

#ifdef macintosh
/* This is where strdup() is declared (?) */
#include <extras.h>

/* Get some of the PythonCore stuff defined correctly */
#include "mwerks_shared_config.h"
#include "Python.h"
#include "macglue.h"
static void mac_addlibresources(void);
static FSSpec library_fss;
static int loaded_from_shlib = 0;
#else /* !macintosh */
#include "Python.h"
#endif
#ifdef BUILD_FREEZE
/*FROZEN MODULES*/
#endif

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

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

#ifdef macintosh
#pragma export on
rl_embed_rt rl_run_string(char* val);
rl_embed_rt rl_run_file(char* path);
#pragma export off
#endif

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

static int pyStart(void)
{
#if	defined(macintosh)||defined(WIn32)
	char thisfile[_MAX_PATH+1];
#else
	char *thisfile = "lib_rl_embed";
#endif
#ifdef macintosh
	OSErr err;
#endif
#ifdef	BUILD_FREEZE
	PyImport_FrozenModules = _PyImport_FrozenModules;
	Py_FrozenFlag = 1; /* Suppress errors from getpath.c */
#	ifdef WIN32
		PyInitFrozenExtensions();
#	endif
#endif
	rlert.uncaughtError = 0;

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

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

	initialised = -1;
	
#ifdef macintosh
	err = PyMac_GetFullPathname(&library_fss, thisfile, _MAX_PATH);
	if (err != noErr) {
		write_error("can't get library path\n");
		return -1;
	}
#endif
#ifdef WIN32
	if (!GetModuleFileNameA(DLL, thisfile, _MAX_PATH)) {
		FATALERROR("System error - unable to load!");
		return -1;
		}
#endif
	Py_VerboseFlag = _RL_VERBOSE;
	
	Py_SetProgramName(thisfile);
	VS("Initializing Python\n");
#ifdef macintosh
#if 1 /* !defined(_DEBUG) && !defined(_RL_DEBUG) */
	/*
	** This call redirects sys.stdin/out/err to the bitbucket.
	** If you don't do this the test program will end up with two
	** console windows and menu bars in one process: one created by
	** the MSL library in the main program and one by the MSL library
	** in PythonCore. The two don't share any code or globals,
	** but as they do use the same set of constants for menu commands
	** and such things get very messy and it becomes difficult to even
	** quit the application. But still: disabling this code may allow you
	** to see a traceback or other vital information.
	**
	** The alternative is to pass actual output handlers, see macglue.h.
	*/
	PyMac_SetConsoleHandler(PyMac_DummyReadHandler,
		PyMac_DummyWriteHandler, PyMac_DummyWriteHandler);
#endif
	mac_addlibresources();
	PyMac_Initialize();
#else
	Py_Initialize();
#	ifdef	BUILD_FREEZE
#		ifdef WIN32
		PyWinFreeze_ExeInit();
#		endif
#	endif
#endif
	Py_GetPath();
#ifndef macintosh
	{
	char *dummy_argv[2] = {"embedded_python",0};
	PySys_SetArgv(1, dummy_argv);
	}
#endif
#ifdef	BUILD_FREEZE
	if(PyImport_ImportFrozenModule("__main__")<=0){
		VS("Error: __main__ not frozen or bad initialisation during Python DLL initialisation\n");
		write_error("Error: __main__ not frozen or bad initialisation during Python DLL initialisation\n");
		return -1;
		}
	VS("Importing module sys\n");
	PyRun_SimpleString("import sys;sys.path=[];sys.exec_prefix=sys.executable=''");
#endif

	if(PyErr_Occurred()){
		VS("Error occurred during Python DLL initialisation\n");
		write_error("Error occurred during Python DLL initialisation\n");
		return -1;
		}
	initialised=1;
	{
	PyObject *m = PyImport_ImportModule("__main__");
	if(m){
		PyObject *v = PyString_FromString(thisfile);
		if(v){
			PyObject_SetAttrString(m,"_rl_embed_location",v);
			Py_DECREF(v);
			}
		Py_DECREF(m);
		}
	}
	return 0;
}

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_DelAttrString(m,"_rl_embed_error");
		PyObject_DelAttrString(m,"_rl_uncaught");
		PyObject_DelAttrString(m,"_rl_embed_result");
		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 = "Null 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){
		if(!PyString_Check(e)){
			write_error("_rl_embed_result must be a string");
			}
		else {
			int n = PyString_Size(e);
			s = rlert.resultString = (char*)malloc((unsigned long)n+1);
			rlert.resultStringLength = n;
			memcpy(s,PyString_AsString(e),(unsigned long)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;
}

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

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

#ifndef	NO_MEMORY_TTFONTS
EXPORT(rl_embed_rt,rl_register_ttfont,(char *name, char *data, int len))
{
	PyObject *m;
	if(!pyStart() && (m=PyImport_ImportModule("__main__"))){
		PyObject *mname=PyString_FromString(name), *mdata =PyString_FromStringAndSize(data,len);
		int	err = 0;
		if(mname && mdata)
			err = (PyObject_SetAttrString(m,"_rl_ttf_name",mname)==-1
				|| PyObject_SetAttrString(m,"_rl_ttf_data",mdata)==-1);
		else err = 1;
		if(err) rlert.errorString = strdup("Error in setting _rl_ttf_name/data");
		if(mdata) Py_DECREF(mdata);
		if(mname) Py_DECREF(mname);
		if(!err) rl_run_string("from rlextra.utils.proxyfile import MemFile\n\
from reportlab.pdfbase import pdfmetrics, ttfonts\n\
pdfmetrics.registerFont(ttfonts.TTFont(_rl_ttf_name, MemFile(_rl_ttf_data)))");
		PyObject_DelAttrString(m,"_rl_ttf_name");
		PyObject_DelAttrString(m,"_rl_ttf_data");
		PyErr_Clear();
		Py_DECREF(m);
		}
	return rlert;
}
#endif

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

#ifdef macintosh
/*
** Additional Mac specific code. We replace the shared lib initializer
** with our own function (see the PPC Linker panel in the CodeWarrior
** project file settings) that records the file spec for the shared
** library. This info is used later to open the resource fork of the
** shared lib, which is needed so MacPython can find it's resources,
** which are copied to the shared library when building a compact
** distribution.
*/

#include <Resources.h>
#include <CodeFragments.h>

extern OSErr pascal
__initialize(CFragInitBlockPtr data);
OSErr pascal
init_rl_embed_shlib(CFragInitBlockPtr data);

/*
** If this module is dynamically loaded the following routine should
** be the init routine. It takes care of adding the shared library to
** the resource-file chain, so that MacPython can find its resources.
*/
OSErr pascal
init_rl_embed_shlib(CFragInitBlockPtr data)
{
	OSErr err = __initialize(data);
	if ( err != noErr )
		return noErr;
	if ( data == nil )
		return noErr;
	if ( data->fragLocator.where == kDataForkCFragLocator ) {
		library_fss = *data->fragLocator.u.onDisk.fileSpec;
		loaded_from_shlib = 1;
	} else if ( data->fragLocator.where == kResourceCFragLocator ) {
		library_fss = *data->fragLocator.u.inSegs.fileSpec;
		loaded_from_shlib = 1;
	}
	return noErr;
}

/*
** Insert the library resources into the search path. Put them after
** the resources from the application. Again, we ignore errors.
*/
static void
mac_addlibresources(void)
{
	if ( !loaded_from_shlib ) 
		return;
	(void)FSpOpenResFile(&library_fss, fsRdPerm);
}
#endif /* macintosh */
