/*#io
DynLib ioDoc(
		   docCopyright("Steve Dekorte", 2002)
		   docLicense("BSD revised")
		   docDescription("A DLL Loader by Kentaro A. Kurahone <kurahone@sigusr1.org>.")
		   docCategory("Core")
		   */

#include "IoObject.h"
#include "IoState.h"
#include "IoSeq.h"
#include "IoBlock.h"
#include "IoNumber.h"
#include "IoList.h"
#include "IoMessage.h"
#include "IoDynLib.h"
#include "DynLib.h"

#define DATA(self) ((DynLib *)IoObject_dataPointer(self))

static IoTag *IoDynLib_tag(void *state)
{
	IoTag *tag = IoTag_newWithName_("DynLib");
	tag->state = state;
	tag->cloneFunc = (TagCloneFunc *) IoDynLib_rawClone;
	tag->freeFunc  = (TagFreeFunc *)  IoDynLib_free;
	return tag;
}

IoObject *IoDynLib_proto(void *state)
{
	IoMethodTable methodTable[] = {
	{"setPath", IoDynLib_setPath},
	{"path", IoDynLib_path},
	{"setInitFuncName", IoDynLib_setInitFuncName},
	{"initFuncName", IoDynLib_initFuncName},
	{"setFreeFuncName", IoDynLib_setFreeFuncName},
	{"freeFuncName", IoDynLib_freeFuncName},
	{"open", IoDynLib_open},
	{"close", IoDynLib_close},
	{"isOpen", IoDynLib_isOpen},
	{"call", IoDynLib_call},
	{"voidCall", IoDynLib_voidCall},
	{"callPluginInit", IoDynLib_callPluginInitFunc},
	{"returnsString", IoDynLib_returnsString},
	{NULL, NULL},
	};
	
	IoObject *self = IoObject_new(state);
	self->tag = IoDynLib_tag(state);
	IoObject_setDataPointer_(self, DynLib_new());
	DynLib_setInitArg_(DATA(self), state);
	DynLib_setFreeArg_(DATA(self), state);
	IoState_registerProtoWithFunc_((IoState *)state, self, IoDynLib_proto);
	
	IoObject_addMethodTable_(self, methodTable);
	return self;
}

IoDynLib *IoDynLib_new(void *state)
{ 
	IoDynLib *proto = IoState_protoWithInitFunction_((IoState *)state, IoDynLib_proto);
	return IOCLONE(proto); 
}

IoDynLib *IoDynLib_rawClone(IoDynLib *proto)
{
	/* 
	Note that due to the nature of this object, a clone will *NOT* inherit
	 it's parent's dynamically loaded object.
	 */
	
	IoObject *self = IoObject_rawClonePrimitive(proto);
	IoObject_setDataPointer_(self, DynLib_new());
	DynLib_setInitArg_(DATA(self), IOSTATE);
	DynLib_setFreeArg_(DATA(self), IOSTATE);
	return self;
}

void IoDynLib_free(IoDynLib *self)
{
	DynLib_free(DATA(self));
}

IoDynLib *IoDynLib_setPath(IoDynLib *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("setPath(aString)", 
		   "Sets the path to the dynamic library. Returns self.")
	*/
	
	DynLib_setPath_(DATA(self), 
				 CSTRING(IoMessage_locals_symbolArgAt_(m, locals, 0)));
	return self;
}

IoDynLib *IoDynLib_path(IoDynLib *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("path", "Returns the path to the dynamic library.")
	*/
	
	return IOSYMBOL(DynLib_path(DATA(self))); 
}

IoDynLib *IoDynLib_setInitFuncName(IoDynLib *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("setInitFuncName(aString)", 
		   "Sets the initialization function name for the dynamic library. Returns self.")
	*/
	
	DynLib_setInitFuncName_(DATA(self), 
					    CSTRING(IoMessage_locals_symbolArgAt_(m, locals, 0))); 
	return self;
}

IoDynLib *IoDynLib_initFuncName(IoDynLib *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("initFuncName", "Returns the initialization function name.")
	*/
	
	return IOSYMBOL(DynLib_initFuncName(DATA(self))); 
}

IoDynLib *IoDynLib_setFreeFuncName(IoDynLib *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("setFreeFuncName(aString)", "Sets the free function name. Returns self.")
	*/
	
	DynLib_setFreeFuncName_(DATA(self), 
					    CSTRING(IoMessage_locals_symbolArgAt_(m, locals, 0)));
	return self;
}

IoDynLib *IoDynLib_freeFuncName(IoDynLib *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("freeFuncName", "Returns the free function name.")
	*/
	
	return IOSYMBOL(DynLib_freeFuncName(DATA(self))); 
}

IoDynLib *IoDynLib_open(IoDynLib *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("open", 
		   "Opens the dynamic library and returns self or raises a DynLoad.open Error if there is an error. ")
	*/
	
	if (IoMessage_argCount(m)) 
	{
		IoDynLib_setPath(self, locals, m);
	}
	
	DynLib_open(DATA(self));
	
	if (DynLib_error(DATA(self)))
	{
		IoState_error_(IOSTATE, m, "Error loading object '%s': '%s'", 
					DynLib_path(DATA(self)), DynLib_error(DATA(self)));
	}
	
	return self;
}

IoDynLib *IoDynLib_close(IoDynLib *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("close", 
		   "Closes the library. Returns self.")
	*/
	
	DynLib_close(DATA(self));
	return self;
}

IoDynLib *IoDynLib_isOpen(IoDynLib *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("isOpen", "Returns true if the library is open, or false otherwise.")
	*/
	
	return IOBOOL(self, DynLib_isOpen(DATA(self)));
}

int bouncer(IoBlock *self, int ret, int a, int b, int c, int d, int e)
{
	IoObject *lobby = IoState_lobby(IOSTATE);
	IoNumber *n;
	static IoMessage *m = NULL;
	List *argNames = ((IoBlockData*)IoObject_dataPointer(self))->argNames;
	
	if (m == NULL)
		m = IoMessage_new(IOSTATE);
	if (0 < argNames->size)
		IoMessage_setCachedArg_toInt_(m, 0, a);
	if (1 < argNames->size)
		IoMessage_setCachedArg_toInt_(m, 1, b);
	if (2 < argNames->size)
		IoMessage_setCachedArg_toInt_(m, 2, c);
	if (3 < argNames->size)
		IoMessage_setCachedArg_toInt_(m, 3, d);
	if (4 < argNames->size)
		IoMessage_setCachedArg_toInt_(m, 4, e);
	
	n = IoBlock_activate(self, lobby, lobby, m, lobby);
	
	if (ISNUMBER(n))
	{
		return IoNumber_asInt(n);
	}
	
	return 0;
}

unsigned int marshal(IoDynLib *self, IoObject *arg)
{
	unsigned int n = 0;
	
	if (ISNUMBER(arg))
	{
		n = IoNumber_asInt(arg);
	}
	else if (ISSYMBOL(arg))
	{
		n = (unsigned int)CSTRING(arg);
	}
	else if (ISLIST(arg)) 
	{
		int i;
		unsigned int *l = malloc(IoList_rawSize(arg) * sizeof(unsigned int));
		for (i = 0; i < IoList_rawSize(arg); i ++)
			l[i] = marshal(self, List_at_(IoList_rawList(arg), i));
		n = (unsigned int)l;
	} 
	else if (ISBUFFER(arg)) 
	{
		n = (unsigned int)IoSeq_rawBytes(arg);
	} 
	else if (ISBLOCK(arg)) 
	{
		unsigned char *blk = malloc(20), *p = blk;
		// FIXME: need trampoline code for other architectures
		*p++ = 0x68;
		*((int *)p) = (int)arg;
		p += sizeof(int);
		*p++ = 0xb8;
		*((int *)p) = (int)bouncer;
		p += sizeof(int);
		*p++ = 0xff;
		*p++ = 0xd0;
		*p++ = 0x83;
		*p++ = 0xc4;
		*p++ = 0x04;
		*p++ = 0xc3;
		n = (unsigned int)blk;
	} 
	else 
	{
		n =  (unsigned int)arg; //IONIL(self);
	}
	
	return n;
}

IoObject *demarshal(IoObject *self, IoObject *arg, unsigned int n)
{
	if (ISNUMBER(arg)) 
	{
		return IONUMBER(n);
	} 
	else if (ISSYMBOL(arg)) 
	{
		if (n == 0)
			return IOSYMBOL("");
		return IOSYMBOL((char*)n);
	} 
	else if (ISLIST(arg)) 
	{
		unsigned int *values = (unsigned int *)n;
		int i;
		
		for (i = 0; i < IoList_rawSize(arg); i ++) 
		{
			IoObject *value = List_at_(IoList_rawList(arg), i);
			List_at_put_(IoList_rawList(arg), i, demarshal(self, value, values[i]));
		}
		
		free(values);
		return arg;
	} 
	else if (ISBUFFER(arg)) 
	{
		return arg;
	} 
	else if (ISBLOCK(arg)) 
	{
		return arg;
	}
	
	return IONIL(self);
}

void IoDynLib_rawVoidCall(void *f, int argCount, unsigned int *params)
{	
	switch(argCount - 1) 
	{
		case 0:
			((void (*)(void))f)();
			break;
		case 1:
			((void (*)(int))f)(params[0]);
			break;
		case 2:
			((void (*)(int,int))f)(params[0], params[1]);
			break;
		case 3:
			((void (*)(int,int,int))f)(params[0], params[1], params[2]);
			break;
		case 4:
			((void (*)(int,int,int,int))f)(params[0], params[1], params[2], params[3]);
			break;
		case 5:
			((void (*)(int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4]);
			break;
		case 6:
			((void (*)(int,int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4], params[5]);
			break;
		case 7:
			((void (*)(int,int,int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4], params[5], params[6]);
			break;
		case 8:
			((void (*)(int,int,int,int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]);
			break;
	}
}

int IoDynLib_rawNonVoidCall(void *f, int argCount, unsigned int *params)
{
	int rc;
	
	switch(argCount - 1) 
	{
		case 0:
			rc = ((int (*)(void))f)();
			break;
		case 1:
			rc = ((int (*)(int))f)(params[0]);
			break;
		case 2:
			rc = ((int (*)(int,int))f)(params[0], params[1]);
			break;
		case 3:
			rc = ((int (*)(int,int,int))f)(params[0], params[1], params[2]);
			break;
		case 4:
			rc = ((int (*)(int,int,int,int))f)(params[0], params[1], params[2], params[3]);
			break;
		case 5:
			rc = ((int (*)(int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4]);
			break;
		case 6:
			rc = ((int (*)(int,int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4], params[5]);
			break;
		case 7:
			rc = ((int (*)(int,int,int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4], params[5], params[6]);
			break;
		case 8:
			rc = ((int (*)(int,int,int,int,int,int,int,int))f)(params[0], params[1], params[2], params[3], params[4], params[5], params[6], params[7]);
			break;
	}
	return rc;
}

IoDynLib *IoDynLib_justCall(IoDynLib *self, IoObject *locals, IoMessage *m, int isVoid)
{
	int n, rc = 0;
	unsigned int *params = NULL;
	IoSymbol *callName = IoMessage_locals_symbolArgAt_(m, locals, 0);
	void *f = DynLib_pointerForSymbolName_(DATA(self), CSTRING(callName));

	//printf("DynLib calling '%s'\n", CSTRING(callName));

	if (f == NULL) 
	{
		IoState_error_(IOSTATE, m, "Error resolving call '%s'.", CSTRING(callName));
		return IONIL(self);
	}
	
	if (IoMessage_argCount(m) > 9) 
	{
		IoState_error_(IOSTATE, m, "Error, too many arguments (%i) to call '%s'.", 
					IoMessage_argCount(m) - 1,
					CSTRING(callName));
		return IONIL(self);
	}
	
	if (IoMessage_argCount(m) > 1)
	{
		params = malloc(IoMessage_argCount(m) * sizeof(unsigned int));
	}
	
	for (n = 0; n < IoMessage_argCount(m) - 1; n++) 
	{
		IoObject *arg = IoMessage_locals_valueArgAt_(m, locals, n + 1);
		unsigned int p = marshal(self, arg);

		params[n] = p;
		
		if (p == 0) 
		{
			IoState_error_(IOSTATE, m, "DynLib error marshalling argument (%i) to call '%s'.", 
						n + 1, CSTRING(callName));
			// FIXME this can leak memory.
			free(params);
			return IONIL(self);
		}
	}
	
#if 0
	printf("calling %s with %i arguments\n", 
		  CSTRING(IoMessage_locals_symbolArgAt_(m, locals, 0)),
		  IoMessage_argCount(m) - 1);
#endif
	
	IoState_pushCollectorPause(IOSTATE);
	
	if (isVoid)
	{
		IoDynLib_rawVoidCall(f, IoMessage_argCount(m), params);
	}
	else
	{
		rc = IoDynLib_rawNonVoidCall(f, IoMessage_argCount(m), params); 
	}
	
	IoState_popCollectorPause(IOSTATE);
	
	
	for (n = 0; n < IoMessage_argCount(m) - 1; n ++) 
	{
		IoObject *arg = IoMessage_locals_valueArgAt_(m, locals, n + 1);
		demarshal(self, arg, params[n]);
	}
	
	free(params);
	
	return isVoid ? IONIL(self) : IONUMBER(rc);
}

IoDynLib *IoDynLib_call(IoDynLib *self, IoObject *locals, IoMessage *m)
{
	return IoDynLib_justCall(self, locals, m, 0);
}

IoDynLib *IoDynLib_voidCall(IoDynLib *self, IoObject *locals, IoMessage *m)
{
	return IoDynLib_justCall(self, locals, m, 1);
}


IoDynLib *IoDynLib_callPluginInitFunc(IoDynLib *self, IoObject *locals, IoMessage *m)
{
	int rc = 0;
	unsigned int *params = NULL;
	void *f = DynLib_pointerForSymbolName_(DATA(self), 
								    CSTRING(IoMessage_locals_symbolArgAt_(m, locals, 0)));
	if (f == NULL) 
	{
		IoState_error_(IOSTATE, m, "Error resolving call '%s'.", 
					CSTRING(IoMessage_locals_symbolArgAt_(m, locals, 0)));
		return IONIL(self);
	}
	
	if (IoMessage_argCount(m) < 1) 
	{
		IoState_error_(IOSTATE, m, "Error, you must give an init function name to check for.");
		return IONIL(self);
	}
	
	params = malloc(sizeof(unsigned int) * 2);
	
	params[0] = (unsigned int)IOSTATE;
	params[1] = (unsigned int)IOSTATE->lobby;
	rc = ((int (*)(int,int))f)(params[0], params[1]);
	free(params);
	
	return IONUMBER(rc);
}

IoSeq *IoDynLib_returnsString(IoDynLib *self, IoObject *locals, IoMessage *m)
{
	int n = IoNumber_asInt(IoMessage_locals_numberArgAt_(m, locals, 0));
	
	if (n == 0)
	{
		return IOSYMBOL("");
	}
	
	return IOSYMBOL((char*)n);
}

