// Copyright (c) 2007-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:
//

/**
 @file
 @internalComponent
 @released
*/

#include "pplcontactitemmanager.h"
#include "cntsqlprovider.h"
#include "dbsqlconstants.h"
#include "cntpersistenceutility.h"
#include <cntdef.h>
#include <sqldb.h>
#include <cntdb.h>

/**
Creates a concrete CPplContactItemManager object 

@param 	aDatabase reference to RSqlDatabase
		aTransactionManager reference to MLplTransactionManager (provided by CPlContactsFile)
@return concrete CPplContactItemManager object
@leave KErrNoMemory, an out of memory occured
*/		
CPplContactItemManager* CPplContactItemManager::NewL(RSqlDatabase& aDatabase, MLplTransactionManager& aTransactionManager, CLplContactProperties&  aContactProperties, RPplIccContactStore& aIccContactStore)
	{
	CPplContactItemManager* self = new (ELeave) CPplContactItemManager(aDatabase, aTransactionManager, aContactProperties, aIccContactStore);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;	
	}

/**
Class destructor. Deletes holded CCntSqlStatement

*/		
CPplContactItemManager::~CPplContactItemManager()
	{
	delete iSelectStatement;	
	delete iContactTable;
	delete iCommAddrTable;	
	delete iGroupTable;
	delete iPreferencePersistor;
	}
	
/**
Class constructor

@param aDatabase reference to RSqlDatabase
@param aTransactionManager reference to transaction manager
@param aContactProperties reference to contact properties
*/	
CPplContactItemManager::CPplContactItemManager(RSqlDatabase& aDatabase, MLplTransactionManager& aTransactionManager, CLplContactProperties&  aContactProperties, RPplIccContactStore& aIccContactStore) :
	iDatabase(aDatabase),
	iTransactionManager(aTransactionManager),
	iContactProperties(aContactProperties),
	iIccContactStore(aIccContactStore)
	{
	}
	
/**
Add the given contact item to the database. Forward the call to CPplTableBase
based classes representing the tables in the contact database.

@param 	aItem The contact item to be added to the database. 
@param 	aSessionId The ID of the session that issued the request.  Used to
		prevent Phonebook Synchroniser deadlock.

@return Contact item ID of the contact added to the database.
*/
TContactItemId CPplContactItemManager::CreateL(CContactItem& aItem, TUint aSessionId)
	{
	TBool controlTransaction = !(iTransactionManager.IsTransactionActive());
	
	TBool compressedGuid=EFalse;

	// If needed generate a gui for the current contact item
	if (aItem.Guid() == TPtrC(KNullDesC))
		{
		iPreferencePersistor->SetGuidL(aItem, compressedGuid);
		}

	if (compressedGuid)
		{
		aItem.SetHasCompressedGuid(compressedGuid);
		}

	if (aItem.Type() == KUidContactICCEntry) 
		{
		const TInt ret = iContactProperties.ContactSynchroniserL(aSessionId).ValidateWriteContact(static_cast<CContactICCEntry&>(aItem));
		User::LeaveIfError(ret);	
		}
	
	if(controlTransaction)
		{
		StartTransactionL(aSessionId);
		}
		
	iContactTable->CreateInDbL(aItem);	
	iGroupTable->CreateInDbL(aItem);	
	iCommAddrTable->CreateInDbL(aItem);

   	TContactItemId groupId = iIccContactStore.CreateInDbL(aItem, aSessionId);
   	if(groupId != KNullContactId)
   	    {
   	    //Every ICC entry is added to a special group, created by the Phonebook
        //Synchroniser server during the initial synchronisation with the Contacts Model.
		CContactItemViewDef* itemDef = CContactItemViewDef::NewLC(CContactItemViewDef::EIncludeFields,CContactItemViewDef::EMaskHiddenFields);
		itemDef->AddL(KUidContactFieldMatchAll);

		// Add ICC entry to the group.
		CContactGroup* grp = static_cast<CContactGroup*>(ReadLC(groupId, *itemDef, EPlAllInfo, aSessionId));
		
		grp->AddContactL(aItem.Id());
		UpdateL(*grp, aSessionId);

		CleanupStack::PopAndDestroy(2, itemDef); // grp
   	    }

	if(controlTransaction)
		{
		CommitTransactionL();
		}
	
	return aItem.Id();	
	}
	
/**
Reads the contact item from the database using the given contact item ID.

@param aItemId The Id number of the contact to be read.
@param aView Specifies the fields to be read.
@param aInfoToRead not used 
@param aSessionId The ID of the session that issued the request.  This is used
to prevent Phonebook Synchroniser deadlock.
@param aIccOpenCheck Specifies if validation with the Phonebook Synchroniser is
needed for this contact.

@leave 	KErrArgument 	if the itemID can't be set within select statement
@leave  KErrNotFound 	if a contact item with aItemId does not exist within contact database
@leave  KSqlErrBusy 	the database file is locked; thrown if RSqlStatement::Next() 
						returns this error
@leave  KErrNoMemory 	an out of memory condition has occurred - the statement
                        will be reset;thrown if RSqlStatement::Next() returns this error
@leave  KSqlErrGeneral	a run-time error has occured - this function must not
                        be called again;thrown if RSqlStatement::Next() returns this error
@leave	KSqlErrMisuse	this function has been called after a previous call
                        returned KSqlAtEnd or KSqlErrGeneral.thrown if RSqlStatement::Next() 
                        returns this error
@leave  KSqlErrStmtExpired	the SQL statement has expired (if new functions or
                            collating sequences have been registered or if an
                            authorizer function has been added or changed);
                            thrown if RSqlStatement::Next() returns this error

@return CContactItem created from reading the database tables.
*/	
CContactItem* CPplContactItemManager::ReadLC(TContactItemId aItemId, const CContactItemViewDef& aView, TInt aInfoToRead, TUint aSessionId, TBool aIccOpenCheck) const
	{	
	CContactTemplate* sysTemplate = NULL;
	if (aItemId != KGoldenTemplateId) 
		{
		sysTemplate = const_cast<CContactTemplate*>(&iContactProperties.SystemTemplateL());
		}
	
	RSqlStatement selectStmt;
	CleanupClosePushL(selectStmt);
	User::LeaveIfError(selectStmt.Prepare(iDatabase, iSelectStatement->SqlStringL()));
	
	TInt err = selectStmt.BindInt(KFirstParam, aItemId);
	
	if(err != KErrNone)
		{
		User::Leave(KErrArgument);	
		}
	
	CContactItem* item = NULL;
	TUid type(KNullUid);
	
	if((err = selectStmt.Next()) == KSqlAtRow)
		{
		TInt contactId = selectStmt.ColumnInt(iSelectStatement->ParameterIndex(KContactId));
		TInt templateId = selectStmt.ColumnInt(iSelectStatement->ParameterIndex(KContactTemplateId));
		TInt typeFlags = selectStmt.ColumnInt(iSelectStatement->ParameterIndex(KContactTypeFlags));
		type = TCntPersistenceUtility::TypeFlagsToContactTypeUid(typeFlags); 
		item = CContactItem::NewLC(type);
		item->SetId(contactId);
		TPtrC guid = selectStmt.ColumnTextL(iSelectStatement->ParameterIndex(KContactGuidString));
		item->SetUidStringL(guid);
		TInt attr = (typeFlags & EContactAttributes_Mask) >> EContactAttributes_Shift; 
		item->SetAttributes(attr);
		item->SetTemplateRefId(templateId);
		item->SetLastModified(TTime(selectStmt.ColumnInt64(iSelectStatement->ParameterIndex(KContactLastModified)))); 
		item->SetCreationDate(TTime(selectStmt.ColumnInt64(iSelectStatement->ParameterIndex(KContactCreationDate))));
		item->SetAccessCount(selectStmt.ColumnInt(iSelectStatement->ParameterIndex(KContactAccessCount)));
			
		RArray<TPtrC> fastAccessFields;
		CleanupClosePushL(fastAccessFields);
		fastAccessFields.AppendL(selectStmt.ColumnTextL(iSelectStatement->ParameterIndex(KContactFirstName)));
		fastAccessFields.AppendL(selectStmt.ColumnTextL(iSelectStatement->ParameterIndex(KContactLastName)));
		fastAccessFields.AppendL(selectStmt.ColumnTextL(iSelectStatement->ParameterIndex(KContactCompanyName)));
		fastAccessFields.AppendL(selectStmt.ColumnTextL(iSelectStatement->ParameterIndex(KContactFirstNamePrn)));
		fastAccessFields.AppendL(selectStmt.ColumnTextL(iSelectStatement->ParameterIndex(KContactLastNamePrn)));
		fastAccessFields.AppendL(selectStmt.ColumnTextL(iSelectStatement->ParameterIndex(KContactCompanyNamePrn)));
				
		//set first name, last name, company name, first name pronunciation, last name pronunciation, company name pronunciation
		for (TInt fieldNum = item->CardFields().Count() - 1; fieldNum>=0; --fieldNum) 
			{
			CContactItemField& textField = (item->CardFields())[fieldNum];
			const TInt nameFieldIndex = NameFieldIndex(textField);

			// Check if field is first name, last name, company name,
			// first name pronunciation, last name pronunciation, company name pronunciation.
			if (nameFieldIndex != KErrNotFound)
				{
				HBufC* text = HBufC::NewLC(fastAccessFields[nameFieldIndex].Size());
				text->Des() = fastAccessFields[nameFieldIndex];
				textField.TextStorage()->SetText(text);
				CleanupStack::PopAndDestroy(text);
				}
			}
			
		CleanupStack::PopAndDestroy(&fastAccessFields);		
		}
	else
		{
		if(err == KSqlAtEnd)
			{
			User::Leave(KErrNotFound);	// Select statement did not return any row		
			}
		else
			{
			User::Leave(err); 	// Otherwise selectStmt.Next() generated a sql error
			}	
		
		}

	if (!(item->IsDeleted()) || type == KUidContactCardTemplate)
		{
		TCntPersistenceUtility::ReadBlobL(*item, aView, sysTemplate, iDatabase);
		}
    
    //Fill in group content.
    static_cast<CPplGroupsTable*>(iGroupTable)->ReadL(*item);
    
    //Validate checking ICC entry.
    iIccContactStore.ReadL(*item, aInfoToRead, aSessionId, aIccOpenCheck);
    
	CleanupStack::Pop(item);
	CleanupStack::PopAndDestroy(&selectStmt);
	CleanupStack::PushL(item);
	
	return item;
	}
	
/** 
Updates the given contact item in the database.Forward the call to CPplTableBase
based classes representing the tables in the contact database.

@param aItem The contact to be updated with all the changes (received from the
user).
@param aSessionId The ID of the session that issued the request.  Used to
prevent Phonebook Synchroniser deadlock.
*/	
void CPplContactItemManager::UpdateL(CContactItem& aItem, TUint aSessionId, TBool /*aSpeeDailUpdate*/)
	{
	TBool controlTransaction = !(iTransactionManager.IsTransactionActive());
	
	if (aItem.Type() == KUidContactICCEntry) 
		{
        //Fakely validating read right to ICC entry to keep BC.
		const TInt ret = iContactProperties.ContactSynchroniserL(aSessionId).ValidateContact(MContactSynchroniser::ERead, aItem.Id());
		User::LeaveIfError(ret);	
		}
		
	if(controlTransaction)
		{
		StartTransactionL(aSessionId);
		}

	iIccContactStore.UpdateL(aItem, aSessionId);
	iContactTable->UpdateL(aItem);	
	iGroupTable->UpdateL(aItem);	
	iCommAddrTable->UpdateL(aItem);

	if(controlTransaction)
		{
		CommitTransactionL();
		}
		
	// If the item is the System template then delete it from the template
	// manager.  The template can later be recreated from the table.
	if (aItem.Id() == KGoldenTemplateId)
		{
		iContactProperties.SystemTemplateManager().DeleteTemplate();
		}
	}

/**
Deletes the given contact from the database.Forward the call to CPplTableBase
based classes representing the tables in the contact database. In low disk condition
checks if it can gain access to reserver database space and perform the deletion. If 
this is not possible a KErrDiskFull will be thrown.

@param aItemId The contact ID of the contact item to be deleted.
@param aSessionId The ID of the session that issued the request.  Used to
prevent Phonebook Synchroniser deadlock.

@leave KErrDiskFull if a full disk error appears

@return The item which was read from the database before it was deleted.   This
object can be used by, for example, the CLplAnalyserProxy::DeleteLC() method to
create a notification message.
*/	
CContactItem* CPplContactItemManager::DeleteLC(TContactItemId  aItemId, TUint aSessionId, TCntSendEventAction /*aEventType*/)
	{
	TBool controlTransaction = !(iTransactionManager.IsTransactionActive());

	CContactItemViewDef* viewDef = CContactItemViewDef::NewL(CContactItemViewDef::EIncludeFields, CContactItemViewDef::EIncludeHiddenFields);
	CleanupStack::PushL(viewDef);

	if(controlTransaction)
		{
		StartTransactionL(aSessionId);
		}

	TBool lowDisk = EFalse;
	CContactItem* savedContactItem = static_cast<CPplContactTable*>(iContactTable)->DeleteLC(aItemId, lowDisk);
	if(lowDisk) 
		{
		CleanupStack::PopAndDestroy(savedContactItem); // this was returned from the first call, but not deleted in the db
		TInt err = iDatabase.GetReserveAccess();
		if(err != KErrNotFound)
			{
			User::Leave(KErrDiskFull);	
			}
		
		lowDisk = EFalse;
		
		savedContactItem = static_cast<CPplContactTable*>(iContactTable)->DeleteLC(aItemId, lowDisk);
		if(lowDisk)
			{
			iDatabase.FreeReservedSpace();
			User::Leave(KErrDiskFull);	
			}
		iDatabase.FreeReservedSpace();		
		}

	lowDisk = EFalse;	
	iGroupTable->DeleteL(*savedContactItem, lowDisk);	
	if(lowDisk) 
		{
		DeleteInLowDiskConditionL(iGroupTable, savedContactItem);
		}

	lowDisk = EFalse;
	iCommAddrTable->DeleteL(*savedContactItem, lowDisk);	
	if(lowDisk) 
		{
		DeleteInLowDiskConditionL(iCommAddrTable, savedContactItem);
		}
    
    //Fake checking read access to ICCEntry store to keep BC.
    iIccContactStore.ReadL(*savedContactItem, EPlGroupMembershipInfo, aSessionId, EFalse);    
    
	//Don't need to check low disk state for ICC synchronizer.
	iIccContactStore.DeleteL(*savedContactItem, aSessionId);

	if(controlTransaction)
		{
		CommitTransactionL();  	// Pops item
		}
		
	CleanupStack::Pop();		// Pops item or transaction
	CleanupStack::PopAndDestroy(viewDef);	
	CleanupStack::PushL(savedContactItem);

	return savedContactItem;
	
	}
	
/**
Perform a deletion in low disk condition

@param aTable CPplTableBase on which the deletion will be done
@param aContactItem the contact item to be deleted

@leave KErrDiskFull if the deletion can't be done a KerrDiskFull will be throw
*/
void CPplContactItemManager::DeleteInLowDiskConditionL(CPplTableBase* aTable, CContactItem* aContactItem)
	{
	TInt err = iDatabase.GetReserveAccess();
	if(err != KErrNotFound)
		{
		User::Leave(KErrDiskFull);	
		}
		
	TBool lowDisk = EFalse;
	aTable->DeleteL(*aContactItem, lowDisk);
	if(lowDisk)
		{
		iDatabase.FreeReservedSpace();
		User::Leave(KErrDiskFull);	
		}
	iDatabase.FreeReservedSpace();		
	}
	
/**
Change the type of the given contact item to the given type.Forward the call to CPplTableBase
based classes representing the tables in the contact database.

@param aItemId Contact item ID whose type is to be changed.
@param aNewType New type for contact item.
*/	
void CPplContactItemManager::ChangeTypeL(TContactItemId aItemId, TUid aNewType)
	{
	static_cast<CPplContactTable*>(iContactTable)->ChangeTypeL(aItemId, aNewType);
	}

/** 
Does nothing: the CLplAnalyserProxy class sets the connetion ID.
*/
void CPplContactItemManager::SetConnectionId(TInt /*aConnectionId*/)
	{
	}
	
/**
Second phase constructor for CPplContactItemManager object.
Sets the CCntSqlStatement to be used for reading contact item.

*/
void CPplContactItemManager::ConstructL()
	{
	TCntSqlStatementType statementType(ESelect, KSqlContactTableName);
	
	iSelectStatement = TSqlProvider::GetSqlStatementL(statementType);
	
	// also prepare everything in CCntSqlStatement
	// First add columns from contact table
	iSelectStatement->SetParamL(KContactId, KSpace);
	iSelectStatement->SetParamL(KContactTemplateId, KSpace);
	iSelectStatement->SetParamL(KContactTypeFlags, KSpace);
	iSelectStatement->SetParamL(KContactAccessCount, KSpace);
	iSelectStatement->SetParamL(KContactCreationDate, KSpace);	
	iSelectStatement->SetParamL(KContactLastModified, KSpace);
	iSelectStatement->SetParamL(KContactGuidString, KSpace);
	iSelectStatement->SetParamL(KContactFirstName, KSpace);
	iSelectStatement->SetParamL(KContactLastName, KSpace);
	iSelectStatement->SetParamL(KContactCompanyName, KSpace);
	iSelectStatement->SetParamL(KContactFirstNamePrn, KSpace);
	iSelectStatement->SetParamL(KContactLastNamePrn, KSpace);
	iSelectStatement->SetParamL(KContactCompanyNamePrn, KSpace);
	
	HBufC* condition = HBufC::NewLC(KWhereStringEqualsStringFormatText().Size() + KContactId().Size());
	TPtr ptrCondition = condition->Des();
	ptrCondition.Format(KWhereStringEqualsStringFormatText, &KContactId, &KVar);
	
	// Set condition
	iSelectStatement->SetConditionL(ptrCondition);
	
	CleanupStack::PopAndDestroy( condition ); 

	// construct tables
	iContactTable = CPplContactTable::NewL(iDatabase, iContactProperties);
	iCommAddrTable = CPplCommAddrTable::NewL(iDatabase, iContactProperties);
	iGroupTable = CPplGroupsTable::NewL(iDatabase);
	iPreferencePersistor = CPplPreferencesPersistor::NewL(iDatabase);
	}

/**
Cleanup operation. If one of the crud operation fails, this method will be called
to rollback the sql transaction

*/
void CPplContactItemManager::CleanupOperationRollbackL(TAny* aPplContactItemanager)
	{
	CPplContactItemManager* contactItemManager = static_cast<CPplContactItemManager*>(aPplContactItemanager);
	contactItemManager->iTransactionManager.RollbackCurrentTransactionL(contactItemManager->iSessionId);
	}
	
/**
Utility method used to identify if the provided CContactItemField is first name, last name, 
company name, first name pronunciation, last name pronunciation or company name pronunciation 
Method used to fill information when a CContactItem is read from contact database

@param aNameField reference to a constant CContactItemField for which the index will be returned

*/	
TInt CPplContactItemManager::NameFieldIndex(const CContactItemField& aNameField) const
	{
	// For all the name fields...
	for (TInt nameFieldNum=0; nameFieldNum < sizeof(KFastAccessFieldUids)/sizeof(TInt); ++nameFieldNum)
		{
		if (aNameField.ContentType().ContainsFieldType(TUid::Uid(KFastAccessFieldUids[nameFieldNum])))
			{
			return nameFieldNum;
			}
		}
	return KErrNotFound;
	}
	
/**
Utility method used to start a transaction in database

@param aSessionId id of the session
*/
void CPplContactItemManager::StartTransactionL(TUint aSessionId)
	{
	iTransactionManager.StartTransactionL();
	
	iSessionId = aSessionId;
	CleanupStack::PushL(TCleanupItem(CleanupOperationRollbackL, this));		
	}
	
/**
Utility method used to commit a transaction
*/
void CPplContactItemManager::CommitTransactionL()
	{
	iTransactionManager.CommitCurrentTransactionL(iSessionId);
		
	CleanupStack::Pop(); //CleanupOperationRollbackL		
	}
	
/**
Utility method used to create tables in a newly create database
*/	
void CPplContactItemManager::CreateTablesL()
	{
	TBool controlTransaction = !(iTransactionManager.IsTransactionActive());
	
	if(controlTransaction)
		{
		StartTransactionL(0);
		}
		
	iContactTable->CreateTableL();	
	iGroupTable->CreateTableL();	
	iCommAddrTable->CreateTableL();
	iPreferencePersistor->CreateTableL();
	
	if(controlTransaction)
		{
		CommitTransactionL();
		}			
	}
	
/**
Utility method used to check if there is any record in the contact database

@return ETrue if the contact database is empty and EFalse otherwise
*/	
TBool CPplContactItemManager::IsDatabaseEmptyL()
	{
	return static_cast<CPplContactTable*>(iContactTable)->IsTableEmptyL();
	}

CContactIdArray* CPplContactItemManager::MatchPhoneNumberL(const TDesC& aNumber, TInt aMatchLengthFromRight)
	{
	// Call comm address table
	return  static_cast<CPplCommAddrTable*>(iCommAddrTable)->MatchPhoneNumberL(aNumber, aMatchLengthFromRight);
	}

/**
Utility method used to retrieve an array of card template ids

@return reference to a CContactIdArray containing the card template ids
*/
CContactIdArray& CPplContactItemManager::CardTemplateIdsL()
	{
	// Call contact table
	return  static_cast<CPplContactTable*>(iContactTable)->CardTemplateIdsL();	
	}

/**
Utility method used to retrieve a reference to persistence layer preference persistor
*/
CPplPreferencesPersistor& CPplContactItemManager::PreferencesPersitor()  
	{
	return *iPreferencePersistor;
	}

/**
Utility method used to retrieve own card contact id
*/
TContactItemId CPplContactItemManager::OwnCardIdL()
	{
	// Call contact table
	return  static_cast<CPplContactTable*>(iContactTable)->OwnCardIdL();		
	}
	
/**
Utility method used to set own card id
*/	
void CPplContactItemManager::SetOwnCardIdL(TContactItemId aId)
	{	
	// Call contact table
	static_cast<CPplContactTable*>(iContactTable)->SetOwnCardIdL(aId);		
	}

/**
Utility class. Returns an array with ids of all groups in contact database
*/
CContactIdArray* CPplContactItemManager::GroupIdListL()
	{
	CContactIdArray* idArray = CContactIdArray::NewLC();

	HBufC* selectString = HBufC::NewLC(KOneTypeField().Length() + KSqlContactTableName().Length() + KContactTypeFlags().Length() + 3); 
	TPtr ptrSelectString = selectString->Des();
	ptrSelectString.Format(KOneTypeField, &KContactId, &KSqlContactTableName, &KContactTypeFlags, EContactType_Shift, EContactTypeFlags_Group);

	RSqlStatement selectStatement;
	CleanupClosePushL(selectStatement);
	
	User::LeaveIfError(selectStatement.Prepare(iDatabase, ptrSelectString));
	const TInt KIdx = iSelectStatement->ParameterIndex(KContactId);
	
	TInt err;
	while((err = selectStatement.Next()) == KSqlAtRow)
		{
		idArray->AddL(selectStatement.ColumnInt(KIdx));	
		}

	if(err != KSqlAtEnd)
		{
		User::Leave(err);
		}

    CleanupStack::PopAndDestroy(&selectStatement);
	CleanupStack::PopAndDestroy(selectString);
	CleanupStack::Pop(idArray);
	
	return idArray;
	}

/**
Utility method used to rthe prefered card template id
*/
TInt CPplContactItemManager::CardTemplatePrefIdL() const
	{
	return iPreferencePersistor->PreferredCardTemplateIdL();
	}

/**
Utility method used to set the prefered card template id
*/
void CPplContactItemManager::SetCardTemplatePrefIdL(TInt aCardTemplatePrefId)
	{
	iPreferencePersistor->SetPreferredCardTemplateIdL(aCardTemplatePrefId);
	}
