// Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//


#include "agmdate.h"
#include "agmtlsproxy.h"
#include "agmpanic.h"

#include <tz.h>
#include <tzconverter.h>
#include <vtzrules.h>

const TInt16  KOffsetUnspecified = KMaxTInt16;
const TUint16 KTzIdFloating = 0x8000;
const TUint16 KTzIdUnspecified = 0x7fff;

const TUint32 KAgnNewCalTimeMask = (1 << 30);
const TUint32 KAgmTimeFloatingFlag = 0x01;

// TAgnCalendarTime //

EXPORT_C TAgnCalendarTime::TAgnCalendarTime() : 
	iTime(Time::NullTTime()), 
	iLocalOffsetInMinutes(KOffsetUnspecified), 
	iTzId(KTzIdUnspecified), 
	iTimeZoneAccessor(NULL)
	{
	}

EXPORT_C void TAgnCalendarTime::SetFloatingL(const TTime& aTime)
	{
	SetDateTimeL(aTime, ETrue, MAgnCalendarTimeMode::ELocal);
	}

EXPORT_C void TAgnCalendarTime::SetLocalL(const TTime& aTime)
	{
	SetDateTimeL(aTime, EFalse, MAgnCalendarTimeMode::ELocal);
	}
	
EXPORT_C void TAgnCalendarTime::SetUtcL(const TTime& aTime)
	{
	SetDateTimeL(aTime, EFalse, MAgnCalendarTimeMode::EUtc);
	}

void TAgnCalendarTime::SetNull()
	{
	iTime = Time::NullTTime();
	iLocalOffsetInMinutes = KOffsetUnspecified;
	}
	
EXPORT_C TTime TAgnCalendarTime::LocalL() const
	{
	if (TimeMode() == MAgnCalendarTimeMode::EFixedTimeZone)
		{
		return DateTimeL(MAgnCalendarTimeMode::EFixedLocal);
		}
	return DateTimeL(MAgnCalendarTimeMode::ELocal);
	}

EXPORT_C TTime TAgnCalendarTime::UtcL() const
	{
	return DateTimeL(MAgnCalendarTimeMode::EUtc);
	}

void TAgnCalendarTime::SetDateTimeL(const TTime& aTime, TBool aFloating, MAgnCalendarTimeMode::TFormat aFormat)
/** Set the time and time mode of the AgnCalendarTime
@internalAll

@param aTime The time to be stored, can be UTC if aFormat is EUtc, or local if aFormat is ELocal or EFixedLocal
@param aMode Reference to a MAgnCalendarTimeMode derived time mode class. This time mode must be retrieved from AgnDateTime or CAgnTlsProxy
@param aFormat The time format of aTime
*/
	{
	if (!AgnDateTime::IsValidAgendaTTime(aTime))
		{
		// If the time is outside the range between MinDate and MaxDate, don't 
		// convert it, but normalise it to the max or min datetimes as 
		// appropriate.
		if(aTime == Time::NullTTime())
			{
			iTime = aTime;	
			}
		else if(aTime <= AgnDateTime::MinDate())
			{
			iTime = AgnDateTime::MinDate();
			}
		else if(aTime >= AgnDateTime::MaxDate())
			{
			iTime = AgnDateTime::MaxDate();
			}
 		iLocalOffsetInMinutes = KOffsetUnspecified;
		}
	else
		{
		if ( ! aFloating)
			{
			if (aFormat == MAgnCalendarTimeMode::EUtc)
				{
				// If the time is UTC, just store it. The offset is unknown at this point.
				iTime = aTime;
				iLocalOffsetInMinutes = KOffsetUnspecified;
				}
			else
				{
				// If the time is local, convert it and store the offset as well as the time.
				TTime localTime(aTime);
				TTime utcTime(aTime);
				TimeZoneAccessor()->FixedTimeMode().ToL(aFormat, utcTime);
				//In order to ensure UTC time is still within the range of Max and Min time
				//after the conversion. Any UTC time that is not within the range is converted
				//to Max or Min time.
				if (utcTime <= AgnDateTime::MinDate())
					{
					localTime = utcTime = AgnDateTime::MinDate();
					}
				else if (utcTime >= AgnDateTime::MaxDate())
					{
					localTime = utcTime = AgnDateTime::MaxDate();
					}
				iTime = utcTime;
				StoreNewOffset(localTime);
				SetTzId(TimeZoneAccessor()->CurrentTzId());
				}
			SetFloatingFlag(EFalse);
			}
		else
			{
			// floating time
			if (aFormat == MAgnCalendarTimeMode::EUtc)
				{
				TTime utcTime(aTime);
				TTime localTime(aTime);
				TimeZoneAccessor()->FloatingTimeMode().ToL(aFormat, localTime);
				iTime = localTime;
				StoreNewOffset(utcTime);
				SetTzId(TimeZoneAccessor()->CurrentTzId());
				}
			else
				{
				iTime = aTime;
				iLocalOffsetInMinutes = KOffsetUnspecified;
				}
			SetFloatingFlag(ETrue);
			}
		}
	__ASSERT_DEBUG(IsValidTime(), User::Invariant());
	}

EXPORT_C TTime TAgnCalendarTime::DateTimeL(MAgnCalendarTimeMode::TFormat aFormat) const
	{
	if(iTime <= AgnDateTime::MinDate() || iTime >= AgnDateTime::MaxDate() || iTime == Time::NullTTime())
		{
		// If the time is outside the range between MinDate and MaxDate, don't convert it. 
		// If the time is to be returned in UTC, don't convert it. 
		return iTime;
		}

	if ((TimeMode() == MAgnCalendarTimeMode::EFixedUtc && aFormat == MAgnCalendarTimeMode::EUtc) ||
		(TimeMode() == MAgnCalendarTimeMode::EFloating && aFormat == MAgnCalendarTimeMode::ELocal) ||
		(TimeMode() == MAgnCalendarTimeMode::EFloating && aFormat == MAgnCalendarTimeMode::EFixedLocal) ||
		(TimeMode() == MAgnCalendarTimeMode::EFixedTimeZone && aFormat == MAgnCalendarTimeMode::EFixedLocal))
		{
		return iTime;
		}
	
	// In this case the time is to be converted
	TUint16 tzId = TimeZoneAccessor()->CurrentTzId(); // Check whether or not the time zone has changed. 
	
	// Note that each TAgnCalendarTime's offset is updated on demand, so there is no need to update all 
	// offsets when the local time zone changes.
	
	if (TimeMode() == MAgnCalendarTimeMode::EFixedUtc)
		{
		if (iLocalOffsetInMinutes == KOffsetUnspecified ||
			TzId() != tzId)
			{
			// If the offset is unknown or the time zone has changed, recalculate the offset.
			TTime newLocalTime(iTime);
			CalendarTimeMode()->FromL(aFormat, newLocalTime);
			StoreNewOffset(newLocalTime);
			SetTzId(tzId);
			}
		}
	else if (TimeMode() == MAgnCalendarTimeMode::EFloating)
		{
		if (iLocalOffsetInMinutes == KOffsetUnspecified ||
			TzId() != tzId)
			{
			// If the offset is unknown or the time zone has changed, recalculate the offset.
			TTime newUtcTime(iTime);
			CalendarTimeMode()->FromL(aFormat, newUtcTime);
			StoreNewOffset(newUtcTime);
			SetTzId(tzId);
			}
		}
	else // using rule time mode
		{
		User::Leave(KErrNotSupported);
		}
			
	return iTime + TTimeIntervalMinutes(iLocalOffsetInMinutes);
	}

	
void TAgnCalendarTime::StoreNewOffset(const TTime& aTime) const
	{
	TTimeIntervalMinutes mins;
	aTime.MinutesFrom(iTime, mins);
	iLocalOffsetInMinutes = mins.Int();
	}

CAgnTlsProxy* TAgnCalendarTime::TimeZoneAccessor() const
	{
	if (!iTimeZoneAccessor)
		{
		iTimeZoneAccessor = static_cast<CAgnTlsProxy*>(Dll::Tls());
		__ASSERT_ALWAYS(iTimeZoneAccessor, User::Invariant());
		}
	return iTimeZoneAccessor;
	}

EXPORT_C TBool TAgnCalendarTime::IsSet() const
	{
	return (iTime != Time::NullTTime());
	}


	
EXPORT_C TBool TAgnCalendarTime::operator==(const TAgnCalendarTime& aTime) const
	{
	if (TimeMode() == aTime.TimeMode() &&
		(TimeMode() == MAgnCalendarTimeMode::EFixedUtc || TimeMode() == MAgnCalendarTimeMode::EFloating))
		{
		// both times are either floating or fixed, no conversion needed
		return (iTime == aTime.iTime);
		}
	else
		{
		return (UtcL() == aTime.UtcL());
		}
	}



EXPORT_C TBool TAgnCalendarTime::operator!=(const TAgnCalendarTime& aTime) const
	{
	if (TimeMode() == aTime.TimeMode() &&
		(TimeMode() == MAgnCalendarTimeMode::EFixedUtc || TimeMode() == MAgnCalendarTimeMode::EFloating))
		{
		// both times are either floating or fixed, no conversion needed
		return (iTime != aTime.iTime);
		}
	else
		{
		return (UtcL() != aTime.UtcL());
		}
	}



EXPORT_C TBool TAgnCalendarTime::operator<(const TAgnCalendarTime& aTime) const
	{
	if (TimeMode() == aTime.TimeMode() &&
		(TimeMode() == MAgnCalendarTimeMode::EFixedUtc || TimeMode() == MAgnCalendarTimeMode::EFloating))
		{
		// both times are either floating or fixed, no conversion needed
		return (iTime < aTime.iTime);
		}
	else
		{
		return (UtcL() < aTime.UtcL());
		}
	}



EXPORT_C TBool TAgnCalendarTime::operator>(const TAgnCalendarTime& aTime) const
	{
	if (TimeMode() == aTime.TimeMode() &&
		(TimeMode() == MAgnCalendarTimeMode::EFixedUtc || TimeMode() == MAgnCalendarTimeMode::EFloating))
		{
		// both times are either floating or fixed, no conversion needed
		return (iTime > aTime.iTime);
		}
	else
		{
		return (UtcL() > aTime.UtcL());
		}
	}



EXPORT_C TBool TAgnCalendarTime::operator<=(const TAgnCalendarTime& aTime) const
	{
	if (TimeMode() == aTime.TimeMode() &&
		(TimeMode() == MAgnCalendarTimeMode::EFixedUtc || TimeMode() == MAgnCalendarTimeMode::EFloating))
		{
		// both times are either floating or fixed, no conversion needed
		return (iTime <= aTime.iTime);
		}
	else
		{
		return (UtcL() <= aTime.UtcL());
		}
	}



EXPORT_C TBool TAgnCalendarTime::operator>=(const TAgnCalendarTime& aTime) const
	{
	if (TimeMode() == aTime.TimeMode() &&
		(TimeMode() == MAgnCalendarTimeMode::EFixedUtc || TimeMode() == MAgnCalendarTimeMode::EFloating))
		{
		// both times are either floating or fixed, no conversion needed
		return (iTime >= aTime.iTime);
		}
	else
		{
		return (UtcL() >= aTime.UtcL());
		}
	}



EXPORT_C TBool TAgnCalendarTime::IsValidTime() const
	{
	TBool isNullTime(iTime == Time::NullTTime());
	TBool isWithinValidRange(iTime >= Time::MinTTime() && iTime <= Time::MaxTTime());
	return (isNullTime || isWithinValidRange);
	}

/*
In v9.1 time was stored as a TUint64 value of number of microseconds since midnight
January 1st, 0 AD nominal Gregorian. 

The TAgnCalendarTime count time is a valid if it lay between Midnight, January 1st 1900 and 
Midnight, December 31st 2100. It means TUint64 value should be between 0x00D504A2C672E000 and 0x00eb8d745a6fc000. 
Also NullTime (0x8000000000000000) is supported.

Internalize:
1) read low 32 bits
2) read high 32 bits
3) if 2nd msb bit of high = 1 then: (note the 2nd msb bit is used because the 1st msb bit is used by Time::NullTTime().
        - must be reading v9.2+ format (iTime values that are either agn null time or between agn min time to agn max time can never have this msb bit set to 1)
        - flip 2nd msb bit to 0 to create iTime later in step 4
        - read another 32 bits and if lsb = 1 set to floating mode else must be fixed mode (other 31 bits will be used for TzId in future)
        - read yet another 32 bits (future padding)
4) set iTime = TTime(MAKE_TINT64(high, low))


Externalize:
1) write low 32 bits of iTime,
2) mask the high 32 bits of iTime with (1<<30) to signify a v9.2+ time format
3) write masked high bits of iTime
4) write another 32 bits (lsb = 0 for fixed time mode, 1 for floating time mode)
5) write yet another 32 bits (value = 0 always for future use) 
*/
EXPORT_C void TAgnCalendarTime::InternalizeL(RReadStream& aStream)
	{
	TUint32 low = aStream.ReadUint32L();
	TUint32 high = aStream.ReadUint32L();

	if (high & KAgnNewCalTimeMask)
		{
		// reading v9.2+ format
		high = high & ~KAgnNewCalTimeMask;

		TUint32 attr = aStream.ReadUint32L(); //read attribute
		if (attr & KAgmTimeFloatingFlag)
			{
			SetFloatingFlag(ETrue);
			}
		else	
			{
			SetFloatingFlag(EFalse);
			}

		aStream.ReadUint32L(); //reserved 32 bits for future use
		}
	else
		{
		// for backcompatibility, v9.1 only support fix time mode
		iTzId = KTzIdUnspecified;
		SetFloatingFlag(EFalse);
		}

	iTime = TTime(MAKE_TINT64(high, low));  //old data format
	__ASSERT_ALWAYS(IsValidTime(), User::Leave(KErrCorrupt));
	}

void TAgnCalendarTime::ExternalizeL(RWriteStream& aStream) const
	{
	aStream << 	I64LOW(iTime.Int64());
	aStream << 	(I64HIGH(iTime.Int64()) | KAgnNewCalTimeMask);
	TUint32 attr(0);
	if (TimeMode() == MAgnCalendarTimeMode::EFloating)
		{
		attr |= KAgmTimeFloatingFlag;
		}
	
	aStream << attr;
	aStream.WriteUint32L(0); // reserved 32 bits for future use

	}

void TAgnCalendarTime::SetFloatingFlag(TBool aFloating) const
	{
	if (aFloating)
		{
		iTzId |= KTzIdFloating;
		}
	else
		{
		iTzId &= ~KTzIdFloating;
		}
	}

void TAgnCalendarTime::SetTzId(TUint16 aTzId) const
	{
	iTzId = aTzId | (iTzId & KTzIdFloating);
	}

TUint16 TAgnCalendarTime::TzId() const
	{
	return (iTzId & ~KTzIdFloating);
	}
	
const MAgnCalendarTimeMode* TAgnCalendarTime::CalendarTimeMode() const
	{
	if (iTzId & KTzIdFloating)
		{
		return &TimeZoneAccessor()->FloatingTimeMode();
		}
	else
		{
		// this is needed for alarm info reconsturction
		return &TimeZoneAccessor()->FixedTimeMode();
		}
	}
	
EXPORT_C MAgnCalendarTimeMode::TTimeMode TAgnCalendarTime::TimeMode() const
	{
	if (CalendarTimeMode())
		{
		return CalendarTimeMode()->TimeMode();
		}
	return MAgnCalendarTimeMode::EFixedUtc;    // default is fixed time mode if not set
	}

/*static*/ TInt TAgnCalendarTime::Compare(const TAgnCalendarTime& aLeft, const TAgnCalendarTime& aRight)
	{
	if (aLeft == aRight)
		{
		return 0;
		}
	else if (aLeft > aRight)
		{
		return 1;
		}
	return -1;
	}

/*static*/ void TAgnCalendarTime::InsertInOrderL(RArray<TAgnCalendarTime>& aTimeArray, const TAgnCalendarTime& aTimeToInsert)
	{
	TLinearOrder<TAgnCalendarTime> agnCalTimeOrder(TAgnCalendarTime::Compare);
	TInt err = aTimeArray.InsertInOrder(aTimeToInsert, agnCalTimeOrder);
	if (err != KErrAlreadyExists)
		{
		User::LeaveIfError(err);
		}
	}

/*static*/ TBool TAgnCalendarTime::CompareTimeArrays(const RArray<TAgnCalendarTime>* aLeft, const RArray<TAgnCalendarTime>* aRight)
	{
	if (aLeft == NULL)
		{
		if (aRight == NULL)
			{
			return ETrue;
			}
		return EFalse;
		}

	// aLeft is non-NULL
	if (aRight == NULL)
		{
		return EFalse;
		}
	
	if (aLeft->Count() != aRight->Count())
		{
		return EFalse;
		}
	
	const TInt KTimeCount = aLeft->Count();
	for (TInt i = 0; i < KTimeCount; ++i)
		{
		if ((*aLeft)[i] != (*aRight)[i])
			{
			return EFalse;
			}
		}
	return ETrue;
	}

/*static*/ void TAgnCalendarTime::InternalizeTimeArrayL(RArray<TAgnCalendarTime>& aArray, RReadStream& aStream)
	{
	aArray.Reset();
	const TInt KCount = aStream.ReadUint16L();
	
	TAgnCalendarTime time;
	for (TInt i = 0; i < KCount; ++i)
		{
		aStream >> time;
		aArray.AppendL(time);
		}
	}

/*static*/ void TAgnCalendarTime::ExternalizeTimeArrayL(RArray<TAgnCalendarTime>& aArray, RWriteStream& aStream)
	{
	const TInt KCount = aArray.Count();
	aStream.WriteUint16L(KCount);
	
	for (TInt i = 0; i < KCount; ++i)
		{
		aStream << aArray[i];
		}
	}

// TAgnCalendarFixedTimeMode //

TAgnCalendarFixedTimeMode::TAgnCalendarFixedTimeMode(CTzConverter& aTimeConverter) :
	iTimeConverter(aTimeConverter)
	{
	}

void TAgnCalendarFixedTimeMode::ToL(MAgnCalendarTimeMode::TFormat aFormat, TTime& aTime) const
	{
	switch (aFormat)
		{
		case ELocal: 
		case EFixedLocal: 
			{
			User::LeaveIfError(iTimeConverter.ConvertToUniversalTime(aTime));
			}
		case EUtc:
			{
			// do nothing - no conversion necessary
			break;
			}
		default: 
			{
			Panic(EAgmErrUnsupportedTimeMode);
			}
		};
	}

void TAgnCalendarFixedTimeMode::FromL(MAgnCalendarTimeMode::TFormat aFormat, TTime& aTime) const
	{
	switch (aFormat)
		{
		case ELocal: 
		case EFixedLocal: 
			{
			User::LeaveIfError(iTimeConverter.ConvertToLocalTime(aTime));
			break;
			}
		case EUtc:
			{
			// do nothing - no conversion necessary
			break;
			}
		default: 
			{
			Panic(EAgmErrUnsupportedTimeMode);
			}
		}
	}

MAgnCalendarTimeMode::TTimeMode TAgnCalendarFixedTimeMode::TimeMode() const
	{
	return MAgnCalendarTimeMode::EFixedUtc;
	}

// TAgnCalendarFixedUsingRulesTimeMode //

TAgnCalendarFixedUsingRulesTimeMode::TAgnCalendarFixedUsingRulesTimeMode(CTzRules& aTimeZoneRules, CTzConverter& aTimeConverter) :
	iTimeZoneRules(aTimeZoneRules), iTimeConverter(aTimeConverter)
	{
	}

const CTzRules& TAgnCalendarFixedUsingRulesTimeMode::TzZone() const
	{
	return iTimeZoneRules;
	}

void TAgnCalendarFixedUsingRulesTimeMode::ToL(MAgnCalendarTimeMode::TFormat aFormat, TTime& aTime) const
	{
	switch (aFormat)
		{
		case ELocal:
			{
			User::LeaveIfError(iTimeConverter.ConvertToLocalTime(aTime));
			break;
			}
		case EUtc:
			{
			iTimeZoneRules.ConvertToLocalL(aTime);
			break;
			}
		case EFixedLocal: 
			{
			// do nothing - no conversion necessary
			break;
			}
		default: 
			{
			Panic(EAgmErrUnsupportedTimeMode);
			}
		};
	}

void TAgnCalendarFixedUsingRulesTimeMode::FromL(MAgnCalendarTimeMode::TFormat aFormat, TTime& aTime) const
	{
	switch (aFormat)
		{
		case EUtc: 
			{
			iTimeZoneRules.ConvertToUtcL(aTime);
			break;
			}
		case ELocal:
			{
			User::LeaveIfError(iTimeConverter.ConvertToUniversalTime(aTime));
			break;
			}
		case EFixedLocal: 
			{
			// do nothing - no conversion necessary
			break;
			}
		default: 
			{
			Panic(EAgmErrUnsupportedTimeMode);
			}
		}
	}

MAgnCalendarTimeMode::TTimeMode TAgnCalendarFixedUsingRulesTimeMode::TimeMode() const
	{
	return MAgnCalendarTimeMode::EFixedTimeZone;
	}

// TAgnCalendarFloatingTimeMode //

TAgnCalendarFloatingTimeMode::TAgnCalendarFloatingTimeMode(CTzConverter& aTimeConverter) :
	iTimeConverter(aTimeConverter)
	{
	}

void TAgnCalendarFloatingTimeMode::ToL(MAgnCalendarTimeMode::TFormat aFormat, TTime& aTime) const
	{
	switch (aFormat)
		{
		case ELocal: 
		case EFixedLocal: 
			{
			// do nothing - no conversion necessary
			break;
			}
		case EUtc:
			{
			User::LeaveIfError(iTimeConverter.ConvertToLocalTime(aTime));
			break;
			}
		default: 
			{
			Panic(EAgmErrUnsupportedTimeMode);
			}
		};
	}

void TAgnCalendarFloatingTimeMode::FromL(MAgnCalendarTimeMode::TFormat aFormat, TTime& aTime) const
	{
	switch (aFormat)
		{
		case ELocal: 
		case EFixedLocal: 
			{
			// do nothing - no conversion necessary
			break;
			}
		case EUtc:
			{
			User::LeaveIfError(iTimeConverter.ConvertToUniversalTime(aTime));
			break;
			}
		default: 
			{
			Panic(EAgmErrUnsupportedTimeMode);
			}
		};
	}

MAgnCalendarTimeMode::TTimeMode TAgnCalendarFloatingTimeMode::TimeMode() const
	{
	return MAgnCalendarTimeMode::EFloating;
	}
