/*-------------------------------------------------------------------------
   Time function copy right information

   Copyright (C) 2006-2009. Lin, Rongrong <woody@palmmicro.com>
                            Tang, Li      <tangli@palmmicro.com>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This software 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

   Alex Gradinar <Alex.Gradinar at cartel.md> added Europe Daylight Saving time

   Sebastien RAILLARD <sebastien.raillard at gmail.com> added French support

   Alfredo Sorbello <sorbello at antek.it> added Italian support

   Geisom <geisom at canall.com.br> added Brasil Portuguese support

-------------------------------------------------------------------------*/

#include "version.h"
#include "type.h"
#include "ar168.h"
#include "core.h"
#include "apps.h"

#include "bank1.h"

// swisstime.ethz.ch
// time.windows.com
// pool.ntp.org

#define MONTHS_PER_YEAR			12
#define DAYS_PER_WEEK			7
#define DAYS_PER_YEAR			365
#define DAYS_PER_LEAP_YEAR		366
#define MINUTES_PER_HOUR		60
#define	SECONDS_PER_MINUTE		60
#define	SECONDS_PER_HOUR		3600
#define SECONDS_PER_DAY			86400
#define SECONDS_PER_YEAR		(DAYS_PER_YEAR * SECONDS_PER_DAY)
#define SECONDS_PER_LEAP_YEAR	(DAYS_PER_LEAP_YEAR * SECONDS_PER_DAY)

#define HOURS_PER_DAY			24


#ifdef RES_BR // Brasil
#include "res\\br\\time.h"
#elif defined RES_CN && defined DISPLAY_DOT
#elif defined RES_ES // Español
#include "res\\es\\time.h"
#elif defined RES_FR // French
#include "res\\fr\\time.h"
#elif defined RES_IT // Italy
#include "res\\it\\time.h"
#elif defined RES_KR && defined DISPLAY_DOT // Korean
#include "res\\kr\\time.h"
#elif defined RES_RO // Romanian
#include "res\\ro\\time.h"
#elif defined RES_RU && defined DISPLAY_DOT // Russian
#include "res\\ru\\time.h"
#elif defined RES_TR // Turkish
#ifdef OEM_IP20
#include "res\\ip20\\tr\\time.h"
#else
#include "res\\tr\\time.h"
#endif
#elif defined RES_VN // Vietnam
#include "res\\vn\\time.h"
#else // Default = REG_US
#include "res\\us\\time.h"
#endif

const UCHAR _cMonthDay[MONTHS_PER_YEAR] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const UCHAR _cMonthDayLeap[MONTHS_PER_YEAR] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

#define SECONDS_PER_Q_HOUR	900
#define TIME_ZONE_MAX	76	
#define TIME_OFFSET_SECONDS		43200	// 48 * 900 = 43200

const UCHAR _cTimeZoneTable[TIME_ZONE_MAX] = {
0,  4,  8,  12, 16, 20, 20, 20, 24, 24, 24, 24, 28, 28, 28, 30, 32, 32, 32, 34, 36, 36, 36, 40, 
44, 44, 48, 48, 52, 52, 52, 52, 52, 56, 56, 56, 56, 56, 56, 60, 60, 60, 60, 62, 64, 64, 66, 68, 
68, 70, 71, 72, 72, 72, 74, 76, 76, 80, 80, 80, 80, 80, 84, 84, 84, 86, 86, 88, 88, 88, 88, 88, 
92, 96, 96, 100};

ULONG _TimeZoneOffset()
{
//	UCHAR iDayLightOffset;
	ULONG lTime;

//	iDayLightOffset = OptionsGetByte(OPT_DAY_LIGHT) ? 4 : 0;
//	iDayLightOffset += _cTimeZoneTable[OptionsGetByte(OPT_TIME_ZONE)];
//	lTime = mul_16x8(SECONDS_PER_Q_HOUR, iDayLightOffset);
	lTime = mul_16x8(SECONDS_PER_Q_HOUR, _cTimeZoneTable[OptionsGetByte(OPT_TIME_ZONE)]);
	lTime -= TIME_OFFSET_SECONDS;
	return lTime;
}

BOOLEAN _DayLightOffset(UCHAR iWeek, UCHAR iMonth, UCHAR iDay, UCHAR iHour)
{
	UCHAR iZone, iChangeHour;

	iZone = OptionsGetByte(OPT_TIME_ZONE);
	if (iZone == 4 || iZone == 5 || iZone == 9 || iZone == 13)
	{	//	4 time zones in continental USA
		//	Rule	US	2007	max	-	Mar	Sun>=8	2:00	1:00	D
		//	Rule	US	2007	max	-	Nov	Sun>=1	2:00	0	S
		if (iMonth < 3)		return FALSE;
		else if (iMonth == 3)
		{
			if (iDay < 8)	return FALSE;
			else if (iDay < 15)
			{
				if (!iWeek)	// Sun
				{
					if (iHour < 2)	return FALSE;
					return TRUE;
				}
				else
				{
					if (iDay - 8 < iWeek)	return FALSE;
					return TRUE;
				}
			}
			else	return TRUE;
		}
		else if (iMonth < 11)	return TRUE;
		else if (iMonth == 11)	
		{
			if (iDay < 8)
			{
				if (!iWeek)	// Sun
				{
					if (iHour < 2) return TRUE;
					return FALSE;
				}
				else
				{
					if (iDay - 1 < iWeek)	return TRUE;
					else	return FALSE;
				}
			}
			else	return FALSE;
		}
		else	return FALSE;
	}
	else if (iZone == 26 || iZone == 28 || iZone == 29 || iZone == 30 || iZone == 31 || iZone == 33 || iZone == 35 || iZone == 37 || iZone == 41)
	{	//	EU	 	on	Mar lastSun	1:00UTC	1:00
		//	EU	 	off	Oct lastSun	1:00UTC	0
		//	Zone 41
		//  Rule	Russia	1993	max	-	Mar	lastSun	 2:00s	1:00	S
		//	Rule	Russia	1996	max	-	Oct	lastSun	 2:00s	0	-
		if (iZone == 26)		iChangeHour = 0;
		else if (iZone <= 31)	iChangeHour = 1;
		else					iChangeHour = 2;

		if (iMonth < 3)		return FALSE;
		else if (iMonth == 3)
		{
			if (iDay < 25)	return FALSE;
			else
			{
				if (!iWeek)	// Sun
				{
					if (iHour < iChangeHour)	return FALSE;
					else	return TRUE;
				}
				else
				{
					if (iDay - 25 < iWeek)	return FALSE;
					else	return TRUE;
				}
			}
		}
		else if (iMonth < 10)	return TRUE;
		else if (iMonth == 10)
		{
			if (iDay < 25)	return TRUE;
			else
			{
				if (!iWeek)	// Sun
				{
					if (iHour < iChangeHour)	return TRUE;
					else	return FALSE;
				}
				else
				{
					if (iDay - 25 < iWeek)	return TRUE;
					else 	return FALSE;
				}
			}
		}
		else	return FALSE;
	}
	else if (iZone == 73)	// New Zealand
	{	//	Rule	NZ	2007	max	-	Sep	lastSun	2:00s	1:00	D
		//	Rule	NZ	2008	max	-	Apr	Sun>=1	2:00s	0	S
		if (iMonth < 4)		return TRUE;
		else if (iMonth == 4)	
		{
			if (iDay < 8)
			{
				if (!iWeek)	// Sun
				{
					if (iHour < 2) return TRUE;
					return FALSE;
				}
				else
				{
					if (iDay - 1 < iWeek)	return TRUE;
					else	return FALSE;
				}
			}
		}
		else if (iMonth < 9)	return FALSE;
		else if (iMonth == 9)
		{
			if (iDay < 24)	return FALSE;
			else
			{
				if (!iWeek)	// Sun
				{
					if (iHour < 2)	return FALSE;
					else	return TRUE;
				}
				else
				{
					if (iDay - 24 < iWeek)	return FALSE;
					else	return TRUE;
				}
			}
		}
		else return TRUE;
	}

	return FALSE;
}

BOOLEAN _IsLeapYear(USHORT sYear)
{
	if (!mod_16x8(sYear, 100))
	{
		sYear = div_16x8(sYear, 100);
		if (!mod_16x8(sYear, 4))
		{
			return TRUE;
		}
	}
	else
	{
		if (!mod_16x8(sYear, 4))
		{
			return TRUE;
		}
	}

	return FALSE;
}

void ConvertTime(ULONG lTime, TIME_T * ptm)
{
	USHORT sTotalDays, sVal, sYear;
	UCHAR iWeek, iMonth, iDay, iHour, iMinute, iSecond;
	PCHAR pCur;
	ULONG lVal;

	lTime += _TimeZoneOffset();

	sTotalDays = 0;
	sYear = 1900;
	iMonth = 1;
	iDay = 1;

	// check year
	do
	{
		if (_IsLeapYear(sYear))
		{
			lVal = SECONDS_PER_LEAP_YEAR;
			sVal = DAYS_PER_LEAP_YEAR;
		}
		else
		{
			lVal = SECONDS_PER_YEAR;
			sVal = DAYS_PER_YEAR;
		}
		if (lTime >= lVal)
		{
			lTime -= lVal;
			sTotalDays += sVal;
			sYear ++;
		}
		else
		{
			break;
		}
	} while (1);

	// check month
	if (_IsLeapYear(sYear))
	{
		pCur = _cMonthDayLeap;
	}
	else
	{
		pCur = _cMonthDay;
	}
	do
	{
		sVal = pCur[iMonth-1];		// days in month
		lVal = mul_32x8(SECONDS_PER_DAY, sVal);
		if (lTime >= lVal)
		{
			lTime -= lVal;
			sTotalDays += sVal;
			iMonth ++;
		}
		else
		{
			break;
		}
	} while (1);

	// check day, avoid div to 16 or 32 bit number
	do
	{
		if (lTime >= SECONDS_PER_DAY) 
		{
			lTime -= SECONDS_PER_DAY;
			iDay ++;
			sTotalDays ++;
		}
		else
		{
			break;
		}
	} while (1);

	iWeek = mod_16x8(sTotalDays + 1, DAYS_PER_WEEK);
	// check hour
	iHour = 0;
	do
	{
		if (lTime >= SECONDS_PER_HOUR)
		{
			lTime -= SECONDS_PER_HOUR;
			iHour ++;
		}
		else
		{
			break;
		}
	} while (1);
	sVal = (USHORT)lTime;
	iMinute = div_16x8(sVal, SECONDS_PER_MINUTE);
	iSecond = mod_16x8(sVal, SECONDS_PER_MINUTE);

	// Automatically adjust daylight
	if (OptionsGetByte(OPT_DAY_LIGHT))
	{
		if (_DayLightOffset(iWeek, iMonth, iDay, iHour))
		{
			iHour ++;
			if (iHour == HOURS_PER_DAY)
			{
				iHour = 0;
				if (iDay == pCur[iMonth-1])
				{
					iMonth ++;
					iDay = 1;
				}
				else
				{
					iDay ++;
				}

				if (iWeek == DAYS_PER_WEEK - 1)
				{
					iWeek = 0;
					UdpDebugVal(iWeek, 10);
				}
				else
				{
					iWeek ++;
				}
			}
		}
	}

	ptm->iSec = iSecond;
	ptm->iMin = iMinute;
	ptm->iHour = iHour;
	ptm->iMday = iDay;
	ptm->iMon = iMonth;
	ptm->sYear = sYear;
	ptm->iWday = iWeek;
}

const UCHAR _cHourMin[] = " 00:00";

void _BulidHourMin(PCHAR pBuf, TIME_T * ptm)
{
	strcpy(pBuf, _cHourMin);
	pBuf[1] += div_16x8(ptm->iHour, 10);
	pBuf[2] += mod_16x8(ptm->iHour, 10);
	pBuf[4] += div_16x8(ptm->iMin, 10);
	pBuf[5] += mod_16x8(ptm->iMin, 10);
}

// lTime is the seconds passed since 0h Jan 1, 1900
// this calculation is only correct for lTime elapsed since the very beginning moment of a year, that
// is the 00:00:00, Jan 1
void DisplayTime(TIME_T * ptm, UCHAR iLine)
{
	UCHAR pBuf[DISPLAY_MAX_CHAR+1];

#if defined RES_CN && defined DISPLAY_DOT
	pBuf[0] = div_16x8(ptm->iMon, 10) + '0';
	pBuf[1] = mod_16x8(ptm->iMon, 10) + '0';
	memcpy((PCHAR)(pBuf + 2), "ÔÂ", 2);
	pBuf[4] = div_16x8(ptm->iMday, 10) + '0';
	pBuf[5] = mod_16x8(ptm->iMday, 10) + '0';
	memcpy((PCHAR)(pBuf + 6), "ÈÕ", 2);
	_BulidHourMin((PCHAR)(pBuf + 8), ptm);
#elif defined RES_FR // French
	memcpy(pBuf, g_cWeekDay[ptm->iWday], 3);
	pBuf[3] = '-';
	pBuf[4] = div_16x8(ptm->iMday, 10) + '0';
	pBuf[5] = mod_16x8(ptm->iMday, 10) + '0';
	pBuf[6] = '-';
	memcpy((PCHAR)(pBuf + 7), g_cMonth[ptm->iMon - 1], 3);
	_BulidHourMin((PCHAR)(pBuf + 10), ptm);
#elif defined RES_IT || defined RES_RO
	memcpy(pBuf, g_cWeekDay[ptm->iWday], 3);
	pBuf[3] = ' ';
	pBuf[4] = div_16x8(ptm->iMday, 10) + '0';
	pBuf[5] = mod_16x8(ptm->iMday, 10) + '0';
	pBuf[6] = ' ';
	memcpy((PCHAR)(pBuf + 7), g_cMonth[ptm->iMon - 1], 3);
	_BulidHourMin((PCHAR)(pBuf + 10), ptm);
#elif defined RES_RU && defined DISPLAY_DOT // Russian
	memcpy(pBuf, g_cWeekDay[ptm->iWday], 2);
	pBuf[2] = ',';
	pBuf[3] = ' ';
	pBuf[4] = div_16x8(ptm->iMday, 10) + '0';
	pBuf[5] = mod_16x8(ptm->iMday, 10) + '0';
	pBuf[6] = ' ';
	memcpy((PCHAR)(pBuf + 7), g_cMonth[ptm->iMon - 1], 3);
	_BulidHourMin((PCHAR)(pBuf + 10), ptm);
#elif defined RES_TR // Türkiye
	memcpy(pBuf, g_cWeekDay[ptm->iWday], 3);
	pBuf[3] = ' ';
	pBuf[4] = div_16x8(ptm->iMday, 10) + '0';
	pBuf[5] = mod_16x8(ptm->iMday, 10) + '0';
	pBuf[6] = ' ';
	memcpy((PCHAR)(pBuf + 7), g_cMonth[ptm->iMon - 1], 3);
#ifdef OEM_IP20
	memset((PCHAR)(pBuf + 10), ' ', 4);
	_BulidHourMin((PCHAR)(pBuf + 14), ptm);
#else
	_BulidHourMin((PCHAR)(pBuf + 10), ptm);
#endif
#else // Default = REG_US
	memcpy(pBuf, g_cWeekDay[ptm->iWday], 3);
	pBuf[3] = ' ';
	memcpy((PCHAR)(pBuf + 4), g_cMonth[ptm->iMon - 1], 3);
	pBuf[7] = ' ';
	pBuf[8] = div_16x8(ptm->iMday, 10) + '0';
	pBuf[9] = mod_16x8(ptm->iMday, 10) + '0';
	_BulidHourMin((PCHAR)(pBuf + 10), ptm);
#endif

	LcdDisplay(pBuf, iLine);
	UdpDebugString(pBuf);
#ifdef SERIAL_UI
	// for UART test only
	SerialSend("LOOP", 4);
	SerialSendString(pBuf);
/*
	SerialSend("LCD ", 4);
	SerialSend("1", 1);
	SerialSendString(pBuf);
*/
#endif
}

const UCHAR _cTimeStart[] = "00:00:00";

// lTime is the seconds passed since 0h Jan 1, 1900
void DisplayCallTime(CALL_TIME * pct, UCHAR iLine)
{
	UCHAR pBuf[DISPLAY_MAX_CHAR];
	UCHAR iHour;
	PCHAR pCur;

	pCur = pBuf;
	iHour = pct->iHour;
	if (iHour >= 200)
	{
		pBuf[0] = '2';
		pCur ++;
		iHour -= 200;
	}
	else if (iHour >= 100)
	{
		pBuf[0] = '1';
		pCur ++;
		iHour -= 100;
	}
	strcpy(pCur, _cTimeStart);
	pCur[0] += div_16x8(iHour, 10);
	pCur[1] += mod_16x8(iHour, 10);
	pCur[3] += div_16x8(pct->iMin, 10);
	pCur[4] += mod_16x8(pct->iMin, 10);
	pCur[6] += div_16x8(pct->iSec, 10);
	pCur[7] += mod_16x8(pct->iSec, 10);

	LcdDisplay(pBuf, iLine);
}

#ifdef CALL_IAX2
ULONG CalcIax2Time(PCHAR pParam)
{
	USHORT s, sYear;
	UCHAR i, iMonth, iDay, iHour, iMinute, iSecond;
	ULONG lVal;
	PCHAR pCur;

	sYear = ((pParam[0] & 0xfe) >> 1) + 2000;
	iMonth = ((pParam[0] & 1) << 3) + ((pParam[1] & 0xe0) >> 5);
	iDay = pParam[1] & 0x1f;
	iHour = (pParam[2] & 0xf8) >> 3;
	iMinute = ((pParam[2] & 0x07) << 3) + ((pParam[3] & 0xe0) >> 5);
	iSecond = (pParam[3] & 0x1f) << 1;

	lVal = 0;
	for (s = 1900; s < sYear; s ++)
	{
		if (_IsLeapYear(s))
		{
			lVal += SECONDS_PER_LEAP_YEAR;
		}
		else
		{
			lVal += SECONDS_PER_YEAR;
		}
	}

	if (_IsLeapYear(sYear))
	{
		pCur = _cMonthDayLeap;
	}
	else
	{
		pCur = _cMonthDay;
	}
	for (i = 0; i < iMonth-1; i ++)
	{
		lVal += mul_32x8(SECONDS_PER_DAY, pCur[i]);
	}
	if (iDay > 1)
	{
		lVal += mul_32x8(SECONDS_PER_DAY, iDay-1);
	}
	if (iHour)
	{
		lVal += mul_16x8(SECONDS_PER_HOUR, iHour);
	}
	if (iMinute)
	{
		lVal += mul_8x8(SECONDS_PER_MINUTE, iMinute);
	}
	lVal += iSecond;
	lVal -= _TimeZoneOffset();

	return lVal;
}
#endif