/*#io
Map ioDoc(
          docCopyright("Steve Dekorte", 2002)
          docLicense("BSD revised")    
          docDescription("Object wrapper for an Io coroutine.")
		docCategory("Core")
         */

#include "IoCoroutine.h"
#include "IoObject.h"
#include "IoState.h"
#include "IoCFunction.h"
#include "IoSeq.h"
#include "IoNumber.h"
#include "IoList.h"
#include "IoBlock.h"

//#define DEBUG

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

IoCoroutine *IoMessage_locals_coroutineArgAt_(IoMessage *self, void *locals, int n)
{
	IoObject *v = IoMessage_locals_valueArgAt_(self, (IoObject *)locals, n);
	if (!ISCOROUTINE(v)) IoMessage_locals_numberArgAt_errorForType_(self, (IoObject *)locals, n, "Coroutine");
	return v;
}

void IoCoroutine_writeToStore_stream_(IoCoroutine *self, IoStore *store, BStream *stream)
{
	//IoCoroutineData *data = DATA(self);
}

void IoCoroutine_readFromStore_stream_(IoCoroutine *self, IoStore *store, BStream *stream)
{
	//IoCoroutineData *data = DATA(self);
}

IoTag *IoCoroutine_tag(void *state)
{
	IoTag *tag = IoTag_newWithName_("Coroutine");
	tag->state = state;
	tag->freeFunc  = (TagFreeFunc *)IoCoroutine_free;
	tag->cloneFunc = (TagCloneFunc *)IoCoroutine_rawClone;
	tag->markFunc  = (TagMarkFunc *)IoCoroutine_mark;
	tag->writeToStoreOnStreamFunc  = (TagWriteToStoreOnStreamFunc *)IoCoroutine_writeToStore_stream_;
	tag->readFromStoreOnStreamFunc = (TagReadFromStoreOnStreamFunc *)IoCoroutine_readFromStore_stream_;
	return tag;
}

IoCoroutine *IoCoroutine_proto(void *state)
{	
	IoObject *self = IoObject_new(state);
	
	self->tag = IoCoroutine_tag(state);
	IoObject_setDataPointer_(self, calloc(1, sizeof(IoCoroutineData)));
	DATA(self)->ioStack = Stack_new();
	
	IoState_registerProtoWithFunc_((IoState *)state, self, IoCoroutine_proto);
	
	// init Coroutine proto's coro as the main one
	{
	Coro *coro = Coro_new();
	DATA(self)->cid = coro;
	Coro_initializeMainCoro(coro);
	}

	return self;
}

void IoCoroutine_protoFinish(IoCoroutine *self)
{
	IoMethodTable methodTable[] = {
	{"ioStack", IoCoroutine_ioStack},
	{"run", IoCoroutine_run},
	{"main", IoCoroutine_main},
	{"resume", IoCoroutine_resume},
	{"isCurrent", IoCoroutine_isCurrent},
	{"currentCoroutine", IoCoroutine_currentCoroutine},
	{"implementation", IoCoroutine_implementation},
	{"setMessageDebugging", IoCoroutine_setMessageDebugging},
	{NULL, NULL},
	};
	
	IoObject_addMethodTable_(self, methodTable);
}

IoCoroutine *IoCoroutine_rawClone(IoCoroutine *proto) 
{ 
	IoObject *self = IoObject_rawClonePrimitive(proto);
	IoObject_setDataPointer_(self, calloc(1, sizeof(IoCoroutineData)));
	DATA(self)->ioStack = Stack_new();
	DATA(self)->cid = (Coro *)NULL;
	return self; 
}

IoCoroutine *IoCoroutine_new(void *state)
{
	IoObject *proto = IoState_protoWithInitFunction_((IoState *)state, IoCoroutine_proto);
	IoObject *self = IOCLONE(proto);
	return self;
}

void IoCoroutine_free(IoCoroutine *self) 
{
	Coro *coro = DATA(self)->cid;
	if (coro) Coro_free(coro);
	Stack_free(DATA(self)->ioStack);
	free(DATA(self));
}

void IoCoroutine_mark(IoCoroutine *self) 
{
	//printf("Coroutine_%p mark\n", (void *)self);
	Stack_do_(DATA(self)->ioStack, (ListDoCallback *)IoObject_shouldMark);
}

// raw

Stack *IoCoroutine_rawIoStack(IoCoroutine *self)
{ 
	return DATA(self)->ioStack; 
}

void IoCoroutine_rawShow(IoCoroutine *self)
{
	Stack_do_(DATA(self)->ioStack, (StackDoCallback *)IoObject_show);
	printf("\n");
}

void *IoCoroutine_cid(IoObject *self)
{
	return DATA(self)->cid;
}

// runTarget

void IoCoroutine_rawSetRunTarget_(IoObject *self, IoObject *v)
{
	IoObject_setSlot_to_(self, IOSYMBOL("runTarget"), v);
}

IoObject *IoCoroutine_rawRunTarget(IoObject *self)
{
	return IoObject_rawGetSlot_(self, IOSYMBOL("runTarget"));
}

// runMessage

void IoCoroutine_rawSetRunMessage_(IoObject *self, IoObject *v)
{
	IoObject_setSlot_to_(self, IOSYMBOL("runMessage"), v);
}

IoObject *IoCoroutine_rawRunMessage(IoObject *self)
{
	return IoObject_rawGetSlot_(self, IOSYMBOL("runMessage"));
}

// runLocals

void IoCoroutine_rawSetRunLocals_(IoObject *self, IoObject *v)
{
	IoObject_setSlot_to_(self, IOSYMBOL("runLocals"), v);
}

IoObject *IoCoroutine_rawRunLocals(IoObject *self)
{
	return IoObject_rawGetSlot_(self, IOSYMBOL("runLocals"));
}

// parent

void IoCoroutine_rawSetParentCoroutine_(IoObject *self, IoObject *v)
{
	IoObject_setSlot_to_(self, IOSYMBOL("parentCoroutine"), v);
}

IoObject *IoCoroutine_rawParentCoroutine(IoObject *self)
{
	return IoObject_getSlot_(self, IOSYMBOL("parentCoroutine"));
}

// result

void IoCoroutine_rawSetResult_(IoObject *self, IoObject *v)
{
	IoObject_setSlot_to_(self, IOSYMBOL("result"), v);
}

IoObject *IoCoroutine_rawResult(IoObject *self)
{
	return IoObject_getSlot_(self, IOSYMBOL("result"));
}

// exception

void IoCoroutine_rawRemoveException(IoObject *self)
{
	IoObject_removeSlot_(self, IOSYMBOL("exception"));
}

void IoCoroutine_rawSetException_(IoObject *self, IoObject *v)
{
	IoObject_setSlot_to_(self, IOSYMBOL("exception"), v);
}

IoObject *IoCoroutine_rawException(IoObject *self)
{
	return IoObject_getSlot_(self, IOSYMBOL("exception"));
}

// ioStack

IoObject *IoCoroutine_ioStack(IoCoroutine *self, IoObject *locals, IoMessage *m)
{ 
	/*#io
	docSlot("ioStack", "Returns List of values on this coroutine's stack.")
	*/
	
	return IoList_newWithList_(IOSTATE, Stack_asList(DATA(self)->ioStack));
}

void IoCoroutine_rawReturnToParent(IoCoroutine *self)
{
	IoCoroutine *parent = IoCoroutine_rawParentCoroutine(self);
		  	
	if (parent && ISCOROUTINE(parent))
	{
		IoCoroutine_rawResume(parent);
	}
	else
	{
		if (self == IOSTATE->mainCoroutine)
		{
			printf("IoCoroutine error: attempt to return from main coro\n");
			exit(-1);
		}
	}

	if (!ISNIL(IoCoroutine_rawException(self)))
	{
		IoCoroutine_rawPrintBackTrace(self);
	}
	
	printf("IoCoroutine error: unable to auto abort coro %p by resuming parent coro %s_%p\n", 
		  (void *)self, IoObject_name(parent), (void *)parent);
	exit(-1);
}

void IoCoroutine_coroStart(void *context) // Called by Coro_Start()
{
	IoCoroutine *self = (IoCoroutine *)context;
	IoObject *result;

	IoState_setCurrentCoroutine_(IOSTATE, self);
	//printf("%p-%p start\n", (void *)self, (void *)DATA(self)->cid);
	
	result = IoMessage_locals_performOn_(IOSTATE->mainMessage, self, self);
	
	IoCoroutine_rawSetResult_(self, result);
	IoCoroutine_rawReturnToParent(self);
}

IoObject *IoCoroutine_main(IoObject *self, IoObject *locals, IoMessage *m)
{
	IoObject *runTarget  = IoCoroutine_rawRunTarget(self);
	IoObject *runLocals  = IoCoroutine_rawRunLocals(self);
	IoObject *runMessage = IoCoroutine_rawRunMessage(self);
	
	if (runTarget && runLocals && runMessage)
	{
		return IoMessage_locals_performOn_(runMessage, runLocals, runTarget);
	}
	else
	{
		printf("IoCoroutine_main()  missing needed parameters\n");
	}

	return IONIL(self);
}

Coro *IoCoroutine_rawCoro(IoObject *self)
{
	return DATA(self)->cid;
}


void IoCoroutine_clearStack(IoObject *self)
{
   Stack_clear(DATA(self)->ioStack);
}

void IoCoroutine_rawRun(IoObject *self)
{	
	Coro *coro = DATA(self)->cid;
	//Stack_clear(DATA(self)->ioStack);

	if (coro)
	{
		//printf("error: attempt to run same coro %p twice\n", (void *)self);
		//exit(-1);
	}
	else
	{
		coro = Coro_new();
		DATA(self)->cid = coro;	
	}
	
	{
		IoCoroutine *current = IoState_currentCoroutine(IOSTATE);
		Coro *currentCoro = IoCoroutine_rawCoro(current);
		Coro_startCoro_(currentCoro, coro, self, (CoroStartCallback *)IoCoroutine_coroStart);
		//IoState_setCurrentCoroutine_(IOSTATE, current);
	}
}

IoObject *IoCoroutine_run(IoObject *self, IoObject *locals, IoMessage *m)
{ 
	IoCoroutine_rawRun(self);
	return IoCoroutine_rawResult(self);
}

void IoCoroutine_try(IoObject *self, IoObject *target, IoObject *locals, IoMessage *message)
{
	IoCoroutine *currentCoro = (IoCoroutine *)IoState_currentCoroutine((IoState *)IOSTATE);
	IoCoroutine_rawSetRunTarget_(self, target);
	IoCoroutine_rawSetRunLocals_(self, locals);
	IoCoroutine_rawSetRunMessage_(self, message);
	IoCoroutine_rawSetParentCoroutine_(self, currentCoro);
	IoCoroutine_rawRun(self);
}

IoCoroutine *IoCoroutine_newWithTry(void *state, 
							 IoObject *target, 
							 IoObject *locals, 
							 IoMessage *message)
{
	IoCoroutine *self = IoCoroutine_new(state);
	IoCoroutine_try(self, target, locals, message);
	return self;
}

void IoCoroutine_raiseError(IoCoroutine *self, IoSymbol *description, IoMessage *m)
{
	IoObject *e = IoObject_rawGetSlot_(self, IOSYMBOL("Exception"));
	
	if (e)
	{
		e = IOCLONE(e);
		IoObject_setSlot_to_(e, IOSYMBOL("error"), description);
		if (m) IoObject_setSlot_to_(e, IOSYMBOL("caughtMessage"), m);
		IoObject_setSlot_to_(e, IOSYMBOL("coroutine"), self);
		IoCoroutine_rawSetException_(self, e);
	}
	
	IoCoroutine_rawReturnToParent(self);
}

// methods

IoObject *IoCoroutine_rawResume(IoObject *self)
{ 
	IoCoroutine *current = IoState_currentCoroutine(IOSTATE);
	IoState_setCurrentCoroutine_(IOSTATE, self);
	//printf("IoCoroutine resuming %p\n", (void *)self);
	Coro_switchTo_(IoCoroutine_rawCoro(current), IoCoroutine_rawCoro(self));
	
	//IoState_setCurrentCoroutine_(IOSTATE, current);
	return self;
}

IoObject *IoCoroutine_resume(IoObject *self, IoObject *locals, IoMessage *m)
{ 
	//printf("IoCoroutine_resume()\n");
	return IoCoroutine_rawResume(self);
}

IoObject *IoCoroutine_implementation(IoObject *self, IoObject *locals, IoMessage *m)
{ 
	return IOSYMBOL(CORO_IMPLEMENTATION);
}

IoObject *IoCoroutine_isCurrent(IoObject *self, IoObject *locals, IoMessage *m)
{ 
	IoObject *v = IOBOOL(self, self == IoState_currentCoroutine(IOSTATE));
	return v;
}

IoObject *IoCoroutine_currentCoroutine(IoObject *self, IoObject *locals, IoMessage *m)
{ 
	return IoState_currentCoroutine(IOSTATE);
}

// stack trace

int IoCoroutine_rawIoStackSize(IoObject *self)
{
	return Stack_count(DATA(self)->ioStack);
}

void IoCoroutine_rawPrint(IoObject *self)
{
	Coro *coro = DATA(self)->cid;
	
	if (coro)
	{
		printf("Coroutine_%p with cid %p ioStackSize %i\n", 
			  (void *)self, 
			  (void *)coro, 
			  (int)Stack_count(DATA(self)->ioStack));
	}
}

// debugging

int IoCoroutine_rawDebuggingOn(IoObject *self)
{
	return DATA(self)->debuggingOn;
}

IoObject *IoCoroutine_setMessageDebugging(IoObject *self, IoObject *locals, IoMessage *m)
{
	/*#io
	docSlot("setMessageDebugging(aBoolean)", "Turns on message level debugging for this coro. When on, this 
coro will send a vmWillSendMessage message to the Debugger object before 
each message send and pause itself. See the Debugger object documentation 
for more information. ")
	*/
	IoObject *v = IoMessage_locals_valueArgAt_(m, locals, 0);
	
	DATA(self)->debuggingOn = ISTRUE(v);
	IoState_updateDebuggingMode(IOSTATE);
	
	return self;
}

IoObject *IoObject_performWithDebugger(IoObject *self, IoObject *locals, IoMessage *m)
{
	IoState *state = IOSTATE;
	IoObject *currentCoroutine = IoState_currentCoroutine(state);
	
	if (IoCoroutine_rawDebuggingOn(currentCoroutine))
	{
		IoObject *debugger = state->debugger; // stack retain it?

		if (debugger)
		{							
			IoObject_setSlot_to_(debugger, IOSYMBOL("messageCoroutine"), currentCoroutine);
			IoObject_setSlot_to_(debugger, IOSYMBOL("messageSelf"), self);
			IoObject_setSlot_to_(debugger, IOSYMBOL("messageLocals"), locals);
			IoObject_setSlot_to_(debugger, IOSYMBOL("message"), m);
			
			{
				IoObject *context;
				IoCoroutine *c = IoObject_rawGetSlot_context_(debugger, IOSYMBOL("debuggerCoroutine"), &context);
				IOASSERT(c, "Debugger needs a debuggerCoroutine slot"); 
				IoCoroutine_rawResume(c);
			}
		}
	}
	
	return IoObject_perform(self, locals, m);
}

void IoCoroutine_rawPrintBackTrace(IoObject *self)
{
	IoObject *e = IoCoroutine_rawException(self);
	IoMessage *caughtMessage = IoObject_rawGetSlot_(e, IOSYMBOL("caughtMessage"));
	
	if (IoObject_rawGetSlot_(e, IOSYMBOL("showStack"))) // sanity check
	{
		IoState_on_doCString_withLabel_(IOSTATE, e, "showStack", "[Coroutine]");
	}
	else
	{
		IoSymbol *error = IoObject_rawGetSlot_(e, IOSYMBOL("error"));
		
		if (error)
		{
			fputs(CSTRING(error), stderr); 
			fputs("\n", stderr); 
		}
		else
		{
			fputs("error: [missing error slot in Exception object]\n", stderr); 
		}
		
		if (caughtMessage)
		{
			ByteArray *ba = IoMessage_asMinimalStackEntryDescription(caughtMessage);
			fputs(ByteArray_asCString(ba), stderr); 
			fputs("\n", stderr); 
			ByteArray_free(ba);
		}
	}
}

