/*
  Copyright (c) 2007-2013, John Ryland
 */
#include <event.h>
#include <keypad.h>
#include <corelayer.h>
#include <oslayer.h>


inline void getTimeOfDay(timeval* tv)
{
#if defined(ARCH_MacOSX64)
  tv->tv_sec = sysGetTimeOfDay(tv, 0);
  tv->tv_usec = 0;
#else
  sysGetTimeOfDay(tv, 0);
#endif
}


timeval globalTimeOfDay = { -1, -1 };

typedef struct {
    unsigned msTimeout;
    unsigned repeat;
    unsigned id;
    void *data;
    timeval next;
} Timer;

Timer timerPool[128];
unsigned timerPoolFlag[128];
unsigned timerPoolInit = 0;

void timerPool_Init()
{
    unsigned i;
    for (i = 0; i < 128; i++)
		timerPoolFlag[i] = 0;
    timerPoolInit = 1;
}

Timer *timerPool_AllocTimer()
{
    unsigned i;
    if (!timerPoolInit)
		timerPool_Init();
    for (i = 0; i < 128; i++) {
		if ( timerPoolFlag[i] == 0 ) {
			timerPoolFlag[i] = 1;
			return &timerPool[i];
		}
    }
    strPrint("Timer Pool Full\n"); // XXX
    return 0;
}

void timerPool_FreeTimer(Timer *timer)
{
    unsigned i;
    if (!timerPoolInit)
		timerPool_Init();
    for (i = 0; i < 128; i++) {
		if ( &timerPool[i] == timer ) {
			timerPoolFlag[i] = 0;
			return;
		}
    }
    strPrint("Erroneous Timer, not from Timer Pool\n");
}

Timer *timerQueue[128];
unsigned timerQueueCount = 0;
unsigned timerQueueWatermark = 0;
unsigned lastTimerId = 0;

int timerQueue_CreateTimer(unsigned int msTimeout, unsigned int repeat, void *data)
{
//    strPrint("creating timer\n");
    if (timerQueueCount > 127) {
		strPrint("Timer Queue Full, ignoring timer\n"); // XXX
    } else {
		Timer *timer = timerPool_AllocTimer();
//	strPrint("allocated timer\n");
		if (timer) {
			lastTimerId++;
			timer->id = lastTimerId;
			timer->data = data;

//	    	strPrint("created ");
//	    	if (timer->data)
//			strPrint((char*)timer->data);
//	    	strPrint(" timer\n");

			timer->msTimeout = msTimeout;
			timer->repeat = repeat;

			if ( globalTimeOfDay.tv_usec == -1 && globalTimeOfDay.tv_sec == -1 )
                getTimeOfDay(&globalTimeOfDay);
			timer->next = globalTimeOfDay;
//             sysGetTimeOfDay(&timer->next, 0);

			timer->next.tv_usec += msTimeout * 1000;
			while (timer->next.tv_usec > 1000000) {
				timer->next.tv_usec -= 1000000;
				timer->next.tv_sec++;
			}
			timerQueue[timerQueueCount] = timer;
			timerQueueCount++;
//START_PROFILE
			if (timerQueueCount > timerQueueWatermark)
				timerQueueWatermark = timerQueueCount;
//END_PROFILE
//	    	strPrint("returning timer\n");
			return timer->id;
		}
    }
    return 0;
}


void timerQueue_DeleteTimer(int id)
{
//    strPrint("deleting timer\n");
    if (!timerQueueCount) {
		strPrint("Timer Queue Empty, you shouldn't be calling this if it is empty\n");
    } else {
		unsigned i = 0;
		while (timerQueue[i]->id != id && i < timerQueueCount)
			i++;
		if ( i < timerQueueCount ) {
			Timer *tm = timerQueue[i];
			timerQueueCount--;
			while (i < timerQueueCount) {
				timerQueue[i] = timerQueue[i + 1];
				i++;
			}

//	    	strPrint("deleting ");
//	    	if (tm->data)
//				strPrint((char*)tm->data);
//	    	strPrint(" timer\n");

			timerPool_FreeTimer(tm);
		}
    }
}

Timer *timerQueue_FindNextTimer()
{
#define INT_MAX	    2147483647
    timeval tmSmallest = { INT_MAX, INT_MAX };
    unsigned i, t = 0;
    if (!timerQueueCount)
		return 0;
    for (i = 0; i < timerQueueCount; i++) {
		if ((timerQueue[i]->next.tv_sec < tmSmallest.tv_sec) ||
			((timerQueue[i]->next.tv_sec == tmSmallest.tv_sec) &&
			(timerQueue[i]->next.tv_usec < tmSmallest.tv_usec)))
		{
			tmSmallest = timerQueue[i]->next;
			t = i;
		}
    }
    return timerQueue[t];
}

void fireTimer(Timer *timer)
{
    Event te;
    if (!timer) {
		strPrint("Strange, timer is empty\n");
		return;
    }
    te.type = TimerEventType;
    te.event.timerEvent.id = timer->id;
    te.event.timerEvent.data = timer->data;
    strPrint("Adding timeout event\n");
    eventQueue_AppendEvent(te);
    if (timer->repeat) {
		timer->next.tv_usec += timer->msTimeout * 1000;
		while (timer->next.tv_usec > 1000000) {
			timer->next.tv_usec -= 1000000;
			timer->next.tv_sec++;
		}
    } else {
		timerQueue_DeleteTimer(timer->id);
    }
}

// Special case for firing timers that are now late
// Old non-repeating timers will generate an event
// Old repeating timers that missed their timeout do not.
void fireExpiredTimer(Timer *timer)
{
    Event te;
    if (!timer) {
		strPrint("Strange, timer is empty\n");
		return;
    }
    if (timer->repeat) {
		timer->next.tv_usec += timer->msTimeout * 1000;
		while (timer->next.tv_usec > 1000000) {
			timer->next.tv_usec -= 1000000;
			timer->next.tv_sec++;
		}
    } else {
		te.type = TimerEventType;
		te.event.timerEvent.id = timer->id;
		te.event.timerEvent.data = timer->data;
		eventQueue_AppendEvent(te);
		timerQueue_DeleteTimer(timer->id);
    }
}

Event eventPool[1024];
unsigned eventPoolFlag[1024];
unsigned eventPoolInit = 0;

void eventPool_Init()
{
    unsigned i;
    for (i = 0; i < 1024; i++)
		eventPoolFlag[i] = 0;
    eventPoolInit = 1;
}

Event *eventPool_AllocEvent()
{
    unsigned i;
    if (!eventPoolInit)
		eventPool_Init();
    for (i = 0; i < 1024; i++) {
		if (eventPoolFlag[i] == 0) {
			eventPoolFlag[i] = 1;
			return &eventPool[i];
		}
    }
    strPrint("Event Pool Full\n"); // XXX
    return 0;
}

void eventPool_FreeEvent(Event *event)
{
    unsigned i;
    if (!eventPoolInit)
		eventPool_Init();
    for (i = 0; i < 1024; i++) {
		if (&eventPool[i] == event) {
			eventPoolFlag[i] = 0;
			return;
		}
    }
    strPrint("Erroneous Event, not from Event Pool\n");
}

Event *eventQueue[1024];
unsigned eventQueueCount = 0;
unsigned eventQueueWatermark = 0;

void eventQueue_AppendEvent(Event ev)
{
    if (eventQueueCount > 1023) {
		strPrint("Event Queue Full, ignoring event\n"); // XXX
		return;
    } else {
		Event *event = eventPool_AllocEvent();
		if (event) {
			*event = ev;
			eventQueue[eventQueueCount] = event;
			eventQueueCount++;
//START_PROFILE
			if (eventQueueCount > eventQueueWatermark)
				eventQueueWatermark = eventQueueCount;
//END_PROFILE
		}
    }
}

Event eventQueue_TakeEvent(void)
{
    Event event;
    if (!eventQueueCount) {
		strPrint("Event Queue Empty, you mustn't call this if it is empty\n");
    } else {
		Event *ev = eventQueue[0];
		unsigned i;
		for (i = 1; i <= eventQueueCount; i++)
			eventQueue[i - 1] = eventQueue[i];
		eventQueueCount--;
		event = *ev;
		eventPool_FreeEvent(ev);
    }
    return event;
}

extern int keyFd;

// TODO: This is a hack to move these out of the function
// I have no idea why it doesn't work putting them in the function
fd_set rfds, wfds, xfds;

void eventQueue_getEvents()
{
    while (!eventQueueCount) {
		unsigned highestFd = 0, ret;
		Timer *timer = timerQueue_FindNextTimer();
		timeval timeout, timeOfDay;
		Event *ev;
		int foundTimeout = (timer) ? 0 : 1;

		FD_ZERO(&rfds);
		FD_ZERO(&wfds);
		FD_ZERO(&xfds);
		FD_SET(keyFd, &rfds);
		highestFd = keyFd;

        getTimeOfDay(&timeOfDay);

/*

		int day, month, year, hour, min, sec;
		unsigned long val = timeOfDay.tv_sec;
		sec = val % 60;
		val /= 60;
		min = val % 60;
		val /= 60;
		hour = val % 24;
		val /= 24;
		day = val % 30;     // ### just quick hack
		val /= 30;
		month = val % 12;   // ### just quick hack
		val /= 12;
		year = val + 1970;

		// not real day/month/year, also no timezone adjustment in time 
		strPrintf("Date/Time:  %i/%i/%i %i:%i:%i\n", day,month,year, hour,min,sec);
*/


/*
XXX could record here the latency of dispatch between timeOfDay and globalTimeOfDay
*/

		while (!foundTimeout) {
			timeout.tv_sec = timer->next.tv_sec - timeOfDay.tv_sec;
			timeout.tv_usec = timer->next.tv_usec - timeOfDay.tv_usec;
			while (timeout.tv_usec < 0) {
				timeout.tv_usec += 1000000;
				timeout.tv_sec--;
			}
			if (timeout.tv_sec < 0) {
//				strPrint("Expired timeout, firing ");
//				if (timer->data)
//				    strPrint((char*)timer->data);
//				strPrint(" timer\n");
				fireExpiredTimer(timer);
				fireTimer(timer);
				timer = timerQueue_FindNextTimer();
			    if (!timer)
				  foundTimeout = 1;
			} else {
				foundTimeout = 1;
			}
		}

//		strPrint("Calling select\n");
		ret = sysSelect(highestFd + 1, &rfds, &wfds, &xfds, (timer)?&timeout:0);
//		strPrint("select returned\n");

        getTimeOfDay(&globalTimeOfDay);

		if (ret) {
			if (FD_ISSET(keyFd, &rfds)) {
			readKeyData();
			}
		} else {
//	    	strPrint("Timeout, firing ");
//	    	if (timer->data)
//			strPrint((char*)timer->data);
//	    	strPrint(" timer\n");
			fireTimer(timer);
		}

// #define LESS_SELECTS_AND_LONGER_HOLD_OF_CPU_TIME
#ifdef LESS_SELECTS_AND_LONGER_HOLD_OF_CPU_TIME
		timer = timerQueue_FindNextTimer();
		while (timer) {
			timeout.tv_sec = timer->next.tv_sec - globalTimeOfDay.tv_sec;
			timeout.tv_usec = timer->next.tv_usec - globalTimeOfDay.tv_usec;
			while (timeout.tv_usec < 0) {
				timeout.tv_usec += 1000000;
				timeout.tv_sec--;
			}
			if (timeout.tv_sec < 0) {
				fireTimer(timer);
				timer = timerQueue_FindNextTimer();
			} else {
				timer = 0;
			}
		}
#endif

    }
}

Event getNextEvent()
{
    eventQueue_getEvents();
    return eventQueue_TakeEvent();
}


