#include "TriTciCallStack.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <string.h>
#include <stdio.h>
#endif

void initCallStackRegister(TriTciCallStackRegister * csr)
{
#ifdef _WIN32
	csr->hLock = CreateEvent(NULL, FALSE, TRUE, NULL);
#else
	pthread_mutex_init(&csr->mtxLock, 0);
#endif
	csr->mStacks = newArrayItemList();
}

int saveRemoteId(TriTciCallStackRegister *csr, int nRemoteId, int nLocalId)
{

#ifdef _WIN32
	unsigned long nThreadId = -1;	
	nThreadId = GetCurrentThreadId();
	if(!WaitForSingleObject(csr->hLock, INFINITE))
	{
#else
	pthread_t nThreadId = pthread_self();
	pthread_mutex_lock(&csr->mtxLock);
#endif
		if (nLocalId == -1)//new communication dialogue
        {
			int pTmp = 0;
			LocalCallStack *lcs = newLocalCallStack();
			lcs->nRemoteId = nRemoteId;
			lcs->nThreadId = nThreadId;
			//look for an empty spot inbetween first,
			//if no empty spot is found, then take the last, current item count
			nLocalId = getAvailableIndex(csr->mStacks, -1, &pTmp, &pTmp);
			addArrayItem(csr->mStacks, lcs, nLocalId);
        }
        else//this message belongs to an existing communication dialogue
        {
			LocalCallStack *lcs = csr->mStacks->pData[nLocalId];//take this call stack and...
			lcs->nThreadId = nThreadId;
			if(lcs->nRemoteId == -1)
				lcs->nRemoteId = nRemoteId;
			//else if(lcs->nRemoteId != nRemoteId)
			//	onError("Invalid channel ID")   
		}
#ifdef _WIN32
		SetEvent(csr->hLock);
	}
#else
		pthread_mutex_unlock(&csr->mtxLock);
#endif
	return nLocalId;
}

void onAck(TriTciCallStackRegister *csr, int nLocalId)
{
#ifdef _WIN32
	if(!WaitForSingleObject(csr->hLock, INFINITE))
	{
#else
		pthread_mutex_lock(&csr->mtxLock);
#endif
		if (nLocalId < csr->mStacks->reserved)
		{
			LocalCallStack *lcs = csr->mStacks->pData[nLocalId];
		if (lcs)
		{
				TriTciCallDataStack *stack = lcs->stack;
				if (stack->items == 0)
					deleteArrayItem(csr->mStacks, nLocalId);
				else
					lcs->nThreadId = (THREAD_ID_TYPE)-1;
		}
        }
#ifdef _WIN32
		SetEvent(csr->hLock);
	}
#else
		pthread_mutex_unlock(&csr->mtxLock);
#endif
}

void fillId_proc(TriTciCallStackRegister *csr, char * pData, int bPop)
{
#ifdef _WIN32
	unsigned long nThreadId = GetCurrentThreadId();
#else
	pthread_t nThreadId = pthread_self();
#endif
    int nRemoteId = -1, nLocalId = -1;
	int nFirstFreeIndex = -1;

	Array* pItems = csr->mStacks;
	int i = 0;
	for (i = 0; nLocalId < 0 && i < pItems->reserved; i++)
	{
		LocalCallStack *lcs = pItems->pData[i];
		if (!lcs)
		{
			if (nFirstFreeIndex < 0)
				nFirstFreeIndex = i;                        
		}
		else if (lcs->nThreadId == nThreadId)
		{
			nLocalId = i;
			nRemoteId = lcs->nRemoteId;
		}                    
	}
	if (nLocalId < 0)
	{
		LocalCallStack *lcs = newLocalCallStack();
		lcs->nThreadId = nThreadId;
		lcs->nRemoteId = -1;
		lcs->isInternal = -1;
		if (nFirstFreeIndex < 0)
			addArrayItem(pItems, lcs, pItems->items);
		else
		{
			nLocalId = nFirstFreeIndex;
			addArrayItem(pItems, lcs, nLocalId);
		}
	}
	if (bPop)
	{
		if (nLocalId < csr->mStacks->reserved)
		{
			LocalCallStack *lcs = csr->mStacks->pData[nLocalId];
			if (lcs)
			{
				TriTciCallDataStack *stack = lcs->stack;
				if (stack->items == 0)
					deleteArrayItem(csr->mStacks, nLocalId);
				else
					lcs->nThreadId = (THREAD_ID_TYPE)-1;
			}
		}
	}
	memcpy(pData, &nRemoteId, 4);
	memcpy(&pData[4], &nLocalId, 4);
}

void fillCommunicationIds(TriTciCallStackRegister *csr, char * pData, int bPop)
{
#ifdef _WIN32
	if(!WaitForSingleObject(csr->hLock, INFINITE))
	{
#else
		pthread_mutex_lock(&csr->mtxLock);
#endif
        fillId_proc(csr, pData, bPop);
#ifdef _WIN32
		SetEvent(csr->hLock);
	}
#else
		pthread_mutex_unlock(&csr->mtxLock);
#endif
}

TriTciCallData * registerCall(TriTciCallStackRegister *csr, char * pData, int requiredAck, int nWaitingTime)
{
	TriTciCallDataStack* stack = 0;
    TriTciCallData* res = 0;

#ifdef _WIN32
	if(!WaitForSingleObject(csr->hLock, INFINITE))
	{
#else
		pthread_mutex_lock(&csr->mtxLock);
#endif
		LocalCallStack * lcs;
		int nLocalId = -1;
		fillId_proc(csr, pData, 0);
		memcpy(&nLocalId, &pData[4], 4);

		//if it gets allocated after the channel is closed,
		//it will never be released
		res = newTriTciCallData(requiredAck, nWaitingTime);
		lcs = csr->mStacks->pData[nLocalId];
		stack = lcs->stack;

		if(stack->items)
			suspendWaiting(peekItem(stack));
		pushItem(stack, res);

#ifdef _WIN32
		SetEvent(csr->hLock);
	}
#else
		pthread_mutex_unlock(&csr->mtxLock);
#endif

	return res;
}

int acceptMessage(TriTciCallStackRegister *csr, AbstractTriTciMessage *msg)
{
	int nLocalId = msg->nLocalId;
#ifdef _WIN32
	if(!WaitForSingleObject(csr->hLock, INFINITE))
	{
#else
		pthread_mutex_lock(&csr->mtxLock);
#endif
		LocalCallStack *lcs = 0;
		if(nLocalId >= 0 && msg->nLocalId < csr->mStacks->reserved)
			lcs = csr->mStacks->pData[nLocalId];
		if(lcs)
		{
			TriTciCallDataStack* stack = lcs->stack;
			if(stack->items)
			{
				TriTciCallData * cd = peekItem(stack);
				if(acceptMsg(cd, msg))//unregister
				{
					unregisterProc(csr, stack, nLocalId);
#if _WIN32
					SetEvent(csr->hLock);
#else
					pthread_mutex_unlock(&csr->mtxLock);
#endif
					return 1;
				}
			}
		}
#ifdef _WIN32
		SetEvent(csr->hLock);
	}
#else
		pthread_mutex_unlock(&csr->mtxLock);
#endif
    return 0;
}

void unregisterProc(TriTciCallStackRegister *csr, TriTciCallDataStack* stack, int nLocalId)
{
	popItem(stack);
	if (stack->items == 0)
	{
		LocalCallStack * lcs = csr->mStacks->pData[nLocalId];
		if(lcs && lcs->isInternal)
			deleteArrayItem(csr->mStacks, nLocalId);
	}
	else
	{
		// restart timer for the old call
		restartWaiting(peekItem(stack));		
		csr->mStacks->pData[nLocalId]->nThreadId = 
#ifdef _WIN32
			GetCurrentThreadId();
#else
			pthread_self();
#endif
	}
}

void unregisterCall(TriTciCallStackRegister *csr)//, int nLocalId)
{
#ifdef _WIN32
	if(!WaitForSingleObject(csr->hLock, INFINITE))
	{
		int i;
		unsigned long nThreadId = GetCurrentThreadId();
#else
		int i;
		pthread_t nThreadId = pthread_self();
		pthread_mutex_lock(&csr->mtxLock);
#endif
		for(i = 0; i < csr->mStacks->reserved; i++)
		{
			LocalCallStack * lcs = csr->mStacks->pData[i];
			if(lcs && lcs->nThreadId == nThreadId)
				unregisterProc(csr, lcs->stack, i);
		}
#ifdef _WIN32
		SetEvent(csr->hLock);
	}
#else
		pthread_mutex_unlock(&csr->mtxLock);
#endif
}

void clear(TriTciCallStackRegister *csr)
{
	int i;
	for(i = 0; i<csr->mStacks->reserved; i++)
	{
		LocalCallStack * lcs = csr->mStacks->pData[i];
		csr->mStacks->pData[i] = 0;
		if(lcs)
		{
			TriTciCallDataStack* st = lcs->stack;
			csr->mStacks->items--;
			while(st->items)
			{
				TriTciCallData *cd = popItem(st);
				stop(cd);
			}
			disposeLocalCallStack(lcs);
		}	
	}
}

void disposeCallStackRegister(TriTciCallStackRegister * csr)
{
#ifdef _WIN32
	if(csr->hLock && !WaitForSingleObject(csr->hLock, INFINITE))
#endif
	{
		clear(csr);
		disposeArray(csr->mStacks);
		csr->mStacks = 0;
#ifdef _WIN32
		CloseHandle(csr->hLock);
		csr->hLock = 0;
#else
		pthread_mutex_destroy(&csr->mtxLock);
#endif
		
	}
}
