/////////////////////////////////////////////////////////////////////////////
//

#include <time.h>
#include <errno.h>
#include "timer.h"

/////////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG
#define _VALIDATE_TIMER_RET(tnum, ret)		if(!ValidateTimer(tnum, __FUNCTION__)) return (ret)
#define _VALIDATE_TIMER_VOID(tnum)			if(!ValidateTimer(tnum, __FUNCTION__)) return
#define TRACE								printf
#else	//	_DEBUG
#define _VALIDATE_TIMER_RET(tnum, ret)
#define _VALIDATE_TIMER_VOID(tnum)
#define TRACE(...)
#endif	//	_DEBUG

#define _GFA_DEFAULT_TIMER_COUNT			32
#define _GFA_MAX_TIMER_COUNT				256
#define _COUNTOF(a)							(sizeof(a) / sizeof(*a))
#define _INVALID_THREAD_ID					0
#define _NANOSECS_PER_SEC					1000000000

/////////////////////////////////////////////////////////////////////////////

const int CGfaTimer::m_nPulseFreq[GTCP_Last] = {250, 500, 750, 1000, 1500, 2000, 5000, 10000};

/////////////////////////////////////////////////////////////////////////////

CGfaTimer::CGfaTimer(void) : m_pTimer(NULL), m_nTimerCount(0), m_nTimerIncrement(0), m_nClockCount(0), m_nClockPulse(0), m_threadID(_INVALID_THREAD_ID), m_bProcessTimers(false)
{
	memset(&m_timInterval, 0, sizeof(m_timInterval));
	memset(m_ClkPulseCallbackTable, 0, sizeof(m_ClkPulseCallbackTable));

	if(!m_mutex.Create(true))
	{
		TRACE("CGfaTimer::CGfaTimer: failed to create mutex!\n");
		throw -1;
	}
}

CGfaTimer::~CGfaTimer(void)
{
	Release();
}

/////////////////////////////////////////////////////////////////////////////

int CGfaTimer::Initialize(int nTimerCount, GfaTimerResolution res)
{
	if(!m_pTimer)
	{
		if(nTimerCount == 0)
			nTimerCount = _GFA_DEFAULT_TIMER_COUNT;
		else if(nTimerCount < 0)
		{
			TRACE("CGfaTimer::Initialize: invalid timer count: %d!\n", nTimerCount);
			return 0;
		}
		else if(nTimerCount > _GFA_MAX_TIMER_COUNT)
			nTimerCount = _GFA_MAX_TIMER_COUNT;

		switch(res)
		{
		case GTR_10ms:
			m_timInterval.tv_sec	= 0;
			m_timInterval.tv_nsec	= 10000000;
			m_nTimerIncrement		= 10;
			break;
		case GTR_25ms:
			m_timInterval.tv_sec	= 0;
			m_timInterval.tv_nsec	= 25000000;
			m_nTimerIncrement		= 25;
			break;
		case GTR_50ms:
			m_timInterval.tv_sec	= 0;
			m_timInterval.tv_nsec	= 50000000;
			m_nTimerIncrement		= 50;
			break;
		default:
			TRACE("CGfaTimer::Initialize: invalid timer resolution: %d!\n", (int)res);
			return 0;
		}

		m_pTimer = new GFA_TIMER[nTimerCount];
		memset(m_pTimer, 0, sizeof(GFA_TIMER) * nTimerCount);
		m_nTimerCount = nTimerCount;
		m_bProcessTimers = true;

		if(::pthread_create(&m_threadID, NULL, &CGfaTimer::LordOfTheTimers, reinterpret_cast<void*>(this)))
		{
			TRACE("CGfaTimer::Initialize: Failed to create timer thread!\n");
			Release();
		}

		TRACE("CGfaTimer::Initialize: Success!\n");
	}
	else
	{
		TRACE("CGfaTimer::Initialize: Already initialized!\n");
	}

	return m_nTimerCount;
}

/////////////////////////////////////////////////////////////////////////////

void CGfaTimer::Release(void)
{
	if(m_pTimer)
	{
		m_mutex.Lock();
		if(m_threadID != _INVALID_THREAD_ID)
		{
			m_bProcessTimers = false;
			m_mutex.Unlock();
			::pthread_join(m_threadID, NULL);
			m_mutex.Lock();
			m_threadID = _INVALID_THREAD_ID;
		}

		m_nTimerCount = m_nTimerIncrement = m_nClockCount = m_nClockPulse = 0;
		delete [] m_pTimer;
		m_pTimer = NULL;
		m_mutex.Unlock();
		TRACE("CGfaTimer::Release: Timer released.\n");
	}
}

/////////////////////////////////////////////////////////////////////////////

void CGfaTimer::RegisterTimerCallback(int tnum, pfnTIMERCALLBACK pfnCallback, void *pContext)
{
	_VALIDATE_TIMER_VOID(tnum);
	if(pfnCallback)
	{
		m_mutex.Lock();
	    m_pTimer[tnum].pfnCallback		= pfnCallback;
    	m_pTimer[tnum].pContext			= pContext;
    	m_pTimer[tnum].bCallScheduled	= false;
		m_mutex.Unlock();
		TRACE("CGfaTimer::RegisterTimerCallback: Timer %d - callback: %p, context: %p.\n", tnum, pfnCallback, pContext);
    }
    else
	{
		m_mutex.Lock();
	    m_pTimer[tnum].pfnCallback		= NULL;
    	m_pTimer[tnum].pContext			= NULL;
    	m_pTimer[tnum].bCallScheduled	= false;
		m_mutex.Unlock();
		TRACE("CGfaTimer::RegisterTimerCallback: Timer %d - Unregister callback.\n", tnum);
	}
}

void CGfaTimer::RegisterClockPulseCallback(GfaTimerClockPulse cp, pfnPULSECALLBACK pfnCallback, void *pContext)
{
	if(cp <= GTCP_Invalid || cp >= GTCP_Last)
	{
		TRACE("CGfaTimer::RegisterClockPulseCallback: invalid clock pulse %d!\n", cp);
		return;
	}
	
	if(pfnCallback)
	{
		m_mutex.Lock();
		m_ClkPulseCallbackTable[cp].pfnCallback	= pfnCallback;
		m_ClkPulseCallbackTable[cp].pContext	= pContext;
		m_mutex.Unlock();
		TRACE("CGfaTimer::RegisterClockPulseCallback:  Clock pulse %dms: Register callback: %p, context: %p.\n", CGfaTimer::m_nPulseFreq[cp], pfnCallback, pContext);
	}
	else
	{
		m_mutex.Lock();
		m_ClkPulseCallbackTable[cp].pfnCallback	= NULL;
		m_ClkPulseCallbackTable[cp].pContext	= NULL;
		m_mutex.Unlock();
		TRACE("CGfaTimer::RegisterClockPulseCallback: Clock pulse %dms: Unregister callback.\n", CGfaTimer::m_nPulseFreq[cp]);
	}
}

/////////////////////////////////////////////////////////////////////////////

bool CGfaTimer::ValidateTimer(int tnum, const char *pszCaller)
{
	if(!m_pTimer)
	{
		TRACE("%s: Timer not initialized!\n", pszCaller);
		return false;
	}

	if((tnum < 0) || (tnum >= m_nTimerCount))
	{
		TRACE("%s: Invalid Timer number: %d\n", pszCaller, tnum);
		return false;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////

int CGfaTimer::tf_test(int tnum)
{
	_VALIDATE_TIMER_RET(tnum, 0);
	m_mutex.Lock();
    int nRet = m_pTimer[tnum].tf;
	m_mutex.Unlock();
	return nRet;
}

/////////////////////////////////////////////////////////////////////////////

void CGfaTimer::tf_set(int tnum)
{
	_VALIDATE_TIMER_VOID(tnum);
	m_mutex.Lock();
	m_pTimer[tnum].tf = 1;
	m_mutex.Unlock();
}

/////////////////////////////////////////////////////////////////////////////

void CGfaTimer::tf_clear(int tnum)
{
	_VALIDATE_TIMER_VOID(tnum);
	m_mutex.Lock();
	m_pTimer[tnum].tf = 0;
	m_mutex.Unlock();
}

/////////////////////////////////////////////////////////////////////////////

void CGfaTimer::tf_store(int tnum, int fset)
{
	_VALIDATE_TIMER_VOID(tnum);
	if(fset)
	{
		m_mutex.Lock();
		m_pTimer[tnum].tf = 1;
		m_mutex.Unlock();
	}
	else
	{
		m_mutex.Lock();
		m_pTimer[tnum].tf = 0;
		m_mutex.Unlock();
	}
}

/////////////////////////////////////////////////////////////////////////////

unsigned long CGfaTimer::tw_read(int tnum)
{
	_VALIDATE_TIMER_RET(tnum, 0);
	m_mutex.Lock();
    unsigned long nRet = m_pTimer[tnum].tw;
	m_mutex.Unlock();
	return nRet;
}

/////////////////////////////////////////////////////////////////////////////

void CGfaTimer::tw_set(int tnum, unsigned long val)
{
	_VALIDATE_TIMER_VOID(tnum);
	m_mutex.Lock();
    m_pTimer[tnum].tw = val;
	m_mutex.Unlock();
}

/////////////////////////////////////////////////////////////////////////////

int CGfaTimer::cp_test(GfaTimerClockPulse cp)
{
	if(cp > GTCP_Invalid && cp < GTCP_Last)
	{
		return !!(m_nClockPulse & (0x00000001 << cp));
	}

	TRACE("CGfaTimer::cp_test: Invalid clock pulse: %d.\n", (int)cp);
	return 0;
}

/////////////////////////////////////////////////////////////////////////////

void CGfaTimer::AddTimeSpecs(const struct timespec &ts1, const struct timespec &ts2, struct timespec &tsResult)
{
	long ns = ts1.tv_nsec + ts2.tv_nsec;
	tsResult.tv_sec = ts1.tv_sec + ts2.tv_sec + (time_t)ns / _NANOSECS_PER_SEC;
	tsResult.tv_nsec = ns % _NANOSECS_PER_SEC;
}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

void* CGfaTimer::LordOfTheTimers(void *pParam)
{
	int nRet;
	struct timespec tsDueTime;
	CGfaTimer* pSelf = reinterpret_cast<CGfaTimer*>(pParam);
	TRACE("CGfaTimer::LordOfTheTimers: thread enter.\n");

	::clock_gettime(CLOCK_MONOTONIC, &tsDueTime);

	/////////////////////////////////////////////////////////////////////////
	// enter loop

	do
	{
		/////////////////////////////////////////////////////////////////////

		CGfaTimer::AddTimeSpecs(tsDueTime, pSelf->m_timInterval, tsDueTime);

		if((nRet = ::clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &tsDueTime, NULL)))
		{
			while(nRet && (errno == EINTR))
			{
				errno = 0;
				nRet = ::clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &tsDueTime, NULL);
			}
		}
		/////////////////////////////////////////////////////////////////////

		for(int i = 0; i < pSelf->m_nTimerCount; i++)
		{
			GFA_TIMER &rt = pSelf->m_pTimer[i];

			pSelf->m_mutex.Lock();
            if(rt.tf)
            {
                if(rt.tw > pSelf->m_nTimerIncrement)
                	rt.tw -= pSelf->m_nTimerIncrement;
                else if(rt.tw)
                {
                	rt.tw = 0;
                	if(rt.pfnCallback)
                		rt.bCallScheduled = true;
                }
            }
            else if(rt.tw)
            {
                rt.tw = 0;
            }
			pSelf->m_mutex.Unlock();
		}

		/////////////////////////////////////////////////////////////////////
		// process clock pulses

		pSelf->m_mutex.Lock();
		bool bDoCallbacks = false;
		unsigned long nPrevClockPulse = pSelf->m_nClockPulse;
		unsigned long nScheduled;
		pSelf->m_nClockCount += pSelf->m_nTimerIncrement;

		if(!(pSelf->m_nClockCount % CGfaTimer::m_nPulseFreq[0]))
		{
			unsigned long nPulseMask = 1;
			pSelf->m_nClockPulse ^= nPulseMask;

			for(int i = 1; i < GTCP_Last; i++)
			{
				nPulseMask <<= 1;

				if(!(pSelf->m_nClockCount % CGfaTimer::m_nPulseFreq[i]))
				{
					pSelf->m_nClockPulse ^= nPulseMask;
				}
			}

			bDoCallbacks	= true;
			nScheduled		= pSelf->m_nClockPulse ^ nPrevClockPulse;
		}

		pSelf->m_mutex.Unlock();

		/////////////////////////////////////////////////////////////////////
		// call clock pulse callbacks
		
		if(bDoCallbacks)
		{
			unsigned long nPulseMask	= 1;

			for(int i = GTCP_250ms; i < GTCP_Last; i++, nPulseMask <<= 1)
			{
				pSelf->m_mutex.Lock();
				pfnPULSECALLBACK pfnCallback = pSelf->m_ClkPulseCallbackTable[i].pfnCallback;
				void *pContext = pSelf->m_ClkPulseCallbackTable[i].pContext;
				int nState = !!(pSelf->m_nClockPulse & nPulseMask);
				pSelf->m_mutex.Unlock();
				
				if(pfnCallback && (nScheduled & nPulseMask))
				{
					(*pfnCallback)((GfaTimerClockPulse)i, nState, pContext);
				}
			}
		}

		/////////////////////////////////////////////////////////////////////
		// process timer callbacks

		for(int i = 0; i < pSelf->m_nTimerCount; i++)
		{
			GFA_TIMER &rt = pSelf->m_pTimer[i];

			pSelf->m_mutex.Lock();
            if(rt.pfnCallback && rt.tf && rt.bCallScheduled && !rt.tw)
            {
            	rt.bCallScheduled = false;
				(*rt.pfnCallback)(i, rt.pContext);
            }
			pSelf->m_mutex.Unlock();
		}
	}
	while(pSelf->m_bProcessTimers);

	TRACE("CGfaTimer::LordOfTheTimers: thread exit.\n");
	return NULL;
}
