// Copyright (c) 2004-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:
// This class contains the parsing and processing of BIO messages.
// This functionality was originally carried out in the BIO Server MTM but
// has been moved to the Client MTM for reasons of capability. Potentially,
// BIO parsers may need additional capabilities for their processing and it would
// be difficult to ensure that the Message Server (and all other server MTMs) had
// the same capabilities.  By moving the processing to the Client MTM it is only 
// the client UI app that needs the additional capabilities.
// Parsing and processing of a message is initiated by calling StartCommandL().
// The command function can be one of the following:
// KBiosMtmParse - just parse the message
// KBiosMtmParseThenProcess - parse the message and then process it
// KBiosMtmProcess - just process the mesage (must have been parsed previously)
// Parsing a message involves finding the relevant BIO parser, loading it and then calling
// ParseL() on the parser.
// Processing a message involves finding the relevant BIO parser, loading it and then
// calling ProcessL() on the parser.
// To avoid unresponsiveness, a parse-then-process command is carried out in two steps.
// The parsing is carried out first and then the AO is set up so the processing is
// carried out in a separate RunL().
// Both the parser ParseL() and parser ProcessL() are asynchronous methods.
// 
//

#include <e32uid.h>   	
#include <msvstd.h>
#include <biodb.h>		// bio database
#include <biouids.h>	// contains panic codes
#include <txtrich.h>
#include <txtfmlyr.h>

#include "BIOSCMDS.H"
#include "regpsdll.h"   // Parser Registry - used to load the parser
#include "bsp.h"		// CBaseScriptParser, CBaseScriptParser2
#include "BIOOP.H"
#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS
#include <biomessageuids.h>
#include "tmsvbioinfo.h"
#endif

// panic text
_LIT(KBioOpPanic, "BIOO");
	
CBIOOperation* CBIOOperation::NewL(RFs aRFs, CMsvSession& aSession, CBIODatabase* aBDB, TRequestStatus& aCompletionStatus)
	{
	CBIOOperation* self=new(ELeave) CBIOOperation(aRFs, aSession, aBDB, aCompletionStatus);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}


CBIOOperation::CBIOOperation(RFs aRFs, CMsvSession& aSession, CBIODatabase* aBDB, TRequestStatus& aCompletionStatus)
: CMsvOperation(aSession, EPriorityStandard, aCompletionStatus), 
iFs(aRFs), iBioDatabase(aBDB), iInOperation(EFalse)
	{
	}

	
void CBIOOperation::ConstructL()
//
// 2nd phase construction
//
	{
	// initiallise progress
	iProgress.iBioState = TBioProgress::EBiosWaiting;

	// create msv entry
	iCurrentEntry = CMsvEntry::NewL(iMsvSession, KMsvRootIndexEntryIdValue, TMsvSelectionOrdering());
	
	// add active object
	CActiveScheduler::Add(this);
	}


CBIOOperation::~CBIOOperation()
	{
	Cancel();
	delete iParser;
	delete iCurrentEntry;
	delete iRegisteredParserDll;
	delete iMessageBody;
	}


void CBIOOperation::StartCommand(const CMsvEntrySelection& aSelection, TInt aCommand, TRequestStatus& aStatus)
//
// BIOServer specific commands
//
	{
	__ASSERT_DEBUG(iBioOperationState == EBiooWaiting, User::Panic(KBioOpPanic, KBIOMessageOperationNotInactive));
	__ASSERT_ALWAYS(aSelection.Count() >= 1, User::Panic(KBioOpPanic, KBIOMessageNotFound));
	
	// to notify caller on completion
	iReportStatus = &aStatus;
	aStatus = KRequestPending;

	TMsvId service;
	TMsvEntry entry;

	TInt error = iMsvSession.GetEntry(aSelection.At(0), service, entry);

	iMtm = entry.iMtm;   
	iService = entry.iServiceId; 

	// perform the command, no need to go on if we already got an error
	if (error == KErrNone)
		{
		switch (aCommand)
			{
		case KBiosMtmParse:
			TRAP(error, ParseL(aSelection, EFalse));
			break;
		case KBiosMtmParseThenProcess:
			TRAP(error, ParseL(aSelection, ETrue));
			break;
		case KBiosMtmProcess:
			TRAP(error, ProcessL(aSelection));
			break;
		default:
			error = KErrNotSupported;
			break;
			}
		};
	
	// check for errors	
	if (error != KErrNone)
		{
		// complete
		iStatus = KRequestPending;
		TRequestStatus* stat = &iStatus;
		SetActive();
		User::RequestComplete(stat, error);
		}
	}


void CBIOOperation::DoCancel()
//
// Cancel
//
	{
	if (iParser)
		iParser->Cancel();
	iInOperation = EFalse;
	User::RequestComplete(iReportStatus, KErrCancel);
	}


void CBIOOperation::RunL()
//
// Active object RunL
//
	{
	TInt error = iStatus.Int();
	if (error == KErrNone)
		{
		switch (iBioOperationState)
			{
		case EBiooParsing:
			{
			iCurrentEntry->SetEntryL(iCurrentMsvId);

			if (iBioOperationOperation == EBioParseAndProcess)
				{
				iBioOperationState = EBiooWaiting;
				ProcessL();
				}
			else
				{
				iBioOperationState = EBiooComplete;
				}
			break;
			}
		case EBiooProcessing:
			iBioOperationState = EBiooComplete;
		case EBiooCreating:
		case EBiooWaiting:
		default:
			break;
			}
		}

	if (iBioOperationState == EBiooComplete || error != KErrNone)
		{
		RunError(error);
		}
	}


TInt CBIOOperation::RunError(TInt aError)
	{
	// also called with aError == KErrNone
	TMsvEntry entry = iCurrentEntry->Entry();
	entry.SetReadOnly(ETrue);
	TInt error = KErrNone;
	TRAP(error,iCurrentEntry->ChangeL(entry));
		
	iBioOperationState = EBiooWaiting;
	iInOperation = EFalse;
	iProgress.iErrorCode = aError;
		
	User::RequestComplete(iReportStatus, aError);
	
	return error;
	}


void CBIOOperation::ParseL(const CMsvEntrySelection& aSelection, TBool aCommit)
	{
	// parse and process?
	iBioOperationOperation = aCommit ? EBioParseAndProcess : EBioParseOnly;

	// set context
    iCurrentMsvId = aSelection[0];
	iCurrentEntry->SetEntryL(iCurrentMsvId);

	// check if it's alreadly parsed
	// iMtmData3 == 0 => not yet parsed
	// iMtmData3 == 1 => parsed
	// iMtmData3 == 2 => processed
	if (iCurrentEntry->Entry().MtmData3()==EBioMsgNotParsed)
		{
		// put in check that it's a valid SMS/smart message	
		StartParserL();
		}
	else if (iCurrentEntry->Entry().MtmData3()==EBioMsgParsed || 
			 iCurrentEntry->Entry().MtmData3()==EBioMsgProcessed)
		{
		iStatus = KRequestPending;
		TRequestStatus* ps = &iStatus;
		SetActive();
		User::RequestComplete(ps, KErrNone);
		iProgress.iBioState = TBioProgress::EBiosCreating;

		if (iBioOperationOperation == EBioParseAndProcess)
			{
			iBioOperationState = EBiooParsing;
			CreateParserL();
			}
		else
			{
			iBioOperationState = EBiooComplete;
			}
		}
	else
		{
		User::Leave(KErrCorrupt);
		}
	iInOperation = ETrue;
	}


void CBIOOperation::CreateParserL()
	{

	// get bio type uid from message	
	TUid parserUid;
	parserUid.iUid = iCurrentEntry->Entry().iBioType;

	// check if we already have a parser and if it's the correct one
	if ((iParser != NULL) && (parserUid != iParser->ParserUid()))
		{
		// first delete the old parser before creating the new one
		delete iParser;
		iParser = NULL;
		}
		
	if (iParser == NULL)
		{	
	  	// create the local variables 
		if (parserUid.iUid == 0)
			User::Leave(KBspSmartMessageNoParserDefined);
		
		// get parser filename for loading
		TFileName parserDllName(iBioDatabase->GetBioParserNameL(parserUid));
		
		// create dll registry
		delete iRegisteredParserDll;
		iRegisteredParserDll = NULL;
		iRegisteredParserDll = CRegisteredParserDll::NewL(parserDllName);

		// get handle to dll		
		RLibrary parserlibrary;
		User::LeaveIfError(iRegisteredParserDll->GetLibrary(iFs, parserlibrary));

		// create a typedef so that we can make a call to the dll to create a parser.
		typedef CBaseScriptParser2* (*NewParserL)(CRegisteredParserDll& registeredparserdll, CMsvEntry& aEntry, RFs& aFs);
		
		// entry point for NewL function found at ordinal 1
		TLibraryFunction libFunc = parserlibrary.Lookup(1);
		
		// check that we found the entry point
		if (libFunc == NULL) 
			User::Leave(KErrBadLibraryEntryPoint) ;

		// create a function pointer to NewL
		NewParserL pFunc = (NewParserL)libFunc;  
		CBaseScriptParser2* parser(NULL);
	
		TInt refcount = iRegisteredParserDll->DllRefCount();
		TRAPD(ret, parser = ((*pFunc)(*iRegisteredParserDll, *iCurrentEntry, iFs)));

	    // unload the library if allocation failed and refcount == 1
		if ((ret != KErrNone) && (iRegisteredParserDll->DllRefCount() == refcount))
			iRegisteredParserDll->ReleaseLibrary();
		
		// leave if error
		User::LeaveIfError(ret);
		iParser = parser;
		}
	}


void CBIOOperation::StartParserL()
//
// We have a parser parse the message (fire up the parser)
//
	{
	__ASSERT_DEBUG(iBioOperationState == EBiooWaiting, User::Panic(KBioOpPanic, KBIOMessageOperationNotInactive));
	
	// get the msg body
	ExtractMessageBodyL();

	// create parser
	CreateParserL();

	// set the entry and start the parser (parser takes ownership of entry)
    // make sure it points to the message
    iCurrentEntry->SetEntryL(iCurrentMsvId);

	// make the entry editable
	TMsvEntry entry(iCurrentEntry->Entry());
	entry.SetReadOnly(EFalse);
	iCurrentEntry->ChangeL(entry);
	
	// parse 
	iParser->ParseL(iStatus, *iMessageBody);
	SetActive();
	
	iBioOperationState = EBiooParsing;
	iProgress.iBioState = TBioProgress::EBiosParsing;
	}


void CBIOOperation::ExtractMessageBodyL()
//
// Get the message body
//
	{
	// build a CRichText object to read in message body
	CParaFormatLayer* paraFormatLayer = CParaFormatLayer::NewL();
    CleanupStack::PushL(paraFormatLayer);
    CCharFormatLayer* charFormatLayer = CCharFormatLayer::NewL();
    CleanupStack::PushL(charFormatLayer);
    CRichText* richText = CRichText::NewL(paraFormatLayer, charFormatLayer);
    CleanupStack::PushL(richText);

	// get store holding message body
    CMsvStore* store = iCurrentEntry->ReadStoreL();
	CleanupStack::PushL(store);

	// check for message body
    if (!store->HasBodyTextL())
    	User::Leave(KErrNotFound);
	
    // extract the body text
    store->RestoreBodyTextL(*richText);
    
    // create buffer to hold body text
	TInt messageLength = richText->DocumentLength();
	if (iMessageBody)
		{
		delete iMessageBody;
		iMessageBody = NULL;
		}
	iMessageBody = HBufC::NewL(messageLength);

	// read in message body
	TPtr messDes = iMessageBody->Des();
	TInt length = messDes.Length();
	while (length < messageLength)
		{
		TPtrC desc = richText->Read(length, messageLength-length);
		messDes.Append(desc);
		length+=desc.Length();
		}
	CleanupStack::PopAndDestroy(4, paraFormatLayer); // store, text, charFormatLayer, paraFormatLayer
	}


void CBIOOperation::ProcessL(const CMsvEntrySelection& aSelection)
//
// successfully parsed the message - do the settings
//
	{
	// set operation
	iBioOperationOperation = EBioProcess;

	// get the id of the bio message
    iCurrentMsvId = aSelection[0];
	iCurrentEntry->SetEntryL(iCurrentMsvId);

	CreateParserL();

	iInOperation = ETrue;
	ProcessL();
	}
	
	
void CBIOOperation::ProcessL()
//
// successfully parsed the message - do the settings
//
	{
	__ASSERT_DEBUG(iBioOperationState == EBiooWaiting, User::Panic(KBioOpPanic, KBIOMessageOperationNotInactive));

	if (iInOperation == EFalse)
		User::Leave(KErrDisconnected);
	
	__ASSERT_ALWAYS(iParser != NULL, User::Panic(KBioOpPanic, KBIOMessageNoParserCreated));
	iProgress.iBioState = TBioProgress::EBiosProcessing;
	
	// make entry readable so we can change things
	TMsvEntry entry = iCurrentEntry->Entry();
	if (entry.ReadOnly())
		{
		entry.SetReadOnly(EFalse);
		iCurrentEntry->ChangeL(entry);
		}

	// start to process message
	iParser->ProcessL(iStatus);
	SetActive();
	
	iBioOperationState = EBiooProcessing;
	}
	
	
const TDesC8& CBIOOperation::ProgressL()
//
// get progress information
//
	{
	iProgressBuf = TBioProgressBuf(iProgress);
	return iProgressBuf;
	}
