//Copyright Paul Reiche, Fred Ford. 1992-2002

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include "gameev.h"
#include "globdata.h"
#include "sis.h"
		// for DrawStatusMessage()
#include "setup.h"
#include "libs/compiler.h"
#include "libs/gfxlib.h"
#include "libs/tasklib.h"
#include "libs/threadlib.h"
#include "libs/log.h"
#include "libs/misc.h"

// the running of the game-clock is based on game framerates
// *not* on the system (or translated) timer
// and is hard-coded to the original 24 fps
#define CLOCK_BASE_FRAMERATE 24

// WARNING: Most of clock functions are only meant to be called by the
//   Starcon2Main thread! If you need access from other threads, examine
//   the locking system!
// XXX: This mutex is only necessary because debugging functions
//   may access the clock and event data from a different thread
static Mutex clock_mutex;

static BOOLEAN
IsLeapYear (COUNT year)
{
	//     every 4th year      but not 100s          yet still 400s
	return (year & 3) == 0 && ((year % 100) != 0 || (year % 400) == 0);
}

/* month is 1-based: 1=Jan, 2=Feb, etc. */
static BYTE
DaysInMonth (COUNT month, COUNT year)
{
	static const BYTE days_in_month[12] =
	{
		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
	};

	if (month == 2 && IsLeapYear (year))
		return 29; /* February, leap year */

	return days_in_month[month - 1];
}

static void
nextClockDay (void)
{
	++GLOBAL (GameClock.day_index);
	if (GLOBAL (GameClock.day_index) > DaysInMonth (
			GLOBAL (GameClock.month_index),
			GLOBAL (GameClock.year_index)))
	{
		GLOBAL (GameClock.day_index) = 1;
		++GLOBAL (GameClock.month_index);
		if (GLOBAL (GameClock.month_index) > 12)
		{
			GLOBAL (GameClock.month_index) = 1;
			++GLOBAL (GameClock.year_index);
		}
	}

	// update the date on screen
	DrawStatusMessage (NULL);
}

static void
processClockDayEvents (void)
{
	HEVENT hEvent;

	while ((hEvent = GetHeadEvent ()))
	{
		EVENT *EventPtr;

		LockEvent (hEvent, &EventPtr);

		if (GLOBAL (GameClock.day_index) != EventPtr->day_index
				|| GLOBAL (GameClock.month_index) != EventPtr->month_index
				|| GLOBAL (GameClock.year_index) != EventPtr->year_index)
		{
			UnlockEvent (hEvent);
			break;
		}
		RemoveEvent (hEvent);
		EventHandler (EventPtr->func_index);

		UnlockEvent (hEvent);
		FreeEvent (hEvent);
	}
}

BOOLEAN
InitGameClock (void)
{
	if (!InitQueue (&GLOBAL (GameClock.event_q), NUM_EVENTS, sizeof (EVENT)))
		return (FALSE);
	clock_mutex = CreateMutex ("Clock Mutex", SYNC_CLASS_TOPLEVEL);
	GLOBAL (GameClock.month_index) = 2;
	GLOBAL (GameClock.day_index) = 17;
	GLOBAL (GameClock.year_index) = START_YEAR; /* Feb 17, START_YEAR */
	GLOBAL (GameClock.tick_count) = 0;
	GLOBAL (GameClock.day_in_ticks) = 0;

	return (TRUE);
}

BOOLEAN
UninitGameClock (void)
{
	DestroyMutex (clock_mutex);
	clock_mutex = NULL;

	UninitQueue (&GLOBAL (GameClock.event_q));

	return (TRUE);
}

// For debugging use only
void
LockGameClock (void)
{
	// Block the GameClockTick() for executing
	if (clock_mutex)
		LockMutex (clock_mutex);
}

// For debugging use only
void
UnlockGameClock (void)
{
	if (clock_mutex)
		UnlockMutex (clock_mutex);
}

// For debugging use only
BOOLEAN
GameClockRunning (void)
{
	SIZE day_in_ticks;

	if (!clock_mutex)
		return FALSE;

	LockMutex (clock_mutex);
	day_in_ticks = GLOBAL (GameClock.day_in_ticks);
	UnlockMutex (clock_mutex);
	
	return day_in_ticks != 0;
}

void
SetGameClockRate (COUNT seconds_per_day)
{
	SIZE new_day_in_ticks, new_tick_count;

	new_day_in_ticks = (SIZE)(seconds_per_day * CLOCK_BASE_FRAMERATE);
	if (GLOBAL (GameClock.day_in_ticks) == 0)
		new_tick_count = new_day_in_ticks;
	else if (GLOBAL (GameClock.tick_count) <= 0)
		new_tick_count = 0;
	else if ((new_tick_count = (SIZE)((DWORD)GLOBAL (GameClock.tick_count)
			* new_day_in_ticks / GLOBAL (GameClock.day_in_ticks))) == 0)
		new_tick_count = 1;
	GLOBAL (GameClock.day_in_ticks) = new_day_in_ticks;
	GLOBAL (GameClock.tick_count) = new_tick_count;
}

BOOLEAN
ValidateEvent (EVENT_TYPE type, COUNT *pmonth_index, COUNT *pday_index,
		COUNT *pyear_index)
{
	COUNT month_index, day_index, year_index;

	month_index = *pmonth_index;
	day_index = *pday_index;
	year_index = *pyear_index;
	if (type == RELATIVE_EVENT)
	{
		month_index += GLOBAL (GameClock.month_index) - 1;
		year_index += GLOBAL (GameClock.year_index) + (month_index / 12);
		month_index = (month_index % 12) + 1;

		day_index += GLOBAL (GameClock.day_index);
		while (day_index > DaysInMonth (month_index, year_index))
		{
			day_index -= DaysInMonth (month_index, year_index);
			if (++month_index > 12)
			{
				month_index = 1;
				++year_index;
			}
		}

		*pmonth_index = month_index;
		*pday_index = day_index;
		*pyear_index = year_index;
	}

	// translation: return (BOOLEAN) !(date < GLOBAL (Gameclock.date));
	return (BOOLEAN) (!(year_index < GLOBAL (GameClock.year_index)
			|| (year_index == GLOBAL (GameClock.year_index)
			&& (month_index < GLOBAL (GameClock.month_index)
			|| (month_index == GLOBAL (GameClock.month_index)
			&& day_index < GLOBAL (GameClock.day_index))))));
}

HEVENT
AddEvent (EVENT_TYPE type, COUNT month_index, COUNT day_index, COUNT
		year_index, BYTE func_index)
{
	HEVENT hNewEvent;

	if (type == RELATIVE_EVENT
			&& month_index == 0
			&& day_index == 0
			&& year_index == 0)
		EventHandler (func_index);
	else if (ValidateEvent (type, &month_index, &day_index, &year_index)
			&& (hNewEvent = AllocEvent ()))
	{
		EVENT *EventPtr;

		LockEvent (hNewEvent, &EventPtr);
		EventPtr->day_index = (BYTE)day_index;
		EventPtr->month_index = (BYTE)month_index;
		EventPtr->year_index = year_index;
		EventPtr->func_index = func_index;
		UnlockEvent (hNewEvent);

		{
			HEVENT hEvent, hSuccEvent;
			for (hEvent = GetHeadEvent (); hEvent != 0; hEvent = hSuccEvent)
			{
				LockEvent (hEvent, &EventPtr);
				if (year_index < EventPtr->year_index
						|| (year_index == EventPtr->year_index
						&& (month_index < EventPtr->month_index
						|| (month_index == EventPtr->month_index
						&& day_index < EventPtr->day_index))))
				{
					UnlockEvent (hEvent);
					break;
				}

				hSuccEvent = GetSuccEvent (EventPtr);
				UnlockEvent (hEvent);
			}
			
			InsertEvent (hNewEvent, hEvent);
		}

		return (hNewEvent);
	}

	return (0);
}

void
GameClockTick (void)
{
	// XXX: This mutex is only necessary because debugging functions
	//   may access the clock and event data from a different thread
	LockMutex (clock_mutex);

	--GLOBAL (GameClock.tick_count);
	if (GLOBAL (GameClock.tick_count) <= 0)
	{	// next day -- move the calendar
		GLOBAL (GameClock.tick_count) = GLOBAL (GameClock.day_in_ticks);
		// Do not do anything until the clock is inited
		if (GLOBAL (GameClock.day_in_ticks) > 0)
		{	
			nextClockDay ();
			processClockDayEvents ();
		}
	}

	UnlockMutex (clock_mutex);
}

void
MoveGameClockDays (COUNT days)
{
	// XXX: This should theoretically hold the clock_mutex, but if
	//   someone manages to hit the debug button while this function
	//   runs, it's their own fault :-P
	
	for ( ; days > 0; --days)
	{
		nextClockDay ();
		processClockDayEvents ();
	}
	GLOBAL (GameClock.tick_count) = GLOBAL (GameClock.day_in_ticks);
}
