// Copyright (c) 2006-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 <calrrule.h>
#include <calinstance.h>
#include <calcategory.h>
#include "entryparser.h"

//update entry with value if updated value doesnt equal default variable value
#define	UPDATEVALUE( entry, defaultvalue, value )	\
	if( entry.value != defaultvalue)					\
		{												\
		this->value = entry.value;						\
		}
		
CEntryProperty* CEntryProperty::NewLC()
	{
	CEntryProperty* entry = new(ELeave) CEntryProperty();
	CleanupStack::PushL( entry );
	return entry;
	}
	
CEntryProperty::~CEntryProperty()
	{
	iCustomCategoriesList.Reset();
	iCustomCategoriesList.Close();
	}

CEntryProperty::CEntryProperty() :
    iStandardEntryCount( 0 ),
    iRepeatingEntryCount( 0 ),
    iStartDaysFromToday( 0 ),
    iEndDaysFromToday( 0 ),
    iEntryDateIncrement( 1 ),
    iStandardcategory( 0 ),
    iCustomcategory( 0 ),
    iCategoryType( ESingleCategory ),
    iIntervalType( EDay ),
    iEntryType( EEntryEnds ),
    iFloating( EFalse )
	{
	}		
			
void CEntryProperty::Update( const CEntryProperty& aPropertyUpdate )
	{
	//update all simple member variables, if updated value is not empty/default   
	UPDATEVALUE( aPropertyUpdate, 0, iStandardEntryCount );
    UPDATEVALUE( aPropertyUpdate, 0, iRepeatingEntryCount);	
    UPDATEVALUE( aPropertyUpdate, 0, iStartDaysFromToday );
    UPDATEVALUE( aPropertyUpdate, 0, iEndDaysFromToday );
    //result value should equal variable default value, only update if value does not equal default
    UPDATEVALUE( aPropertyUpdate, 1, iEntryDateIncrement); 
    UPDATEVALUE( aPropertyUpdate, 0, iStandardcategory );
    UPDATEVALUE( aPropertyUpdate, 0, iCustomcategory );
    UPDATEVALUE( aPropertyUpdate, ESingleCategory, iCategoryType );
    UPDATEVALUE( aPropertyUpdate, EDay, iIntervalType);
    UPDATEVALUE( aPropertyUpdate, EEntryEnds, iEntryType   );
	UPDATEVALUE( aPropertyUpdate, 0, iAlarmOffset );
	UPDATEVALUE( aPropertyUpdate, 0, iExceptionNumber );
	UPDATEVALUE( aPropertyUpdate, 0, iChildCount );
	UPDATEVALUE( aPropertyUpdate, EFalse, iFloating );
    if( aPropertyUpdate.iCustomCategoryString != KNullDesC )
    	{
    	//this only copies pointer to buffer, propertyupdate should not go out of scope before this
    	this->iCustomCategoryString.Set(aPropertyUpdate.iCustomCategoryString);
    	}
	if( aPropertyUpdate.iCustomCategoriesList.Count() > 0 )
		{
		this->iCustomCategoriesList.Reset();
		//this only copies pointer to buffers, propertyupdate should not go out of scope before this
		CCalendarTestUtility::CopyArrayL( aPropertyUpdate.iCustomCategoriesList, this->iCustomCategoriesList );
		}
	//no need to copy iStartDate and iEndDate, autogenerated
	}

TEntryParser::TEntryParser( CTestCalInterimApiSuiteStepBase& aTestStep ) :
	iTestStep( aTestStep )
		{
		}
/**
parses config section and creates and populates entry property
*/		
CEntryProperty* TEntryParser::ParseEntryL( const TDesC& aSection )
	{
	if( aSection == KNullDesC )
		{
		return NULL;
		}
		
	CEntryProperty* entry = CEntryProperty::NewLC();
    _LIT( KStandardEntries, "numberofstandardentries" );
    _LIT( KRepeatingEntries, "numberofrepeatingentries" );
    _LIT( KStartdate, "startdate" );
    _LIT( KEnddate, "enddate" );
    _LIT( KDateincrement, "dateincrement" );
    _LIT( KStandardcategory, "standardcategory" );
    _LIT( KCustomcategory, "customcategory" );
    _LIT( KCustomcategoryList, "customcategoryList" );
    _LIT( KMultipleCategory, "multipleCategory" );
    _LIT( KTimePeriod, "timeperiod" );
    _LIT( KAlarmOffset, "alarmoffset" );
    _LIT( KExceptionNumber, "exceptionNumber" );
    _LIT( KChildCount, "childCount" );
    _LIT( KFloating, "floating" );
    const TInt KRepeatsForever = -999999999;
    
    
	TBool multipleCategories = EFalse;
	TInt intervalType = -1;
    
    //parse config file and retrive requested entry data
    iTestStep.GetIntFromConfig( aSection, KStandardEntries,  entry->iStandardEntryCount);
    iTestStep.GetIntFromConfig( aSection, KRepeatingEntries,  entry->iRepeatingEntryCount);
    iTestStep.GetIntFromConfig( aSection, KStartdate,  entry->iStartDaysFromToday);
    iTestStep.GetIntFromConfig( aSection, KEnddate,  entry->iEndDaysFromToday);
    iTestStep.GetIntFromConfig( aSection, KDateincrement,  entry->iEntryDateIncrement);
    iTestStep.GetIntFromConfig( aSection, KStandardcategory,  entry->iStandardcategory);
    iTestStep.GetIntFromConfig( aSection, KCustomcategory,  entry->iCustomcategory);
    iTestStep.GetStringFromConfig( aSection, KCustomcategoryList,  entry->iCustomCategoryString);
    iTestStep.GetIntFromConfig( aSection, KAlarmOffset,  entry->iAlarmOffset);
    iTestStep.GetIntFromConfig( aSection, KExceptionNumber,  entry->iExceptionNumber);
    iTestStep.GetIntFromConfig( aSection, KChildCount,  entry->iChildCount);
    iTestStep.GetBoolFromConfig( aSection, KFloating,  entry->iFloating);
    
    iTestStep.GetBoolFromConfig( aSection, KMultipleCategory,  multipleCategories);
    //add multiple or single categories to an entry
    entry->iCategoryType = multipleCategories ? CEntryProperty::EMultipleCategory : CEntryProperty::ESingleCategory;
    
    //the time period to add between created entries, days, hours...
    iTestStep.GetIntFromConfig( aSection, KTimePeriod,  intervalType );
    entry->iIntervalType = static_cast<CEntryProperty::TIntervalType>( intervalType );
    
    entry->iEntryType = ( entry->iEndDaysFromToday == KRepeatsForever ) ? CEntryProperty::EEntryRepeatsForever : CEntryProperty::EEntryEnds;
    //retrieve a list of custom category names
    iTestStep.TokenizeStringL( entry->iCustomCategoryString, entry->iCustomCategoriesList, ',');
	
	CleanupStack::Pop( entry );
	return entry;
	}

//return true if entry property contains 1 or more standard/repeating entries
TBool CEntryProperty::ContainsEntries() const
	{
	return ( ( iStandardEntryCount > 0 ) || ( iRepeatingEntryCount > 0 ) );
	}

CEntryBuilder* CEntryBuilder::NewL( 
	CTestCalInterimApiSuiteStepBase& aTestStep, 
	CEntryProperty& aEntry,
	TTime& 	aTestTime,
	TTime& 	aPresentTime
	)
	{
	CEntryBuilder* builder = new (ELeave) CEntryBuilder(aTestStep, aEntry, aTestTime, aPresentTime);
	CleanupStack::PushL(builder);
	builder->iUtility = CCalendarTestUtility::NewLC( aTestStep );
	CleanupStack::Pop( builder->iUtility );
	CleanupStack::Pop(builder);
	return builder;
	}
	
CEntryBuilder::~CEntryBuilder()
	{
	iEntryList.ResetAndDestroy();
	delete iUtility;
	}

CEntryBuilder::CEntryBuilder( 
		CTestCalInterimApiSuiteStepBase& aTestStep, 
		CEntryProperty& aEntry,
		TTime& 	aTestTime,
		TTime& 	aPresentTime  
		)
:	iTestStep( aTestStep ),
	iEntry( aEntry ),
	iPresentTime( aPresentTime ),
	iTestTime( aTestTime ),
	iEntryList()
	{
	}

//specify that only a single entry should be update
void CEntryBuilder::SetSingleUpdate( const CCalEntry& aUpdateEntry )
	{
	iSingleEntry = const_cast<CCalEntry*>(&aUpdateEntry);
	}

//all entries should be updated
void CEntryBuilder::SetMultipleUpdate()
	{
	iSingleEntry = NULL;
	}

//alwasy true if in multiple update mode
//compare update entry to single entry, and return true if same object	
TBool CEntryBuilder::IsUpdatableL( const CCalEntry& aUpdateEntry )
	{
	//if no single entry to update, then update all entries
	if( !iSingleEntry )
		{
		return ETrue;
		}
	return CCalendarTestUtility::CompareEntriesL( *iSingleEntry, aUpdateEntry);
	}

//add list of predefined categories to all entries
//if multiple categories, add all categories to each entry
void CEntryBuilder::AddDefaultCategoriesL(
        const TInt aFirstEntry, //location of first entry to update
        const TInt aNumberOfEntries, //number of entries to update
        const CEntryProperty::TCategoryType aCategoryType
        )
    {
    const TInt KEntryCount = iEntryList.Count();
    //ignore ECalExtended, as it appears to be invalid, cause leave error -6
    const TInt KNumberOfDefaultCategories = CCalCategory::ECalVacation + 1;
    //if multiple categories, add all categories to each entry
    const TInt multipleCategoryItrationCount = aCategoryType == CEntryProperty::ESingleCategory ? 1 : KNumberOfDefaultCategories;
    const TInt KLastEntry = aNumberOfEntries + aFirstEntry;
    for(TInt i = aFirstEntry; ( (i < KLastEntry ) && (i < KEntryCount) ); ++i)
        {
        //if this isn't the entry to be updated, skip
        if( !IsUpdatableL( *iEntryList[i] ) )
        	{
        	continue;
        	}
	    for( TInt j = 0; j < multipleCategoryItrationCount; ++j)
	    	{
	    	const TInt KIteration = aCategoryType == CEntryProperty::ESingleCategory ? i : j;
	        //cycle through all possible entry types
	        const CCalCategory::TCalCategoryType KCategoryType = 
	        			static_cast < CCalCategory::TCalCategoryType >( KIteration % KNumberOfDefaultCategories );
			CCalCategory* category = CCalCategory::NewL( KCategoryType );
			CleanupStack::PushL( category );		        			
			iEntryList[i]->AddCategoryL( category );
			CleanupStack::Pop( category );
	        }
    	}
    }

//add list of custom categories to all entries
//if multiple categories, add all categories to each entry
void CEntryBuilder::AddCustomCategoriesL(
        const TInt aFirstEntry, //location of first entry to update
        const TInt aNumberOfEntries, //number of entries to update
        const CEntryProperty::TCategoryType aCategoryType
        )
    {
    //if no custom categories defined do nothing
    if( iEntry.iCustomCategoriesList.Count() > 0  )
    	{
    	return;
    	}
    	
    const TInt KEntryCount = iEntryList.Count();
    const TInt KNumberOfCustomCategories = iEntry.iCustomCategoriesList.Count();
	//if multiple categories, add all categories to each entry
    const TInt multipleCategoryItrationCount = aCategoryType == CEntryProperty::ESingleCategory ? 1 : KNumberOfCustomCategories;
    const TInt KLastEntry = aNumberOfEntries + aFirstEntry;
    for(TInt i = aFirstEntry; ( (i < KLastEntry ) && (i < KEntryCount) ); ++i)
        {
        //if this isn't the entry to be updated, skip
        if( !IsUpdatableL( *iEntryList[i] ) )
        	{
        	continue;
        	}
	    for( TInt j = 0; j < multipleCategoryItrationCount; ++j)
	    	{
	        //cycle through all possible entry types
	    	const TInt KIteration = aCategoryType == CEntryProperty::ESingleCategory ? i : j;
	    	const TInt KCategoryIndex = KIteration >= KNumberOfCustomCategories ?  ( KIteration % KNumberOfCustomCategories ) : KIteration;
	    	
			CCalCategory* category = CCalCategory::NewL( iEntry.iCustomCategoriesList[ KCategoryIndex ] );
			CleanupStack::PushL( category );
			iEntryList[i]->AddCategoryL( category );
			CleanupStack::Pop( category );
	        }
    	}
    }

//create aNumberOfEntries many empty entries
void CEntryBuilder::CreateEmptyEntriesL(
        const CCalEntry::TMethod aEntryMethodType,
        const TInt aNumberOfEntries)
    {
    //base guid value
    _LIT8(KGuidSeed, "EntryGUID ");
    
    const TInt KNumberOfEntryTypes = CCalEntry::EAnniv + 1;
    CCalEntry::TType entryType;
    CCalEntry* entry = NULL;
    for(TInt i = 0; i < aNumberOfEntries; ++i)
        {
        //cycle through all possible entry types
        entryType = static_cast<CCalEntry::TType>( i % KNumberOfEntryTypes );
        if( entryType == CCalEntry::ETodo )
        	{
        	//TODO:: temporary only, dont create todo entries until DEF096288 fixed
        	continue;
        	}
        //assume less than 10000000 ((7+1)*2 bytes) entries will be added
        HBufC8* guidBuf = HBufC8::NewLC( KGuidSeed().Length() + 16 ); 
        
        //increment guid id by 1, so that all guid are unique
        TPtr8 guidPtr = guidBuf->Des();
        guidPtr.Append( KGuidSeed() );
        guidPtr.AppendNum( ++iEntryCount );
        
        entry = CCalEntry::NewL(entryType, guidBuf, aEntryMethodType, 0);
        CleanupStack::Pop( guidBuf );
        
        TInt error = iEntryList.Append( entry );
        //if error, unload entry and leave
        if( error != KErrNone )
        	{
        	DELETE( entry );
        	User::Leave( error );
        	}
        entry = NULL;
        }
    DELETE ( entry );
    }

//set start and end date for entries,
void CEntryBuilder::SetEntriesStartEndDatesL(
        const TInt aStartPeriodFromToday,//time period from today until start date
        const TInt aEndPeriodFromToday,//time period from today until end date
        const TInt aEntryDateIncrement,
        const TInt aFirstEntry
        )
    {
    //if test time not set use actual current time
    const TTime KHomeTime = ( iTestTime == Time::NullTTime() ) ? iPresentTime : iTestTime;
    
    TCalTime earliestTime;
    earliestTime.SetTimeLocalL( TCalTime::MaxTime() );
    
    TCalTime latestTime;
    latestTime.SetTimeLocalL( TCalTime::MinTime() );
    
    const TInt KEntryCount = iEntryList.Count();
    for(TInt i = aFirstEntry; i < KEntryCount; ++i)
        {
        //number of units to inc/decrease an entry wiht
        const TInt KIncrement = i * aEntryDateIncrement;
        //number of units to inc/decrease an start date plus inital offset
        const TInt KStartIncrement = aStartPeriodFromToday + KIncrement;
        //number of units to inc/decrease an end date plus inital offset
        const TInt KEndIncrement = aEndPeriodFromToday + KIncrement;
        
        //apply time interval offset to start/end dates
        const TTime KStartTime = ApplyTimeInterval( KStartIncrement, KHomeTime );
        const TTime KEndTime = ApplyTimeInterval( KEndIncrement, KHomeTime );               
        
        //set caltime to floating/fixed time
        TCalTime calStartTime = TCalTime();
        SetLocalTimeL( calStartTime, KStartTime );
        TCalTime calEndTime = TCalTime();
        SetLocalTimeL( calEndTime, KEndTime );
        
        //appply start/end time to entry
        iEntryList[i]->SetStartAndEndTimeL(calStartTime, calEndTime);

        //update earlier/latest times
        if( calStartTime.TimeLocalL() < earliestTime.TimeLocalL() && calStartTime.TimeLocalL() != Time::NullTTime() )
        	{
        	earliestTime = calStartTime;
        	}
        if( calEndTime.TimeLocalL() > latestTime.TimeLocalL() && calEndTime.TimeLocalL() != Time::NullTTime() )
        	{
        	latestTime = calEndTime;
        	}
        }
    //set entry property earliest start and latest end times
    if( earliestTime.TimeLocalL() < TCalTime::MaxTime() && latestTime.TimeLocalL() > TCalTime::MinTime() )
    	{
    	if( iEntry.iStartDate.TimeLocalL() == Time::NullTTime() || 
    		iEntry.iStartDate.TimeLocalL() > earliestTime.TimeLocalL()
    		)
    		{
	    	//decrment one day to include earliest entry
			iEntry.iStartDate.SetTimeLocalL( earliestTime.TimeLocalL() - TTimeIntervalDays( 1 ) );
    		}
    	if( iEntry.iEndDate.TimeLocalL() == Time::NullTTime() || 
    		iEntry.iEndDate.TimeLocalL() < latestTime.TimeLocalL()
    		)
    		{
	    	//increment one day to include latest entry
	    	iEntry.iEndDate.SetTimeLocalL( latestTime.TimeLocalL() + TTimeIntervalDays( 1 ) );
    		}
    	}
    }

//set caltime to floating/fixed time
void CEntryBuilder::SetLocalTimeL( 
		TCalTime& aCalTime, 
		const TTime aTTime )
	{
	if( iEntry.iFloating )
		{
		aCalTime.SetTimeLocalFloatingL( aTTime );
		}
	else
		{
		aCalTime.SetTimeLocalL( aTTime );
		}
	}

//update all entries to floating/fixed time
void CEntryBuilder::SetEntryLocalTime( 
		const CCalEntry& aEntry,  
		const CEntryProperty& aProperty )
	{
	iEntry.Update( aProperty );
	for( TInt i = 0; i < iEntryList.Count(); ++i )
		{
		if( iEntryList[i] == &aEntry )
			{
			TCalTime calStartTime = iEntryList[i]->StartTimeL();
			TCalTime calEndTime = iEntryList[i]->EndTimeL();
			SetLocalTimeL( calStartTime, calStartTime.TimeLocalL() );
			SetLocalTimeL( calEndTime, calEndTime.TimeLocalL() );
			iEntryList[i]->SetStartAndEndTimeL( calStartTime, calEndTime );
			break;
			}
		}
	}

// inc/decrement initial time by current interval type
//second, minute...
TTime CEntryBuilder::ApplyTimeInterval( 
			const TInt aInterval,
			const TTime aInitialTime )
	{
	TTime timePlusPeriod = Time::NullTTime();
	switch( iEntry.iIntervalType )
		{
		case CEntryProperty::ESecond:
			{
			timePlusPeriod = aInitialTime + TTimeIntervalSeconds( aInterval );
			}
		case CEntryProperty::EMinute:
			{
			timePlusPeriod = aInitialTime + TTimeIntervalMinutes( aInterval );
			}
		case CEntryProperty::EHour:
			{
			timePlusPeriod = aInitialTime + TTimeIntervalHours( aInterval );
			}
		case CEntryProperty::EMonth:
			{
			timePlusPeriod = aInitialTime + TTimeIntervalMonths( aInterval );
			}
		case CEntryProperty::EYear:
			{
			timePlusPeriod = aInitialTime + TTimeIntervalYears( aInterval );
			}
		case CEntryProperty::EDay:
		default:
			{
			timePlusPeriod = aInitialTime + TTimeIntervalDays( aInterval );
			}
		}
	return timePlusPeriod;
	}
		
//set repeat rules for all entries
//iterate through all possible types of rules, weekly, daily...
void CEntryBuilder::SetEntriesRRulesL( 
        const CEntryProperty::TEntryRepeatType aRepeatType, 
        const TInt aFirstEntry, 
        const TInt aNumberOfEntries
        )
    {
    const TInt KEntryCount = iEntryList.Count();
    //only count valid rule types, exclude TCalRRule::EInvalid
    const TInt KNumberOfRuleTypes = TCalRRule::EYearly;
    const TInt KLastEntry = aNumberOfEntries + aFirstEntry;
    CCalEntry* entry = NULL;
    for(TInt i = aFirstEntry; ( (i < KLastEntry) && (i < KEntryCount) ); ++i)
        {
        entry = iEntryList[i];
        
        //+1 to ignore TCalRRule::EInvalid, cycle through all possible rule types, from EDaily to EYearly
        TCalRRule::TType ruleType = static_cast<TCalRRule::TType>( ( i % KNumberOfRuleTypes ) + 1 );
        TCalRRule entryRule = TCalRRule( ruleType );
        
        //repeat rule start date matches entry start date
        entryRule.SetDtStart( entry->StartTimeL() );
        
        //if this entry doesn't repeat forever, set end date
        if( aRepeatType != CEntryProperty::EEntryRepeatsForever )
            {
            entryRule.SetUntil( entry->EndTimeL() );
            }
        //else entry repeats forever, default behaviour of rrule
        
        //change entry end time to be +1 hour from start time
        TCalTime correctedEndtime;
        SetLocalTimeL( correctedEndtime, entry->StartTimeL().TimeLocalL() + TTimeIntervalHours( 1 ) );
        entry->SetStartAndEndTimeL(entry->StartTimeL(), correctedEndtime );
        
        //if weekly rpt rule, must set day of week to repeat on   
        if( ruleType == TCalRRule::EWeekly )
        	{
        	RArray<TDay> weekDays;
        	CleanupClosePushL( weekDays );
        	weekDays.AppendL( entry->StartTimeL().TimeLocalL().DayNoInWeek() );
        	entryRule.SetByDay( weekDays );
        	CleanupStack::PopAndDestroy( &weekDays );
        	}
        //set rrule for entry
        entry->SetRRuleL( entryRule );
        entry = NULL;
        }
    DELETE( entry );
    }

//adds an alarm to each entry, the alarm is set to expire  aMinutesFromNow
// minutes from current time
void CEntryBuilder::AddAlarmsToEntriesL( const TInt aMinutesFromNow )
	{
	TTimeIntervalMinutes minutesIntervalFromHome( aMinutesFromNow );
	TTime alarmTime;
	alarmTime.HomeTime();
	alarmTime += minutesIntervalFromHome;
	
	const TInt KEntryCount = iEntryList.Count();
	CCalEntry* entry = NULL;
	for( TInt i = 0; i < KEntryCount; ++i )
		{
		entry = iEntryList[i];
		//set alarm offset to 0
		TTimeIntervalMinutes entryAlarmOffset(0);
		TCalTime calAlarmTime;
		calAlarmTime.SetTimeLocalL( alarmTime );
		//set entry start/end time to match alarm time
		entry->SetStartAndEndTimeL(calAlarmTime, calAlarmTime );
		CCalAlarm* alarm = CCalAlarm::NewL();
		CleanupStack::PushL( alarm );
		alarm->SetTimeOffset( entryAlarmOffset );
		entry->SetAlarmL( alarm );
		CleanupStack::PopAndDestroy( alarm );
		entry = NULL;
		}
	DELETE( entry );
	}

//get array of entries
RPointerArray< CCalEntry >& CEntryBuilder::GetEntryList()
	{
	return iEntryList;
	}

//return entry property used by builder
CEntryProperty& CEntryBuilder::GetEntryProperty()
	{
	return iEntry;
	}

//returns entry count
TInt CEntryBuilder::Count() const 
	{
	if( iEntryCount != iEntryList.Count() )
		{
		//discrepency in count
		return -1;
		}
	return iEntryCount;
	}

