//////////////////////////////////////////////////////////////////////////////////////////
//
//  FILE        :  Asura_Time_System.cpp
//
//  CREATED     :  Adam A. Brown  (9.1.2004)
//
//  Copyright (C) 2004 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////////////////


#include "Core/Asura.h"

// This file's header.
#include "Core/Asura_Time_System.h"

#include "Core/Asura_String.h"

#include "Debugging/Asura_Assert.h"

//------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
//
// Asura_Time_System
//
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------

////////////////////////////////////////
// statics
////////////////////////////////////////
int Asura_Time_System::s_aiDaysInMonthsNormal[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int Asura_Time_System::s_aiDaysInMonthsLeap[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
Asura_Time Asura_Time_System::s_xTime;
Asura_UnicodeChar Asura_Time_System::s_wszDateBuffer[64];
Asura_UnicodeChar Asura_Time_System::s_wszDateAndTimeBuffer[64];
Asura_UnicodeChar Asura_Time_System::s_wszTimeBuffer[16];


////////////////////////////////////////////////////////////////////////////////
//
// GetCurrentDateAndTime
//
////////////////////////////////////////////////////////////////////////////////
const Asura_Time& Asura_Time_System::GetCurrentDateAndTime()
{
	Platform_GetCurrentDateAndTime();

	return s_xTime;
}


////////////////////////////////////////////////////////////////////////////////
//
// GetTimeString
//
////////////////////////////////////////////////////////////////////////////////
const Asura_UnicodeChar* Asura_Time_System::GetTimeString(const Asura_Time& xTime)
{
	ASURA_TIME_FORMAT eTimeFormat = Project_GetTimeFormat();

	switch (eTimeFormat)
	{
		case ASURA_24_HOUR:
		{
			Asura_PrintfToBfr(s_wszTimeBuffer, L"%.2d:%.2d", 16, true, xTime.m_ucHour, xTime.m_ucMinute);
			break;
		}

		case ASURA_12_HOUR:
		{
			//  AAB:  (12/01/2004 11:57:42):  am and pm shouldn't need localising - see comment in header.
			Asura_PrintfToBfr(s_wszTimeBuffer, L"%d:%.2d%s", 16, true, ((xTime.m_ucHour + 11) % 12)+1, xTime.m_ucMinute, ((xTime.m_ucHour / 12) ? L"pm": L"am"));
			break;
		}

		default:
		{
			Asura_Assert(false, "Unrecognised time format");
			break;
		}
	}

	return s_wszTimeBuffer;
}


////////////////////////////////////////////////////////////////////////////////
//
// GetDateString
//
////////////////////////////////////////////////////////////////////////////////
const Asura_UnicodeChar* Asura_Time_System::GetDateString(const Asura_Time &xTime, ASURA_DATE_STRING_FORMAT eStringFormat)
{
	ASURA_DATE_FORMAT eFormat = Project_GetDateFormat();

	switch (eStringFormat)
	{
		//  AAB:  (12/01/2004 12:33:53):  eg: 12/01/2004
		case ASURA_DATE_MINIMAL:
		{
			switch (eFormat)
			{
				case ASURA_DATE_DDMMYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%.2d/%.2d/%.2d", 64, true, xTime.m_ucDayOfMonth+1, xTime.m_ucMonth+1, xTime.m_usYear%100);
					break;
				}

				case ASURA_DATE_DDMMYYYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%.2d/%.2d/%.4d", 64, true, xTime.m_ucDayOfMonth+1, xTime.m_ucMonth+1, xTime.m_usYear);
					break;
				}

				case ASURA_DATE_MMDDYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%.2d/%.2d/%.2d", 64, true, xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1, xTime.m_usYear%100);
					break;
				}

				case ASURA_DATE_MMDDYYYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%.2d/%.2d/%.4d", 64, true, xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1, xTime.m_usYear);
					break;
				}

				case ASURA_DATE_YYMMDD:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%.2d/%.2d/%.2d", 64, true, xTime.m_usYear%100, xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1);
					break;
				}

				case ASURA_DATE_YYYYMMDD:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%.4d/%.2d/%.2d", 64, true, xTime.m_usYear, xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1);
					break;
				}

				default:
				{
					Asura_Assert(false, "Unrecognised date format");
					break;
				}
			}

			break;
		}

		//  AAB:  (12/01/2004 12:34:17):  eg: Mon 12/01/2004
		case ASURA_DATE_SHORT:
		{
			switch (eFormat)
			{
				case ASURA_DATE_DDMMYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%s %.2d/%.2d/%.2d", 64, true, Project_GetDayOfWeekShortName(GetDayOfWeek(xTime)), xTime.m_ucDayOfMonth+1, xTime.m_ucMonth+1, xTime.m_usYear%100);
					break;
				}

				case ASURA_DATE_DDMMYYYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%s %.2d/%.2d/%.4d", 64, true, Project_GetDayOfWeekShortName(GetDayOfWeek(xTime)), xTime.m_ucDayOfMonth+1, xTime.m_ucMonth+1, xTime.m_usYear);
					break;
				}

				case ASURA_DATE_MMDDYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%s %.2d/%.2d/%.2d", 64, true, Project_GetDayOfWeekShortName(GetDayOfWeek(xTime)), xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1, xTime.m_usYear%100);
					break;
				}

				case ASURA_DATE_MMDDYYYY:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%s %.2d/%.2d/%.4d", 64, true, Project_GetDayOfWeekShortName(GetDayOfWeek(xTime)), xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1, xTime.m_usYear);
					break;
				}

				case ASURA_DATE_YYMMDD:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%s %.2d/%.2d/%.2d", 64, true, Project_GetDayOfWeekShortName(GetDayOfWeek(xTime)), xTime.m_usYear%100, xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1);
					break;
				}

				case ASURA_DATE_YYYYMMDD:
				{
					Asura_PrintfToBfr(s_wszDateBuffer, L"%s %.4d/%.2d/%.2d", 64, true, Project_GetDayOfWeekShortName(GetDayOfWeek(xTime)), xTime.m_usYear, xTime.m_ucMonth+1, xTime.m_ucDayOfMonth+1);
					break;
				}

				default:
				{
					Asura_Assert(false, "Unrecognised date format");
					break;
				}
			}

			break;
		}

		//  AAB:  (12/01/2004 12:34:54):  eg: Monday 12 January 2004
		case ASURA_DATE_LONG:
		{
			Asura_PrintfToBfr(s_wszDateBuffer, L"%s %.2d %s, %.4d", 64, true, Project_GetDayOfWeekName(static_cast<int>(GetDayOfWeek(xTime))), xTime.m_ucDayOfMonth+1, Project_GetMonthName(static_cast<int>(xTime.m_ucMonth)), xTime.m_usYear);

			break;
		}

		default:
		{
			Asura_Assert(false, "Unrecognised date string format");
			break;
		}
	}

	return s_wszDateBuffer;
}


////////////////////////////////////////////////////////////////////////////////
//
// GetDateAndTimeString
//
////////////////////////////////////////////////////////////////////////////////
const Asura_UnicodeChar* Asura_Time_System::GetDateAndTimeString(const Asura_Time& xTime, ASURA_DATE_STRING_FORMAT eStringFormat)
{
	Asura_PrintfToBfr(s_wszDateAndTimeBuffer, L"%s %s", 64, true, GetTimeString(xTime), GetDateString(xTime, eStringFormat));

	return s_wszDateAndTimeBuffer;
}


////////////////////////////////////////////////////////////////////////////////
//
// GetCurrnetTimeString
//
////////////////////////////////////////////////////////////////////////////////
const Asura_UnicodeChar* Asura_Time_System::GetCurrentTimeString()
{
	return GetTimeString(GetCurrentDateAndTime());
}


////////////////////////////////////////////////////////////////////////////////
//
// GetCurrentDateString
//
////////////////////////////////////////////////////////////////////////////////
const Asura_UnicodeChar* Asura_Time_System::GetCurrentDateString(ASURA_DATE_STRING_FORMAT eStringFormat)
{
	return GetDateString(GetCurrentDateAndTime(), eStringFormat);
}


////////////////////////////////////////////////////////////////////////////////
//
// GetCurrentDateAndTimeString
//
////////////////////////////////////////////////////////////////////////////////
const Asura_UnicodeChar* Asura_Time_System::GetCurrentDateAndTimeString(ASURA_DATE_STRING_FORMAT eStringFormat)
{
	return GetDateAndTimeString(GetCurrentDateAndTime(), eStringFormat);
}



////////////////////////////////////////////////////////////////////////////////
//
// Internal Functions
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//
// GetNextLeapYear
//
////////////////////////////////////////////////////////////////////////////////
int Asura_Time_System::GetNextLeapYear(const Asura_Time &xTime)
{
	unsigned short usYear = xTime.m_usYear;
	bool bBefore29th = false;

	//Are we before the 29th of February
	if(xTime.m_ucMonth < 1 || (xTime.m_ucMonth == 1 && xTime.m_ucDayOfMonth < 29)) bBefore29th = true;

	//If we are before the 29th, we need to advance until exactly the next leap year
	//Otherwise, we need to advance to the next leap year which is after the current year.
	if(!bBefore29th) usYear++;

	while(!IsLeapYear(usYear)) usYear++;
	
	return static_cast<int>(usYear);
}


////////////////////////////////////////////////////////////////////////////////
//
// GetPrevLeapYear
//
////////////////////////////////////////////////////////////////////////////////
int Asura_Time_System::GetPrevLeapYear(const Asura_Time& xTime)
{
	unsigned short usYear = xTime.m_usYear;
	bool bBefore29th = false;

	if(xTime.m_ucMonth < 1 || (xTime.m_ucMonth == 1 && xTime.m_ucDayOfMonth < 29)) bBefore29th = true;

	//If we are before the 29th, we need to go back to the leap year before the
	//current year. Otherwise we need to go back to the previous year inclusive.
	if(bBefore29th) usYear--;

	while(!IsLeapYear(usYear)) usYear--;

	return static_cast<int>(usYear);
}


////////////////////////////////////////////////////////////////////////////////
//
// IsLeapYear
//
////////////////////////////////////////////////////////////////////////////////
bool Asura_Time_System::IsLeapYear(unsigned short usYear)
{
	bool bDiv4 = ((usYear % 4) == 0);
	bool bDiv100 = ((usYear % 100) == 0);
	bool bDiv400 = ((usYear % 400) == 0);

	if(bDiv4 && (!bDiv100 || (bDiv100 && bDiv400))) return true;

	return false;
}


////////////////////////////////////////////////////////////////////////////////
//
// GetLeapYearNumber
//
////////////////////////////////////////////////////////////////////////////////
int Asura_Time_System::GetLeapYearNumber(unsigned short usYear)
{
	return static_cast<int>(usYear/4 - usYear/100 + usYear/400);
}


////////////////////////////////////////////////////////////////////////////////
//
// GetDaysSoFarInYear - If specified, this ignores the leap day, (used to prevent 
// double accounting of the 29th in GetDayNumber).
//
////////////////////////////////////////////////////////////////////////////////
int Asura_Time_System::GetDaysSoFarInYear(const Asura_Time& xTime,bool bIncludeLeap/*=true*/)
{
	int iMonth = static_cast<int>(xTime.m_ucMonth);
	int iDays = 0;

	if(iMonth > 11) iMonth = 11;

	if( bIncludeLeap && IsLeapYear(xTime.m_usYear) )
	{
		for(int i = 0; i < iMonth; i++)
		{
			iDays += s_aiDaysInMonthsLeap[i];
		}
	}
	else
	{
		for(int i = 0; i < iMonth; i++)
		{
			iDays += s_aiDaysInMonthsNormal[i];
		}
	}

	iDays += static_cast<int>(xTime.m_ucDayOfMonth);

	return iDays;
}


////////////////////////////////////////////////////////////////////////////////
//
// GetDayNumber
//
// returns number of days since 1/1/70
//
////////////////////////////////////////////////////////////////////////////////
int Asura_Time_System::GetDayNumber(const Asura_Time &xTime)
{
	//Use the ANSI reference date.
	static Asura_Time xRefTime(1970, 0, 0, 0, 0, 0);

	int iFirstLeap = GetLeapYearNumber(static_cast<unsigned short>(GetNextLeapYear(xRefTime)));
	int iLastLeap = GetLeapYearNumber(static_cast<unsigned short>(GetPrevLeapYear(xTime)));
	int iLeaps = iLastLeap - iFirstLeap + 1;

	int iYears = static_cast<int>(xTime.m_usYear) - static_cast<int>(xRefTime.m_usYear);

	int iDays = iYears * 365 + iLeaps + GetDaysSoFarInYear(xTime,false); // leap years already accounted for

	return iDays;
}


////////////////////////////////////////////////////////////////////////////////
//
// GetDayOfWeek
//
////////////////////////////////////////////////////////////////////////////////
int Asura_Time_System::GetDayOfWeek(const Asura_Time &xTime)
{
	//Reference for day-of-week: 1st Jan 2004 was a Thursday
	static Asura_Time xRefTime(2004, 0, 0, 0, 0, 0);
	int iRefDayNumber = GetDayNumber(xRefTime);

	int iDayNumber = GetDayNumber(xTime);

	if(iDayNumber > iRefDayNumber)
	{
		return (4 + iDayNumber - iRefDayNumber) % 7;
	}
	else
	{
		return (7 - (3 + iRefDayNumber - iDayNumber) % 7) % 7;
	}

}



////////////////////////////////////////////////////////////////////////////////
//
// GetTimeFromAsctimeDate
//
////////////////////////////////////////////////////////////////////////////////
bool Asura_Time_System::GetTimeFromAsctimeDate( const char* szString, Asura_Time& xOutput )
{
	// This stuff comes directly from http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html
	// and http://notabug.com/2002/coherent/man/_5F5Fdate.html. It should match the stuff you get out
	// of the __DATE__ macro.
	// Input string should be in the format "Mmm dd yyyy"
	static const char* mon_name[12] = 
	{
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	};

	int iToken = 0;
	char szTempString[256];
	Asura_PrintfToBfr( szTempString, szString, 256, true );

	char* szWorkString;
	
	szWorkString = strtok( szTempString, " " );
	while( szWorkString != NULL )
	{		
		if( iToken == 0 )
		{
			// month
			u_char ucMonth=0;
			for( ; ucMonth<12; ++ucMonth )
			{
				if( stricmp( szWorkString, mon_name[ucMonth] ) == 0 )
				{
					xOutput.m_ucMonth = ucMonth;
					break;
				}
			}

			if( ucMonth >= 12 ) return false; // month failed
		}
		else
		{
			// day/year
			int iTemp = 0;
			if( !Asura_StringUtil::GetIntValue( szWorkString, &iTemp ) )
			{
				// failed to get value from string
				return false;
			}

			if( iToken == 1 )
			{
				xOutput.m_ucDayOfMonth = static_cast<u_char> (iTemp);
			}
			else if( iToken == 2 ) 
			{
				xOutput.m_usYear = static_cast<u_short> (iTemp);
			}
			else
			{
				// invalid token number
				return false;
			}
		}		
		
		iToken++;		
		szWorkString = strtok( NULL, " " );
	}	

	return true;
}



////////////////////////////////////////////////////////////////////////////////
//
// GetDaysSince
//
////////////////////////////////////////////////////////////////////////////////
u_int Asura_Time_System::GetDaysSince( const Asura_Time& xEndTime, const Asura_Time& xStartTime )
{
	Asura_Assert( xEndTime > xStartTime, "Start time is before current time!" );
	if( (xStartTime > xEndTime) || (xStartTime == xEndTime) ) return 0;

	u_int uDaysSince = 0;

	u_short usYearsSince = xEndTime.m_usYear - xStartTime.m_usYear;
	if( usYearsSince > 0 )
	{
		// count the days in the full years since the start time
		for( u_short usYear=0; usYear<usYearsSince; ++usYear )
		{		
			uDaysSince += IsLeapYear( xStartTime.m_usYear + usYear ) ? 366 : 365;
		}

		// add the number of days since the start of this year and deduct the number of days from the beginning of the first year
		uDaysSince += Asura_Time_System::GetDaysSoFarInYear(xEndTime) - Asura_Time_System::GetDaysSoFarInYear(xStartTime);
	}
	else
	{
		// just difference the days
		uDaysSince += Asura_Time_System::GetDaysSoFarInYear(xEndTime) - Asura_Time_System::GetDaysSoFarInYear(xStartTime);
	}
	
	return uDaysSince;
}


//------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
//
// Asura_Time
//
////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
//
// ConvertUTCToLocalTime
//
////////////////////////////////////////////////////////////////////////////////
void Asura_Time::ConvertUTCToLocalTime()
{
	// UTC = local time + bias
	// use GetDayNumber which is an internal function that returns num days since
	// 1/1/1970.
	int iBias = Asura_Time_System::GetTimeZoneBias();
	int iDaysSinceBaseTime = Asura_Time_System::GetDayNumber(*this);
	int iHours = iDaysSinceBaseTime * 24 + m_ucHour;
	int iMinutes = iHours * 60 + m_ucMinute - iBias;
	int iSecondsSinceBaseTime = iMinutes * 60;
	FillFromLibCTime(iSecondsSinceBaseTime);
}
