/*
UserinfoEx plugin for Miranda IM

Copyright:
� 2006-2010 DeathAxe, Yasnovidyashii, Merlin, K. Romanov, Kreol

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.
*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "svc_Timezone.h"

/******************************************************************************************
 * class MTime
 *
 *****************************************************************************************/

/*********************************************
 * construction
 *********************************************/

MTime::MTime() 
{
	ZeroDate();
}

MTime::MTime(SYSTEMTIME &st, const BOOLEAN bIsLocal)
{
	_SysTime = st;
	_isLocal = bIsLocal != FALSE;
}

MTime::MTime(FILETIME &ft, const BOOLEAN bIsLocal)
{
	ZeroDate();
	Set(ft, bIsLocal);
}

MTime::MTime(LARGE_INTEGER &li, const BOOLEAN bIsLocal)
{
	ZeroDate();
	Set(li, bIsLocal);
}

MTime::MTime(DWORD dwStamp)
{
	ZeroDate();
	FromStampAsUTC(dwStamp);
}

MTime::MTime(const MTime& mtime)
{
	Set(mtime);
}

VOID	MTime::ZeroDate() 
{
	_isLocal = FALSE;
	ZeroMemory(&_SysTime, sizeof(_SysTime));
}

/*********************************************
 * validation / checks
 *********************************************/

BOOLEAN	MTime::IsValid() const
{
	return (
		_SysTime.wYear > 1600 &&
		_SysTime.wMonth > 0 && _SysTime.wMonth < 13 &&
		_SysTime.wDay > 0 && _SysTime.wDay <= DaysInMonth(_SysTime.wMonth) &&
		_SysTime.wHour < 25 && 
		_SysTime.wMinute < 60 && 
		_SysTime.wSecond < 60 );
}

BOOLEAN	MTime::IsLeapYear() const
{
	return (!(((_SysTime.wYear) % 4 != 0) || (((_SysTime.wYear) % 100 == 0) && ((_SysTime.wYear) % 400 != 0))));
}

LONG	MTime::Compare(const DWORD dwTimeStamp) const
{
	return (LONG)(TimeStamp() - dwTimeStamp);
}

/**
 * name:	Compare
 * desc:	compare a filetime with the value of current object and return difference as number of seconds
 * param:	ft	- FILETIME to compare with
 * return:	number of seconds the ft differs from the class value
 **/ 
LONG	MTime::Compare(const FILETIME &ft) const
{
	const FILETIME ft1 = FileTime();
	return (LONG)((*(__int64*)&ft1 - *(__int64*)&ft) / 10000000i64);
}

/**
 * name:	Compare
 * desc:	compare a systemtime with the value of current object and return difference as number of seconds it handles some strange, too.
 * param:	st	- SYSTEMTIME to compare with
 * return:	number of seconds the st differs from the class value
 **/ 
LONG	MTime::Compare(SYSTEMTIME st) const
{
	FILETIME ft2;

	//strange day-in-month thing
	if (st.wYear == 0) {
		if (Month() < st.wMonth) return -1;
		if (Month() > st.wMonth) return 1;

		MTime mtTmp(st, FALSE);

		mtTmp.Year(Year());
		mtTmp.Day(1);
		mtTmp.Set(mtTmp.FileTime(), FALSE);	//gets the day of week of the first of the month
		mtTmp.Day(1 + (7 + st.wDayOfWeek - mtTmp.DayOfWeek()) % 7);

		//last wDayOfWeek in month
		if (st.wDay == 5) {
			mtTmp.Day(mtTmp.Day() + 7 * 3);	//can't be less than 4 of that day in the month
			if (mtTmp.Day() + 7 <= mtTmp.DaysInMonth(st.wMonth - 1))
				mtTmp.Day(mtTmp.Day() + 7);
		}
		else
			mtTmp.Day(7 * (st.wDay - 1)); //nth of month

		ft2 = mtTmp.FileTime();
	}
	else {
		SystemTimeToFileTime(&st, &ft2);
	}
	return Compare(ft2);
}

/**
 * name:	Compare
 * desc:	compare a MTime with the value of current object and return difference as number of seconds it handles some strange, too.
 * param:	mt	- MTime to compare with
 * return:	number of seconds the st differs from the class value
 **/ 
LONG	MTime::Compare(const MTime &mt) const
{
	return Compare(mt.SystemTime());
}

/*********************************************
 * conversions
 *********************************************/

VOID	MTime::UTCToLocal()
{
	if (!IsLocal()) {
		TIME_ZONE_INFORMATION tzInfo;
		
		ZeroMemory(&tzInfo, sizeof(TIME_ZONE_INFORMATION));
		GetTimeZoneInformation(&tzInfo);
		UTCToTzSpecificLocal(&tzInfo);
	}
}

VOID	MTime::UTCToTzSpecificLocal(INT tzh)
{

	TIME_ZONE_INFORMATION tzInfo;

	if (IsLocal()) LocalToUTC();
	ZeroMemory(&tzInfo, sizeof(TIME_ZONE_INFORMATION));

	if (tzh > 24) tzh = 24;
	if (tzh < -24)tzh = -24;

	GetTimeZoneInformation(&tzInfo);
	tzInfo.Bias = tzh * 30i64;
	UTCToTzSpecificLocal(&tzInfo);
}

LONG	MTime::_Offset(TIME_ZONE_INFORMATION *tzi)
{
	LONG offset = tzi->Bias;
	// daylight saving times
	if (tzi->StandardDate.wMonth != 0) {
		if (tzi->DaylightDate.wMonth < tzi->StandardDate.wMonth) {
			//northern hemisphere
			if (Compare(tzi->DaylightDate) < 0 || Compare(tzi->StandardDate) > 0) 
				offset += tzi->StandardBias;
			else
				offset += tzi->DaylightBias;
		 }
		 else {
			//southern hemisphere
			if (Compare(tzi->StandardDate) < 0 || Compare(tzi->DaylightDate) > 0)
				offset += tzi->DaylightBias;
			else
				offset += tzi->StandardBias;
		 }
	}
	return offset;
}

VOID	MTime::UTCToTzSpecificLocal(TIME_ZONE_INFORMATION *tzi)
{
	LARGE_INTEGER liFiletime;

	// do not transform to local twice
	if (tzi && !_isLocal) {
		liFiletime = LargeInt();
		liFiletime.QuadPart -= _Offset(tzi) * 60 * 10000000i64;
		Set(liFiletime, TRUE);
	}
}

VOID	MTime::TzSpecificLocalToUTC(TIME_ZONE_INFORMATION *tzi)
{
	LARGE_INTEGER liFiletime;

	// do not transform to utc twice
	if (tzi && _isLocal) {
		liFiletime = LargeInt();
		liFiletime.QuadPart += _Offset(tzi) * 60 * 10000000i64;
		Set(liFiletime, TRUE);
	}
}

VOID	MTime::LocalToUTC()
{
	TIME_ZONE_INFORMATION tzInfo;
	
	GetTimeZoneInformation(&tzInfo);
	TzSpecificLocalToUTC(&tzInfo);
}

/*********************************************
 * return values
 *********************************************/

LARGE_INTEGER	MTime::LargeInt() const
{
	LARGE_INTEGER liFileTime = { 0 };

	SystemTimeToFileTime(&_SysTime, (LPFILETIME)&liFileTime);
	return liFileTime;
}

FILETIME		MTime::FileTime() const
{
	FILETIME ftFileTime;

	SystemTimeToFileTime(&_SysTime, &ftFileTime);
	return ftFileTime;
}

DWORD	MTime::TimeStamp() const
{
	LARGE_INTEGER li;

	if (IsLocal()) {
		MTime mt(*this);
		mt.LocalToUTC();
		li = mt.LargeInt();
	}
	else
		li = LargeInt();

	li.QuadPart /= 10000000i64;
	li.QuadPart -= 11644473600i64;

	if (li.QuadPart < 0)
		return 0;

	return (DWORD)li.QuadPart;
}

WORD	MTime::DaysInMonth(const WORD &wMonth)	const
{
	static const WORD wDaysInMonth[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

	if (wMonth > 12) return 0;
	return (IsLeapYear() && wMonth == 2) ? wDaysInMonth[wMonth] + 1 : wDaysInMonth[wMonth];
}

WORD	MTime::DaysInYear(BOOLEAN bIgnoreLeap)	const
{
	return ((!bIgnoreLeap && IsLeapYear()) ? 366 : 365); 
};

WORD	MTime::DayOfYear()	const
{
	WORD daysResult = 0;
	WORD i;

	for (i = 0; i < _SysTime.wMonth; i++)
		daysResult += DaysInMonth(i);
	daysResult += _SysTime.wDay;
	return daysResult;
}

WORD	MTime::AdjustYear(const INT nDiffDays)
{
	const INT nDay = DayOfYear() + nDiffDays;

	if (nDay > DaysInYear())
		return _SysTime.wYear + 1;
	else if (nDay < 0)
		return _SysTime.wYear - 1;
	return _SysTime.wYear;
}

WORD	MTime::TimeFormat(LPTSTR ptszTimeFormat, WORD cchTimeFormat)
{
	if (!ptszTimeFormat || !cchTimeFormat)
		return 0;
	if ((cchTimeFormat = GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &_SysTime, NULL, ptszTimeFormat, cchTimeFormat)) == 0) {
		*ptszTimeFormat = 0;
		return 0;
	}
	return cchTimeFormat;
}

WORD	MTime::DateFormat(LPTSTR ptszTimeFormat, WORD cchTimeFormat)
{
	if (!ptszTimeFormat || !cchTimeFormat)
		return 0;
	if ((cchTimeFormat = GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &_SysTime, NULL, ptszTimeFormat, cchTimeFormat)) == 0) {
		*ptszTimeFormat = 0;
		return 0;
	}
	return cchTimeFormat;
}

WORD	MTime::DateFormatLong(LPTSTR ptszTimeFormat, WORD cchTimeFormat)
{
	if (!ptszTimeFormat || !cchTimeFormat)
		return 0;
	if ((cchTimeFormat = GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &_SysTime, NULL, ptszTimeFormat, cchTimeFormat)) == 0) {
		*ptszTimeFormat = 0;
		return 0;
	}
	return cchTimeFormat;
}

/*********************************************
 * set class value
 *********************************************/

VOID	MTime::FromStampAsUTC(const DWORD dwTimeStamp)
{
	LARGE_INTEGER li;
	li.QuadPart = (dwTimeStamp + 11644473600i64) * 10000000i64;
	Set(li, FALSE);
}

VOID	MTime::FromStampAsLocal(const DWORD dwTimeStamp)
{
	FromStampAsUTC(dwTimeStamp);
	UTCToLocal();
}

VOID	MTime::Set(LARGE_INTEGER liFileTime, const BOOLEAN bIsLocal)
{
	if (liFileTime.QuadPart < 0i64) liFileTime.QuadPart = 0;
	FileTimeToSystemTime((LPFILETIME)&liFileTime, &_SysTime);
	_isLocal = bIsLocal != FALSE;
}

VOID	MTime::Set(FILETIME &ftFileTime, const BOOLEAN bIsLocal)
{
	FileTimeToSystemTime(&ftFileTime, &_SysTime);
	_isLocal = bIsLocal != FALSE;
}

VOID	MTime::Set(const MTime &mt)
{
	Set(mt.SystemTime(), mt.IsLocal());
}

VOID	MTime::Set(SYSTEMTIME &st, const BOOLEAN bIsLocal)
{
	memcpy(&_SysTime, &st, sizeof(SYSTEMTIME));
	_isLocal = bIsLocal != FALSE;
}

VOID	MTime::GetTimeUTC()
{
	_isLocal = FALSE;
	::GetSystemTime(&_SysTime);
}

VOID	MTime::GetLocalTime()
{
	_isLocal = TRUE;
	::GetLocalTime(&_SysTime);
}

VOID	MTime::GetLocalTime(HANDLE hContact)
{
	TIME_ZONE_INFORMATION tzi;

	GetTimeUTC();

	if (!GetContactTimeZoneInformation((WPARAM)hContact, (LPARAM)&tzi)) {
		UTCToTzSpecificLocal(&tzi);
	}
}

/*********************************************
 * read and write time to miranda's database
 *********************************************/

INT		MTime::DBGetStamp  (HANDLE hContact, LPCSTR pszModule, LPCSTR pszSetting)
{
	DWORD dwTimeStamp;

	if (hContact == INVALID_HANDLE_VALUE ||
			pszModule == NULL || pszModule[0] == 0 ||
			pszSetting == NULL || pszSetting[0] == 0)
	{
		ZeroDate();
		return 1;
	}

	dwTimeStamp = DB::Setting::GetDWord(hContact, pszModule, pszSetting, 0);

	if (dwTimeStamp == 0) {
		ZeroDate();
		return 1;
	}
	FromStampAsUTC(dwTimeStamp);
	_isLocal = FALSE;
	return 0;
}

INT		MTime::DBWriteStamp(HANDLE hContact, LPCSTR pszModule, LPCSTR pszSetting)
{
	if (hContact == INVALID_HANDLE_VALUE ||
			pszModule == NULL || pszModule[0] == 0 ||
			pszSetting == NULL || pszSetting[0] == 0)
	{
		return 1;
	}
	return DB::Setting::WriteDWord(hContact, pszModule, pszSetting, TimeStamp());
}