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

// System includes
#include <cntdb.h>
#include <cntdef.h>
#include <cntitem.h>
#include <cntfield.h>
#include <cntfldst.h>

// System includes
#include <e32std.h>
#include <e32twin.h>
#include <e32test.h>
#include <cntdbobs.h>

// User includes
#include "t_utils2.h"

// Constant
_LIT(KDatabaseFileName, "c:t_owncard.cdb");
_LIT(KSemaphoreNameOne,		"One");
_LIT(KSemaphoreNameTwo,		"Two");

_LIT(KThreadNameOne,		"OneThread");
_LIT(KThreadNameTwo,		"TwoThread");


const TInt KDelay = 1000000;

static RTest					TheTest(_L("t_owncard"));


//
/* CTestActiveScheduler                                                      */
//  This class has been nicked from the t_currentdb.h / .cpp files
class CTestActiveScheduler : public CActiveScheduler
	{
	public:
		void Error (TInt aError) const;
	};
void CTestActiveScheduler::Error(TInt aError) const
	{
	User::Panic(_L("AScheduler"),aError);
	}



//
/* CConcurrentDatabaseAccessBase - Specification                             */
// Forms the base class for both threads.
class CConcurrentDatabaseAccessBase : public CActive
	{
	public: // Data Types
		enum TStage
			{
			EOne_Startup,
			ETwo_FirstCheck,
			EThree_ChangeOwner,
			ENOP_One,
			ENOP_Two,
			EFour_SecondCheck,
			EFive_Cleanup,
			EFinished
			} ;
	public:
		CConcurrentDatabaseAccessBase();
		virtual ~CConcurrentDatabaseAccessBase();

		virtual void ConstructL(const TDesC& aFilename);

		void RunTestL();


	public: // data
		RThread					iThread;

	protected:
		void OpenSemaphore();
		void CloseSemaphore();
		void SyncronizeSignal();
		void SyncronizeWait();
		void OpenDatabaseL();
		void CloseDatabase();
		void CloseTest();

		virtual TBool RunTestImplementationL() = 0;

	protected: // from CActive
		void DoCancel() {};
		void RunL();
		TInt RunError(TInt aError);
		void CallRunLAgain();
		TBool NextStage();


	protected:
		CContactDatabase*		iDb;
		TStage					iStage;
		TInt					iTestError;

	private:
		RSemaphore				iSemaphoreSignal;
		RSemaphore				iSemaphoreWait;
		TBool					iSemaphoreOpen;
		HBufC*					iDatabaseName;
	};

//
/* CConcurrentDatabaseAccessBase - Implementation                            */
CConcurrentDatabaseAccessBase::CConcurrentDatabaseAccessBase()
	: CActive( /*EPriorityStandard*/ EPriorityIdle )
	{
	iStage = EOne_Startup;
	}

CConcurrentDatabaseAccessBase::~CConcurrentDatabaseAccessBase()
	{
	iThread.Close();
	CloseSemaphore();
	delete iDatabaseName;
	}

void CConcurrentDatabaseAccessBase::ConstructL(const TDesC& aFilename)
	{
	iDatabaseName = aFilename.AllocL();
	}

void CConcurrentDatabaseAccessBase::OpenDatabaseL()
	{
	iDb = CContactDatabase::OpenL(*iDatabaseName, CContactDatabase::EMultiThread);
	}

void CConcurrentDatabaseAccessBase::CloseDatabase()
	{
	delete iDb;
	iDb = NULL;
	}

void CConcurrentDatabaseAccessBase::CloseTest()
	{
	}

void CConcurrentDatabaseAccessBase::CallRunLAgain()
	{
	iStatus = KRequestPending;

	TRequestStatus* ptrStatus = &iStatus;
	User::RequestComplete( ptrStatus, KErrNone );
	SetActive();
	}

TBool CConcurrentDatabaseAccessBase::NextStage()
	{
	TInt stage = static_cast<TInt>( iStage );
	stage++;
	iStage = static_cast<TStage>( stage );
	return ( iStage == EFinished );
	}

void CConcurrentDatabaseAccessBase::RunL()
	{
	TBool stopProcessing = EFalse;

	stopProcessing = RunTestImplementationL();

	if (stopProcessing)
		{
		CActiveScheduler::Stop();
		}
	}

TInt CConcurrentDatabaseAccessBase::RunError(TInt aError)
	{
	// propagate the error
	iTestError = aError;

	RDebug::Print(_L("Thread failed at test stage %i, with error %i"), iStage, aError);

	CActiveScheduler::Stop();
	return KErrNone;
	}

void CConcurrentDatabaseAccessBase::RunTestL()
	{
	// set's up an active scheduler for the thread
	// and calls the RunTesImplementation function to actually
	// preform the tests. This function should be implemented in the
	// child class.
	CTestActiveScheduler*  scheduler = NULL;

	scheduler = new (ELeave) CTestActiveScheduler;
	CleanupStack::PushL(scheduler);
	CActiveScheduler::Install(scheduler);

	CActiveScheduler::Add( this );

	OpenDatabaseL();

	CallRunLAgain();
	CActiveScheduler::Start();

	CloseDatabase();

	CleanupStack::PopAndDestroy( scheduler );

	// propagate error
	User::LeaveIfError(iTestError);
	}

void CConcurrentDatabaseAccessBase::OpenSemaphore()
	{
	TInt success = KErrNone;

	success = iSemaphoreSignal.OpenGlobal( KSemaphoreNameOne );
	if ( success == KErrNotFound )
		{
		iSemaphoreSignal.CreateGlobal( KSemaphoreNameOne, 0 );
		success = KErrNone;
		}


	success = iSemaphoreWait.OpenGlobal( KSemaphoreNameTwo );
	if ( success == KErrNotFound )
		{
		iSemaphoreWait.CreateGlobal( KSemaphoreNameTwo, 0 );
		}

	iSemaphoreOpen = ETrue;
	}

void CConcurrentDatabaseAccessBase::CloseSemaphore()
	{
	if (iSemaphoreOpen)
		{
		iSemaphoreSignal.Close();
		iSemaphoreWait.Close();
		}

	iSemaphoreOpen = EFalse;
	}

void CConcurrentDatabaseAccessBase::SyncronizeSignal()
	{
	CallRunLAgain();
	iSemaphoreSignal.Signal();
	iSemaphoreWait.Wait();
	}

void CConcurrentDatabaseAccessBase::SyncronizeWait()
	{
	CallRunLAgain();
	iSemaphoreSignal.Wait();
	iSemaphoreWait.Signal();
	}

//
/* CConcurrentOwnerCardModifier - Specification                               */
// This class should allow the two threads to compare the owner ID's of there db.
class TOwnerCardCompare
	{
	public:
	enum EThreadName
		{
			EModifier,
			EChecker
		};
	public:
	TOwnerCardCompare();
	~TOwnerCardCompare();
	void			AssignValuesAndCheckL(TContactItemId aValue, EThreadName aThreadName);

	private:
	void			CheckValuesL();
	void			ClearValues();

	private: // data
	TBool			iModifierDataPresent;
	TBool			iCheckerDataPresent;
	TContactItemId	iModifierValue;
	TContactItemId	iCheckerValue;
	};

//
/* CConcurrentOwnerCardModifier - Implementatio                              */
// This class should allow the two threads to compare the owner ID's of there db.
TOwnerCardCompare::TOwnerCardCompare()
	{
	ClearValues();
	}
TOwnerCardCompare::~TOwnerCardCompare()
	{
	ClearValues();
	}

void TOwnerCardCompare::AssignValuesAndCheckL(TContactItemId aValue, EThreadName aThreadName)
	{
	switch ( aThreadName )
		{
		case EModifier:
			iModifierDataPresent = ETrue;
			iModifierValue = aValue;
		break;

		case EChecker:
			iCheckerDataPresent = ETrue;
			iCheckerValue = aValue;
		break;
		}
	CheckValuesL();
	}

void TOwnerCardCompare::CheckValuesL()
	{
	if ( iModifierDataPresent && iCheckerDataPresent )
		{
		TheTest( (iCheckerValue == iModifierValue) );
		ClearValues();
		}
	}

void TOwnerCardCompare::ClearValues()
	{
	iCheckerDataPresent = EFalse;
	iModifierDataPresent = EFalse;
	iCheckerValue = 0;
	iModifierValue = 0xFFFFFFFF;
	}

//
/* CConcurrentOwnerCardModifier - Specification                               */
// This class should create the db, and set the owner card.
class CConcurrentOwnerCardModifier : public CConcurrentDatabaseAccessBase
	{
	public:
		CConcurrentOwnerCardModifier(TOwnerCardCompare& aOwnerCardCompare);
		~CConcurrentOwnerCardModifier();

		static CConcurrentOwnerCardModifier* NewLC(const TDesC& aFilename, TOwnerCardCompare& aOwnerCardCompare);
		virtual void ConstructL(const TDesC& aFilename);

		static TInt ThreadFunction(TAny* aSelf);


	protected:
		TBool	RunTestImplementationL();

	private:
		void					AssignAndCompareOwnerCardIdL(TContactItemId aOwner);

	private:
		TOwnerCardCompare&		iOwnerCardCompare;
	};

//
/* CConcurrentOwnerCardModifier - Implementation                              */

CConcurrentOwnerCardModifier* CConcurrentOwnerCardModifier::NewLC(const TDesC& aFilename, TOwnerCardCompare& aOwnerCardCompare)
	{
	CConcurrentOwnerCardModifier* self = NULL;
	self = new (ELeave) CConcurrentOwnerCardModifier(aOwnerCardCompare);
	CleanupStack::PushL( self );
	self->ConstructL(aFilename);
	return self;
	}

CConcurrentOwnerCardModifier::CConcurrentOwnerCardModifier(TOwnerCardCompare& aOwnerCardCompare)
	: iOwnerCardCompare( aOwnerCardCompare )
	{
	}

CConcurrentOwnerCardModifier::~CConcurrentOwnerCardModifier()
	{
	}

TInt CConcurrentOwnerCardModifier::ThreadFunction(TAny* aSelf)
	{
	CConcurrentOwnerCardModifier* self = STATIC_CAST(CConcurrentOwnerCardModifier*, aSelf);

	// Prepare the stuff required before we start the
	// active scheduler.
	TInt error = KErrNone;
    CTrapCleanup* cleanup = CTrapCleanup::New();
	if	(!cleanup)
		return KErrNoMemory;

	// Call virtual handler which does anything that needs doing as
	// a result of the thread function from being created.
	TRAP(error, self->RunTestL());


    delete cleanup;
	return error;
	}

void CConcurrentOwnerCardModifier::ConstructL(const TDesC& aFilename)
	{
	CConcurrentDatabaseAccessBase::ConstructL(aFilename);

	iThread.Create( KThreadNameOne, CConcurrentOwnerCardModifier::ThreadFunction, KDefaultStackSize, 0x2000, 0x20000, this, EOwnerThread );
	iThread.Resume();
	iThread.SetPriority(/*EPriorityNormal*/EPriorityAbsoluteBackground);
	}

// 1. Set up
// 2. Check current owner card.
// 3. Sync
// 4. modify owner card
// 5. Sync
// 6. Check owner card
// 7. Sync
// 8. Finished.
TBool CConcurrentOwnerCardModifier::RunTestImplementationL()
	{
	TInt bit = 0;
	TContactItemId id;
	CRandomContactGenerator* generator = NULL;
	CContactItem* item = NULL;
	TBool retval = EFalse;

	switch( iStage )
		{
		case EOne_Startup:
			OpenSemaphore();
			retval = NextStage();
			SyncronizeWait(); // pause until corresponding Searcher thread is ready.
		break;

		case ETwo_FirstCheck:
			id = iDb->OwnCardId();
			AssignAndCompareOwnerCardIdL( id );
			retval = NextStage();
			SyncronizeWait();
		break;

		case EThree_ChangeOwner:
			generator = CRandomContactGenerator::NewL();
			CleanupStack::PushL( generator );
			generator->SetDbL(*iDb);

			bit |= CContactDatabase::ESmsable;
			id = generator->AddTypicalContactForFilterL(bit);
			item = iDb->ReadContactL( id );
			CleanupStack::PushL( item );
			iDb->SetOwnCardL( *item );
			CleanupStack::PopAndDestroy( item );
			item = NULL;
			CleanupStack::PopAndDestroy( generator );
			generator = NULL;
			retval = NextStage();
			SyncronizeWait(); // wait here.
		break;

		case ENOP_One:
			CallRunLAgain();
			retval = NextStage();
		break;

		case ENOP_Two:
			CallRunLAgain();
			retval = NextStage();
		break;

		case EFour_SecondCheck:
			iDb->CountL();
			id = iDb->OwnCardId();
			AssignAndCompareOwnerCardIdL( id );
			retval = NextStage();
			SyncronizeWait();
		break;

		case EFive_Cleanup:
			CloseSemaphore();
			CloseTest();
			retval = ETrue;
		break;

		default:
		break;
		}

	return retval;
	}


void CConcurrentOwnerCardModifier::AssignAndCompareOwnerCardIdL(TContactItemId aOwner)
	{
	//SyncronizeWait();
	iOwnerCardCompare.AssignValuesAndCheckL( aOwner, TOwnerCardCompare::EModifier );
	//SyncronizeWait();
	//SyncronizeWait();
	}

//
/* CConcurrentOwnerCardChecker                                               */
class CConcurrentOwnerCardChecker : public CConcurrentDatabaseAccessBase
	{
	public:
		CConcurrentOwnerCardChecker(TOwnerCardCompare& aOwnerCardCompare);
		~CConcurrentOwnerCardChecker();

		static CConcurrentOwnerCardChecker* NewLC(const TDesC& aFilename, TOwnerCardCompare& aOwnerCardCompare);
		virtual void ConstructL(const TDesC& aFilename);
		static TInt ThreadFunction(TAny* aSelf);
		static TInt FakeFunction(TAny *aParams);



	protected:
		TBool	RunTestImplementationL();

	private:
		void	AssignAndCompareOwnerCardIdL(TContactItemId aOwner);

	private:
		TOwnerCardCompare& iOwnerCardCompare;
	};

CConcurrentOwnerCardChecker* CConcurrentOwnerCardChecker::NewLC(const TDesC& aFilename, TOwnerCardCompare& aOwnerCardCompare)
	{
	CConcurrentOwnerCardChecker* self = NULL;
	self = new (ELeave) CConcurrentOwnerCardChecker(aOwnerCardCompare);
	CleanupStack::PushL( self );
	self->ConstructL(aFilename);
	return self;
	}

CConcurrentOwnerCardChecker::CConcurrentOwnerCardChecker(TOwnerCardCompare& aOwnerCardCompare)
	: iOwnerCardCompare( aOwnerCardCompare )
	{
	}

CConcurrentOwnerCardChecker::~CConcurrentOwnerCardChecker()
	{
	}

TInt CConcurrentOwnerCardChecker::ThreadFunction(TAny* aSelf)
	{
	CConcurrentOwnerCardChecker* self = static_cast<CConcurrentOwnerCardChecker*>(aSelf);

	// Prepare the stuff required before we start the
	// active scheduler.
	TInt error = KErrNone;

    CTrapCleanup* cleanup = CTrapCleanup::New();
	if	(!cleanup)
		return KErrNoMemory;

	// Call virtual handler which does anything that needs doing as
	// a result of the thread function from being created.
	TRAP(error, self->RunTestL());


    delete cleanup;
	return error;
	}

void CConcurrentOwnerCardChecker::ConstructL(const TDesC& aFilename)
	{
	CConcurrentDatabaseAccessBase::ConstructL(aFilename);

	iThread.Create( KThreadNameTwo, CConcurrentOwnerCardChecker::ThreadFunction, KDefaultStackSize, 0x2000, 0x20000, this, EOwnerThread );
	iThread.Resume();
	iThread.SetPriority(/*EPriorityNormal*/EPriorityAbsoluteBackground);

	}


TInt CConcurrentOwnerCardChecker::FakeFunction(TAny* /*aParams*/)
	{
	return(KErrNone);
	}

TBool CConcurrentOwnerCardChecker::RunTestImplementationL()
	{
	// Prep, and get ready to run. Then before starting the search loop
	// wait for the other thread.
	TContactItemId id;
	TBool retval = EFalse;

	switch( iStage )
		{
		case EOne_Startup:
			//TCallBack callBack(FakeFunction);
			OpenSemaphore();
			retval = NextStage();
			SyncronizeSignal(); // wait for the other thread.
		break;

		case ETwo_FirstCheck:
			id = iDb->OwnCardId();
			AssignAndCompareOwnerCardIdL( id );
			retval = NextStage();
			SyncronizeSignal();
		break;

		case EThree_ChangeOwner:
			retval = NextStage();
			SyncronizeSignal();
		break;

		case ENOP_One:
			CallRunLAgain();
			retval = NextStage();
		break;

		case ENOP_Two:
			CallRunLAgain();
			retval = NextStage();
		break;

		case EFour_SecondCheck:
			iDb->CountL();
			id = iDb->OwnCardId();
			AssignAndCompareOwnerCardIdL( id );
			retval = NextStage();
			SyncronizeSignal();
		break;

		case EFive_Cleanup:
			CloseSemaphore();
			CloseTest();
			retval = NextStage();
		break;

		default:
		break;
		}

	return retval;
	}


void CConcurrentOwnerCardChecker::AssignAndCompareOwnerCardIdL(TContactItemId aOwner)
	{
	//SyncronizeSignal();
	//SyncronizeSignal();
	iOwnerCardCompare.AssignValuesAndCheckL( aOwner, TOwnerCardCompare::EChecker );
	//SyncronizeSignal();
	}

//
/* Test Function Prototypes                                                  */
//
void CreateTestDatabaseL(const TDesC& aFilename);
void TestL();
void doMainL();

//
/* Test Function Implementations                                             */
//
void CreateTestDatabaseL(const TDesC& aFilename )
	{
	TInt counter = 0;
	TInt bit = 0;
	CContactDatabase* database = NULL;
	CRandomContactGenerator* generator = NULL;
	CContactItem* ownerCardItem = NULL;
	TContactItemId ownerCard = 0;

	database = CContactDatabase::ReplaceL(aFilename);
	CleanupStack::PushL( database );

	generator = CRandomContactGenerator::NewL();
	CleanupStack::PushL( generator );

	generator->SetDbL(*database);


	bit |= CContactDatabase::ESmsable;
	ownerCard = generator->AddTypicalContactForFilterL(bit);
	TheTest.Printf(_L("Adding SMS contacts %d\n"), counter);


	ownerCardItem = database->ReadContactL( ownerCard );
	CleanupStack::PushL( ownerCardItem );
	database->SetOwnCardL( *ownerCardItem );
	CleanupStack::PopAndDestroy( ownerCardItem ); ownerCardItem = NULL;

	CleanupStack::PopAndDestroy( generator );
	CleanupStack::PopAndDestroy( database );
	}


void TestL()
	{
	TRequestStatus thread1;
	TRequestStatus thread2;
	TOwnerCardCompare compareTool;
	CConcurrentOwnerCardChecker* searcher = NULL;
	CConcurrentOwnerCardModifier* inserter = NULL;

	CreateTestDatabaseL( KDatabaseFileName );

	searcher = CConcurrentOwnerCardChecker::NewLC( KDatabaseFileName, compareTool );
	User::After(KDelay); // so that checker thread created first
	inserter = CConcurrentOwnerCardModifier::NewLC( KDatabaseFileName, compareTool );

	searcher->iThread.Logon(thread1);
	inserter->iThread.Logon(thread2);

	User::WaitForRequest(thread2);
	User::WaitForRequest(thread1);

	CleanupStack::PopAndDestroy( inserter );
	CleanupStack::PopAndDestroy( searcher );

	// fail if either thread failed
	User::LeaveIfError(thread1.Int());
	User::LeaveIfError(thread2.Int());
	}


//
// -------> Static global functions (source)
//
void doMainL()
	{
	RFs fs;
	User::LeaveIfError(fs.Connect());
	CleanupClosePushL(fs);
	CTestRegister * TempFiles = CTestRegister::NewLC();
	TempFiles->RegisterL(KDatabaseFileName, EFileTypeCnt);

	CTestActiveScheduler*  scheduler = new (ELeave) CTestActiveScheduler;
	CleanupStack::PushL(scheduler);
	CActiveScheduler::Install(scheduler);



	// Delete any existing ini file so that we can be sure this test is ok
	TestL();

	CleanupStack::PopAndDestroy( 3 ); // scheduler, tempfiles, fs

	}


/**

@SYMTestCaseID     PIM-T-OWNCARD-0001

*/

GLDEF_C TInt E32Main()
	{
	CTrapCleanup* cleanupStack = NULL;
	__UHEAP_MARK;
	TheTest.Start(_L("@SYMTESTCaseID:PIM-T-OWNCARD-0001 Multi session testcode"));

	TheTest.Title();
	cleanupStack = CTrapCleanup::New();
	TRAPD(ret, doMainL());
	TheTest(ret == KErrNone);
	delete cleanupStack;

	TheTest.End();
	TheTest.Close();
	__UHEAP_MARKEND;
	return(KErrNone);
	}
