#include "TriTciCallData.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <stdio.h>
#endif

TriTciCallData* newTriTciCallData(int requiredAck, int nWaitingTime)
{
	TriTciCallData *item = (TriTciCallData*)malloc(sizeof(TriTciCallData));
	item->requiredAck = requiredAck;
	item->nWaitingTime = nWaitingTime;
	item->nStartingTime = 0;
#ifdef _WIN32
	item->evAck = CreateEvent(NULL, FALSE, FALSE, NULL);
	item->evRestart = CreateEvent(NULL, FALSE, FALSE, NULL);
	item->evDone = CreateEvent(NULL, FALSE, TRUE, NULL);
#else
	mrevent_init(&item->evAck, 0);
	mrevent_init(&item->evDone, 0);
#endif
	item->msg = (AbstractTriAckMessage*)malloc(sizeof(AbstractTriAckMessage));
	memset(item->msg, 0, sizeof(AbstractTriAckMessage));
	return item;
}

void disposeCallData(TriTciCallData* data)
{
#ifdef _WIN32
	CloseHandle(data->evAck);
	CloseHandle(data->evDone);
	CloseHandle(data->evRestart);
#else
	mrevent_close(&data->evAck);
	mrevent_close(&data->evDone);
#endif
	free(data);
}

int acceptMsg(TriTciCallData *cd, AbstractTriTciMessage *msg)
{
	if (!msg)
		return 0;
	if (msg->messageCode == cd->requiredAck)
	{
		cd->msg->messageCode = msg->messageCode;
		cd->msg->nLen = msg->nLen;
		cd->msg->nSourceThreadId = msg->nSourceThreadId;
		cd->msg->pData = (char*)malloc(msg->nLen);
		cd->msg->nLocalId = msg->nLocalId;
		cd->msg->nServerId = msg->nServerId;
		memcpy(cd->msg->pData, msg->pData, msg->nLen);
#ifdef _WIN32
		SetEvent(cd->evAck);
#else
		mrevent_trigger(&cd->evAck);
#endif
        return 1;
    }
    return 0;
}

void stop(TriTciCallData *cd)
{
#ifdef _WIN32
	ResetEvent(cd->evRestart);
	SetEvent(cd->evDone);
	SetEvent(cd->evAck);   
#else
	mrevent_trigger_code(&cd->evDone, 1);
	mrevent_trigger(&cd->evAck); 
#endif
}

void suspendWaiting(TriTciCallData *cd)
{
#ifdef _WIN32
	ResetEvent(cd->evDone);
#else
	mrevent_reset(&cd->evDone);
#endif
}

void restartWaiting(TriTciCallData *cd)
{
#ifdef _WIN32
	if (WaitForSingleObject(cd->evDone, 0))
		SetEvent(cd->evRestart);
#else
	if (!mrevent_timedwait(&cd->evDone, 0))
	{
		mrevent_trigger_code(&cd->evDone, 2);
	}
#endif
}

unsigned long getCurrentTick()
{
#if _WIN32
	return GetTickCount();
#else
	struct timeval tv;
	gettimeofday(&tv, 0);
	return (unsigned long)tv.tv_sec * 1000 + (unsigned long)tv.tv_usec/1000;
#endif
}

AbstractTriAckMessage* waitForAck(TriTciCallData *cd)
{
#if _WIN32
	HANDLE exitOrRestart[2];
#endif
	int bRestart = 0;
	cd->nStartingTime = getCurrentTick();
	do
	{
		int nTime = 0;
#if _WIN32
		SetEvent(cd->evDone);
#else
		mrevent_trigger_code(&cd->evDone, 1);
#endif
		nTime = cd->nWaitingTime - getEllapsedTime(cd->nStartingTime);
		if (nTime < 0)
		{
			disposeCallData(cd);
			return 0;
		}
#ifdef _WIN32
		if (!WaitForSingleObject(cd->evAck, nTime))
#else
		if (mrevent_timedwait(&cd->evAck, nTime))
#endif
		{
			AbstractTriAckMessage *msg = cd->msg;
			disposeCallData(cd);
			return msg;
		}
#ifdef _WIN32
		exitOrRestart[0] = cd->evDone;
		exitOrRestart[1] = cd->evRestart;
		bRestart = WaitForMultipleObjects(2, exitOrRestart, FALSE, INFINITE) == 1;
#else
		bRestart = mrevent_wait(&cd->evDone) == 2;
#endif
		cd->nStartingTime = getCurrentTick();
	} while (bRestart);

	disposeCallData(cd);
    return 0;
}

int getEllapsedTime(int nStartingTime)
{
	//use clock_t currentTime = clock(); //declared in <time.h> for non-windows compilers
	//time_t timeTillTrigger = clock() + seconds* CLOCKS_PER_SECOND; //offsetTime + timePeriod
	unsigned long currentTime = getCurrentTick();
	return currentTime - nStartingTime;
}

TriTciCallDataStack* newTriTciCallDataStack()
{
	TriTciCallDataStack* stack = (TriTciCallDataStack*)malloc(sizeof(TriTciCallDataStack));

	stack->items = 0;
	stack->allocated = 10;
	stack->pData = (TriTciCallData**)malloc(10 * sizeof(TriTciCallData*));
	memset(stack->pData, 0, 10 * sizeof(TriTciCallData*));
	return stack;
}

void disposeStack(TriTciCallDataStack * st)
{
	int i;
	for(i = 0 ;i < st->items; i++)
	{
		TriTciCallData *cd = st->pData[i];
		disposeCallData(cd);
	}
	st->items = 0;
	if(st->pData)
	{
		free(st->pData);
		st->pData = 0;
	}
	free(st);
}

void increaseStackSize(TriTciCallDataStack *stack)
{
	int oldSize = stack->allocated;
	int newSize = oldSize * 2;
	TriTciCallData** newStack = (TriTciCallData**)malloc(newSize * sizeof(TriTciCallData*));
	memset(newStack, 0, newSize * sizeof(TriTciCallData*));
	memcpy(newStack, stack->pData, stack->items * sizeof(TriTciCallData*));
	free(stack->pData);
	stack->pData = newStack;
}

void pushItem(TriTciCallDataStack *st, TriTciCallData *data)
{
	if(st->items == st->allocated)
		increaseStackSize(st);
	st->pData[st->items++] = data;
}

TriTciCallData * popItem(TriTciCallDataStack *st)
{
	TriTciCallData * ret = 0;
	if(st->items > 0)
		ret = st->pData[--st->items];
	return ret;
}

TriTciCallData * peekItem(TriTciCallDataStack *st)
{
	if(st->items > 0)
		return st->pData[st->items - 1];
	return 0;
}

LocalCallStack * newLocalCallStack()
{
	LocalCallStack * newStack;
	newStack = (LocalCallStack *)malloc(sizeof(LocalCallStack));
	newStack->stack = newTriTciCallDataStack();
	newStack->nLocalId = -1;
	newStack->nRemoteId = -1;
	newStack->nThreadId = (THREAD_ID_TYPE)-1;
	newStack->isInternal = 0;
	return newStack;
}

void disposeLocalCallStack(LocalCallStack * lcs)
{
	disposeStack(lcs->stack);
	free(lcs);
}

AbstractTriAckMessage * newAstractTriTciMessage()
{
	AbstractTriAckMessage *msg = (AbstractTriAckMessage*)malloc(sizeof(AbstractTriAckMessage));
	memset(msg, 0, sizeof(AbstractTriAckMessage));
	msg->nLocalId = -1;
	msg->nServerId = -1;
	return msg;
}

void disposeAbstractMsg(AbstractTriAckMessage* msg)
{
	if(msg)
		free(msg->pData);
	free(msg);
}
