/***********************************************************
Copyright 1991-1997 by Stichting Mathematisch Centrum, Amsterdam,
The Netherlands.

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, 
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in 
supporting documentation, and that the names of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior permission.

STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

******************************************************************/


#include "Python.h"

#include "macglue.h"
#include "marshal.h"
#include "import.h"
#include "importdl.h"

#include "pythonresources.h"

#include <Types.h>
#include <Files.h>
#include <Resources.h>
#include <CodeFragments.h>
#include <StringCompare.h>

typedef void (*dl_funcptr)();
#define FUNCNAME_PATTERN "init%.200s"

static int
fssequal(FSSpec *fs1, FSSpec *fs2)
{
	if ( fs1->vRefNum != fs2->vRefNum || fs1->parID != fs2->parID )
		return 0;
	return EqualString(fs1->name, fs2->name, false, true);
}
/*
** findnamedresource - Common code for the various *ResourceModule functions.
** Check whether a file contains a resource of the correct name and type, and
** optionally return the value in it.
*/
static int
findnamedresource(
	PyStringObject *obj, 
	char *module, 
	char *filename, 
	OSType restype, 
	StringPtr dataptr)
{
	FSSpec fss;
	FInfo finfo;
	short oldrh, filerh;
	int ok;
	Handle h;

	/*
	** Find_module takes care of interning all
	** sys.path components. We then keep a record of all sys.path
	** components for which GetFInfo has failed (usually because the
	** component in question is a folder), and we don't try opening these
	** as resource files again.
	*/
#define MAXPATHCOMPONENTS 32
	static PyStringObject *not_a_file[MAXPATHCOMPONENTS];
	static int max_not_a_file = 0;
	int i;
		
	if (obj && PyString_Check(obj) && PyString_CHECK_INTERNED(obj) ) {
		for( i=0; i< max_not_a_file; i++ )
			if ( obj == not_a_file[i] )
				return 0;
	}
	if ( FSMakeFSSpec(0, 0, Pstring(filename), &fss) != noErr ) {
	     /* doesn't exist or is folder */
		if ( obj && max_not_a_file < MAXPATHCOMPONENTS && PyString_Check(obj) && PyString_CHECK_INTERNED(obj) ) {
			Py_INCREF(obj);
			not_a_file[max_not_a_file++] = obj;
			if (Py_VerboseFlag > 1)
				PySys_WriteStderr("# %s is not a file\n", filename);
		}
		return 0;
	}			
	if ( fssequal(&fss, &PyMac_ApplicationFSSpec) ) {
		/*
		** Special case: the application itself. Use a shortcut to
		** forestall opening and closing the application numerous times
		** (which is dead slow when running from CDROM)
		*/
		oldrh = CurResFile();
		UseResFile(PyMac_AppRefNum);
		filerh = -1;
	} else {
     	if ( FSpGetFInfo(&fss, &finfo) != noErr ) {
     		/* doesn't exist or is folder */
			if ( obj && max_not_a_file < MAXPATHCOMPONENTS && PyString_Check(obj) && PyString_CHECK_INTERNED(obj) ) {
				Py_INCREF(obj);
				not_a_file[max_not_a_file++] = obj;
				if (Py_VerboseFlag > 1)
					PySys_WriteStderr("# %s is not a file\n", filename);
			}
			return 0;
		}			
		oldrh = CurResFile();
		filerh = FSpOpenResFile(&fss, fsRdPerm);
		if ( filerh == -1 )
			return 0;
		UseResFile(filerh);
	}
	if ( dataptr == NULL )
		SetResLoad(0);
	if (Py_VerboseFlag > 1)
		PySys_WriteStderr("# Look for ('PYC ', %s) in %s\n", module, filename);
	h = Get1NamedResource(restype, Pstring(module));
	SetResLoad(1);
	ok = (h != NULL);
	if ( ok && dataptr != NULL ) {
		HLock(h);
		/* XXXX Unsafe if resource not correctly formatted! */
		/* for ppc we take the first pstring */
		*dataptr = **h;
		memcpy(dataptr+1, (*h)+1, (int)*dataptr);
		HUnlock(h);
	}
	if ( filerh != -1 )
		CloseResFile(filerh);
	UseResFile(oldrh);
	return ok;
}

/*
** Returns true if the argument has a resource fork, and it contains
** a 'PYC ' resource of the correct name
*/
int
PyMac_FindResourceModule(obj, module, filename)
PyStringObject *obj;
char *module;
char *filename;
{
	int ok;
	
	ok = findnamedresource(obj, module, filename, 'PYC ', (StringPtr)0);
	return ok;
}

/*
** Returns true if the argument has a resource fork, and it contains
** a 'PYD ' resource of the correct name
*/
int
PyMac_FindCodeResourceModule(obj, module, filename)
PyStringObject *obj;
char *module;
char *filename;
{
	int ok;
	
	ok = findnamedresource(obj, module, filename, 'PYD ', (StringPtr)0);
	return ok;
}


/*
** Load the specified module from a code resource
*/
PyObject *
PyMac_LoadCodeResourceModule(name, pathname)
	char *name;
	char *pathname;
{
	PyObject *m, *d, *s;
	char funcname[258];
	char *lastdot, *shortname, *packagecontext;
	dl_funcptr p = NULL;
	Str255 fragmentname;
	CFragConnectionID connID;
	Ptr mainAddr;
	Str255 errMessage;
	OSErr err;
	char buf[512];
	Ptr symAddr;
	CFragSymbolClass class;

	if ((m = _PyImport_FindExtension(name, name)) != NULL) {
		Py_INCREF(m);
		return m;
	}
	lastdot = strrchr(name, '.');
	if (lastdot == NULL) {
		packagecontext = NULL;
		shortname = name;
	}
	else {
		packagecontext = name;
		shortname = lastdot+1;
	}
	PyOS_snprintf(funcname, sizeof(funcname), FUNCNAME_PATTERN, shortname);
	if( !findnamedresource((PyStringObject *)0, name, pathname, 'PYD ', fragmentname)) {
		PyErr_SetString(PyExc_ImportError, "PYD resource not found");
		return NULL;
	}
	
	/* Load the fragment
	   (or return the connID if it is already loaded */
	err = GetSharedLibrary(fragmentname, kCompiledCFragArch,
			      kLoadCFrag, &connID, &mainAddr,
			      errMessage);
	if ( err ) {
		PyOS_snprintf(buf, sizeof(buf), "%.*s: %.200s",
			errMessage[0], errMessage+1,
			PyMac_StrError(err));
		PyErr_SetString(PyExc_ImportError, buf);
		return NULL;
	}
	/* Locate the address of the correct init function */
	err = FindSymbol(connID, Pstring(funcname), &symAddr, &class);
	if ( err ) {
		PyOS_snprintf(buf, sizeof(buf), "%s: %.200s",
			funcname, PyMac_StrError(err));
		PyErr_SetString(PyExc_ImportError, buf);
		return NULL;
	}
	p = (dl_funcptr)symAddr;
	if (p == NULL) {
		PyErr_Format(PyExc_ImportError,
		   "dynamic module does not define init function (%.200s)",
			     funcname);
		return NULL;
	}
	_Py_PackageContext = packagecontext;
	(*p)();
	_Py_PackageContext = NULL;
	if (PyErr_Occurred())
		return NULL;
	if (_PyImport_FixupExtension(name, name) == NULL)
		return NULL;

	m = PyDict_GetItemString(PyImport_GetModuleDict(), name);
	if (m == NULL) {
		PyErr_SetString(PyExc_SystemError,
				"dynamic module not initialized properly");
		return NULL;
	}
	/* Remember the filename as the __file__ attribute */
	d = PyModule_GetDict(m);
	s = PyString_FromString(pathname);
	if (s == NULL || PyDict_SetItemString(d, "__file__", s) != 0)
		PyErr_Clear(); /* Not important enough to report */
	Py_XDECREF(s);
	if (Py_VerboseFlag)
		PySys_WriteStderr("import %s # pyd fragment %#s loaded from %s\n",
			name, fragmentname, pathname);
	Py_INCREF(m);
	return m;
}

/*
** Load the specified module from a resource
*/
PyObject *
PyMac_LoadResourceModule(module, filename)
char *module;
char *filename;
{
	FSSpec fss;
	FInfo finfo;
	short oldrh, filerh;
	Handle h;
	OSErr err;
	PyObject *m, *co;
	long num, size;
	
	if ( (err=FSMakeFSSpec(0, 0, Pstring(filename), &fss)) != noErr )
		goto error;
	if ( fssequal(&fss, &PyMac_ApplicationFSSpec) ) {
		/*
		** Special case: the application itself. Use a shortcut to
		** forestall opening and closing the application numerous times
		** (which is dead slow when running from CDROM)
		*/
		oldrh = CurResFile();
		UseResFile(PyMac_AppRefNum);
		filerh = -1;
	} else {
		if ( (err=FSpGetFInfo(&fss, &finfo)) != noErr )
			goto error;
		oldrh = CurResFile();
		filerh = FSpOpenResFile(&fss, fsRdPerm);
		if ( filerh == -1 ) {
			err = ResError();
			goto error;
		}
		UseResFile(filerh);
	}
	h = Get1NamedResource('PYC ', Pstring(module));
	if ( h == NULL ) {
		err = ResError();
		goto error;
	}
	HLock(h);
	/*
	** XXXX The next few lines are intimately tied to the format of pyc
	** files. I'm not sure whether this code should be here or in import.c -- Jack
	*/
	size = GetHandleSize(h);
	if ( size < 8 ) {
		PyErr_SetString(PyExc_ImportError, "Resource too small");
		co = NULL;
	} else {
		num = (*h)[0] & 0xff;
		num = num | (((*h)[1] & 0xff) << 8);
		num = num | (((*h)[2] & 0xff) << 16);
		num = num | (((*h)[3] & 0xff) << 24);
		if ( num != PyImport_GetMagicNumber() ) {
			PyErr_SetString(PyExc_ImportError, "Bad MAGIC in resource");
			co = NULL;
		} else {
			co = PyMarshal_ReadObjectFromString((*h)+8, size-8);
			/*
			** Normally, byte 4-7 are the time stamp, but that is not used
			** for 'PYC ' resources. We abuse byte 4 as a flag to indicate
			** that it is a package rather than an ordinary module. 
			** See also py_resource.py. (jvr)
			*/
			if ((*h)[4] & 0xff) {
				/* it's a package */
				/* Set __path__ to the package name */
				PyObject *d, *s;
				int err;
				
				m = PyImport_AddModule(module);
				if (m == NULL) {
					co = NULL;
					goto packageerror;
				}
				d = PyModule_GetDict(m);
				s = PyString_InternFromString(module);
				if (s == NULL) {
					co = NULL;
					goto packageerror;
				}
				err = PyDict_SetItemString(d, "__path__", s);
				Py_DECREF(s);
				if (err != 0) {
					co = NULL;
					goto packageerror;
				}
			}
		}
	}
packageerror:
	HUnlock(h);
	if ( filerh != -1 )
		CloseResFile(filerh);
	else
		ReleaseResource(h);
	UseResFile(oldrh);
	if ( co ) {
		m = PyImport_ExecCodeModuleEx(module, co, "<pyc resource>");
		Py_DECREF(co);
	} else {
		m = NULL;
	}
	if (Py_VerboseFlag)
		PySys_WriteStderr("import %s # pyc resource from %s\n",
			module, filename);
	return m;
error:
	{
		char buf[512];
		
		PyOS_snprintf(buf, sizeof(buf), "%s: %s", filename, PyMac_StrError(err));
		PyErr_SetString(PyExc_ImportError, buf);
		return NULL;
	}
}

/*
** Look for a module in a single folder. Upon entry buf and len
** point to the folder to search, upon exit they refer to the full
** pathname of the module found (if any).
*/
struct filedescr *
PyMac_FindModuleExtension(char *buf, size_t *lenp, char *module)
{
	struct filedescr *fdp;
	unsigned char fnbuf[64];
	int modnamelen = strlen(module);
	FSSpec fss;
	short refnum;
	long dirid;
	
	/*
	** Copy the module name to the buffer (already :-terminated)
	** We also copy the first suffix, if this matches immedeately we're
	** lucky and return immedeately.
	*/
	if ( !_PyImport_Filetab[0].suffix )
		return 0;
		
	strcpy(buf+*lenp, _PyImport_Filetab[0].suffix);
	if ( FSMakeFSSpec(0, 0, Pstring(buf), &fss) == noErr )
		return _PyImport_Filetab;
	/*
	** We cannot check for fnfErr (unfortunately), it can mean either that
	** the file doesn't exist (fine, we try others) or the path leading to it.
	*/
	refnum = fss.vRefNum;
	dirid = fss.parID;
	if ( refnum == 0 || dirid == 0 )	/* Fail on nonexistent dir */
		return 0;
	/*
	** We now have the folder parameters. Setup the field for the filename
	*/
	if ( modnamelen > 54 ) return 0;	/* Leave room for extension */
	strcpy((char *)fnbuf+1, module);
	buf[*lenp] = '\0';
	
	for( fdp = _PyImport_Filetab+1; fdp->suffix; fdp++ ) {
		strcpy((char *)fnbuf+1+modnamelen, fdp->suffix);
		fnbuf[0] = strlen((char *)fnbuf+1);
		if (Py_VerboseFlag > 1)
			PySys_WriteStderr("# trying %s%s\n", buf, fdp->suffix);
		if ( FSMakeFSSpec(refnum, dirid, fnbuf, &fss) == noErr ) {
			/* Found it. */
			strcpy(buf+*lenp, fdp->suffix);
			return fdp;
		}
	}
	return 0;
}
