/*
* Copyright (c) 2007 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:  CS Message Handler, This class caches the conversation server
 *                with the message data and also sends run-time updates to the
 *                server with the message data
 *
*/


// USER INCLUDES
#include "ccsmsghandler.h"
#include "msgbiouids.h"
#include "ccsdefs.h"

// SYSTEM INCLUDES
#include <smsclnt.h>                   
#include <mmsclient.h>
#include <mtclreg.h>
#include <csmsemailfields.h>
#include <txtrich.h>
#include <msvids.h>
#include <e32const.h>
#include <SendUiConsts.h>
#include <pushentry.h> // For services messages
#include <e32cmn.h>


//CONSTANTS
const TInt KMessageArrayGranularity = 50;

// NOTE:- DRAFT ENTRIES ARE NOT HANDLED IN THE PLUGIN

// ============================== MEMBER FUNCTIONS ============================
// ----------------------------------------------------------------------------
// CCsMsgHandler::NewL
// Two Phase Construction
// ----------------------------------------------------------------------------
//
CCsMsgHandler* CCsMsgHandler::NewL(MCsMsgObserver *aMsgObserver)
    {
    PRINT ( _L("Enter CCsMsgHandler::NewL") );

    CCsMsgHandler* self = new ( ELeave ) CCsMsgHandler();
    CleanupStack::PushL( self );
    self->ConstructL(aMsgObserver);
    CleanupStack::Pop( self );

    PRINT ( _L("End CCsMsgHandler::NewL") );

    return self;
    }

// ----------------------------------------------------------------------------
// CCsMsgHandler::~CCsMsgHandler
// Destructor
// ----------------------------------------------------------------------------
//
CCsMsgHandler::~CCsMsgHandler()
    {
    PRINT ( _L("Enter CCsMsgHandler::~CCsMsgHandler") );
    if(iIdle)
        {
        iIdle->Cancel();
        delete iIdle;
        iIdle=NULL;
        }
    if(iMsgPluginUtility)
        {
        delete iMsgPluginUtility;
        iMsgPluginUtility = NULL;
        }

    if(iConverstationEntryList)
        {
        iConverstationEntryList->ResetAndDestroy();
        iConverstationEntryList->Close();
        delete iConverstationEntryList;
        iConverstationEntryList = NULL;
        }

    if(iSmsMtm)
        {
        delete iSmsMtm;
        iSmsMtm = NULL;
        }

    if(iMmsMtm)
        {
        delete iMmsMtm;
        iMmsMtm = NULL;
        }

    if(iMtmRegistry)
        {
        delete iMtmRegistry;
        iMtmRegistry = NULL;
        }

    if(iSession)
        {
        delete iSession;
        iSession = NULL;
        }

    if(iMessageArray)
    {
        iMessageArray->Reset();
        iMessageArray->Close();
        delete iMessageArray;
        iMessageArray = NULL;
    }

    PRINT ( _L("End CCsMsgHandler::~CCsMsgHandler") );
    }

// ----------------------------------------------------------------------------
// CCsMsgHandler::ConstructL
// Two Phase Construction
// ----------------------------------------------------------------------------
//
void CCsMsgHandler::ConstructL(MCsMsgObserver *aMsgObserver)
    {
    PRINT ( _L("Enter CCsMsgHandler::ConstructL") );

    iMsgObserver = aMsgObserver;

    iSession = CMsvSession::OpenSyncL(*this);

    iMsgPluginUtility = CCsMsgPluginUtility::NewL();

    iConverstationEntryList = new(ELeave) RPointerArray<CCsConversationEntry>(1);

    iMtmRegistry = CClientMtmRegistry::NewL( *iSession );

    iSmsMtm = static_cast<CSmsClientMtm*>( iMtmRegistry->
            NewMtmL( KSenduiMtmSmsUid ) );

    iMmsMtm = static_cast<CMmsClientMtm*>( iMtmRegistry->
            NewMtmL( KSenduiMtmMmsUid ) );

    iState = EReadInbox;

    iMessageArray = new (ELeave)RArray <TMsvId>(KMessageArrayGranularity);

    PRINT ( _L("End CCsMsgHandler::ConstructL") );
    }

// ----------------------------------------------------------------------------
// CCsMsgHandler::CCsMsgHandler
// Two Phase Construction
// ----------------------------------------------------------------------------
//
CCsMsgHandler::CCsMsgHandler()
    {
    }

// -----------------------------------------------------------------------------
// CCsMsgHandler::ProcessResultsL()
// ProcessResultsL fetches results for the searchsort query and
// updates the CCsServer with the fetched data
// -----------------------------------------------------------------------------
//
void CCsMsgHandler::ProcessResultsL(TMsvEntry entry)
    {
    PRINT ( _L("Enter CCsMsgHandler::ProcessResultsL"));

    // Cleanup the conversationEntry List initially.
    // So that the previous entries not cleaned up in
    // the event of Leave have been clean up.
    iConverstationEntryList->ResetAndDestroy();

    RPointerArray<HBufC> addressList;

    // Ignore hidden items during upload
    if ( entry.Visible() == EFalse )
        {
        return;
        }
    
    if ( entry.Parent() == KMsvGlobalInBoxIndexEntryIdValue ||
         entry.Parent() == KMsvSentEntryIdValue ||
         entry.Parent() == KMsvGlobalOutBoxIndexEntryIdValue )
        {
        CleanupResetAndDestroyPushL(addressList);
        
        iMsgPluginUtility->CreateContactL(iSession, entry, addressList);

        //process entry
        ExtractAddressesL(entry, ERead, addressList);
        
        // Cleanup addressList
        CleanupStack::PopAndDestroy(&addressList);
        }

    PRINT ( _L("Exit CCsMsgHandler::ProcessResultsL") );
    }

// ----------------------------------------------------------------------------
// CCsMsgHandler::HandleSessionEventL
// Implemented for MMsvSessionObserver
// ----------------------------------------------------------------------------
//
void CCsMsgHandler::HandleSessionEventL( TMsvSessionEvent aEvent,
        TAny* aArg1,
        TAny* aArg2,
        TAny* /*aArg3*/)
    {
    PRINT1 ( _L("Enter CCsMsgHandler::HandleSessionEventL aEvent=%d"),aEvent );

    CMsvEntrySelection* selection=NULL;
    TMsvId parent;

    //args
    if(aArg1 == NULL || aArg2 == NULL)
        {
        PRINT ( _L("Enter CCsMsgHandler::HandleSessionEventL arguments invalid"));
        return;
        }

    //start, processing the event
    selection= (CMsvEntrySelection*)aArg1;
    parent = *(TMsvId*)aArg2;

    // Cleanup the conversationEntry List initially.
    iConverstationEntryList->ResetAndDestroy();

    switch( aEvent )
        {
        case EMsvEntriesChanged:
        case EMsvEntriesMoved:
            {
            HandleEventL(selection,parent);
            }
            break;
            
        case EMsvEntriesDeleted:
            {
            if ( parent == KMsvGlobalInBoxIndexEntryIdValue || 
                 parent == KMsvSentEntryIdValue ||
                 parent == KMsvGlobalOutBoxIndexEntryIdValue)
                {
                for( TInt i=0 ; i < selection->Count() ; i++ )
                    {
                    TMsvId id = selection->At( i );

                    //use utility to create conversation entry
                    CCsConversationEntry *conversationEntry = 
                    iMsgPluginUtility->CreateConversationEntryL(
                            NULL,
                            id,
                            0,
                            parent,
                            NULL,
                            ECsSendStateUnknown,
                            ECsAttributeNone,
                            0);
                    CleanupStack::PushL(conversationEntry);
                    iConverstationEntryList->AppendL( conversationEntry );
                    CleanupStack::Pop(conversationEntry);

                    // call observer interface for each entry
                    iMsgObserver->HandleDeleteCompleteL(iConverstationEntryList);

                    //cleanup before next iteration
                    iConverstationEntryList->ResetAndDestroy();
                    }
                }
            }
            break;
            
        case EMsvMediaChanged:
            iMsgObserver->HandleRefreshCompleteL();
            break;
        }

    // Cleanup the conversationEntry List before function exits.
    iConverstationEntryList->ResetAndDestroy();
    PRINT ( _L("Exit CCsMsgHandler::HandleSessionEventL") );
    }

// ---------------------------------------------------------------------
// CCsMsgHandler::HandleEvent
// Handle events
// ---------------------------------------------------------------------
//
void CCsMsgHandler::HandleEventL(CMsvEntrySelection* aSelection, TMsvId aParent)
    {
    PRINT ( _L("Enter CCsMsgHandler::HandleEvent") );
    TMsvEntry entry;
    TMsvId service;
    TInt error= KErrNone;
    RPointerArray<HBufC> addressList;

    for( TInt i=0 ; i < aSelection->Count() ; i++ )
        {
        error = iSession->GetEntry(aSelection->At(i),service,entry);
        
        if ( entry.Visible() == EFalse )
            {
            // Do a delete if entry becomes invisible.
            // e.g) My Nokia registration messages.
            RPointerArray<CCsConversationEntry>* hiddenEntries =  
                    new (ELeave) RPointerArray<CCsConversationEntry>();
            
            CCsConversationEntry *conversationEntry = 
                    iMsgPluginUtility->CreateConversationEntryL ( 
                            NULL,
                            entry.Id(),
                            0,
                            aParent,
                            NULL,
                            ECsSendStateUnknown,
                            ECsAttributeNone,
                            0 );  
            CleanupStack::PushL(conversationEntry);
            hiddenEntries->AppendL( conversationEntry );
            CleanupStack::Pop(conversationEntry);
            
            // Delete at server
            iMsgObserver->HandleDeleteCompleteL(hiddenEntries);
            hiddenEntries->ResetAndDestroy();
            hiddenEntries->Close();
            delete hiddenEntries;
            
            break;
            }
        
        if ( (KErrNone == error) && (IsMtmSupported(entry.iMtm.iUid) ) )
            {
            // sent/inbox/Outbox entry
            if ( aParent == KMsvSentEntryIdValue ||
                 aParent == KMsvGlobalInBoxIndexEntryIdValue )
                {
                CleanupResetAndDestroyPushL(addressList);
                // currently server needs number, so getting it from header
                iMsgPluginUtility->CreateContactL(iSession, entry,
                        addressList);

                //process entry
                // entry created in sent already exists, hence an update
                ExtractAddressesL(entry, EUpdate, addressList);

                CleanupStack::PopAndDestroy(&addressList);
                }  
                
            //for drafts and outbox, the entry changes multiple times,
            // so making a check if entry has changed
            else if(aParent == KMsvGlobalOutBoxIndexEntryIdValue)
                {
                //outbox
                if(!(iMsgPluginUtility->CompareEntry(iPrevEntry,entry,aParent)))
                    {
                    TUint sendState = entry.SendingState();
                    
                    // if the entry state is sent, we dont need to process it
                    // as it is going to appear in sent items
                    if( ECsSendStateSent != iMsgPluginUtility->GetSendState(entry) )
                        {
                         CleanupResetAndDestroyPushL(addressList);
                        // currently server needs number, so getting it from header
                        iMsgPluginUtility->CreateContactL(iSession, entry,
                                addressList);
                        
                        //process entry
                        // entry created in sent already exists, hence an update
                        ExtractAddressesL(entry, EUpdate, addressList);
                        
                        iPrevEntry = entry;
                        
                        CleanupStack::PopAndDestroy(&addressList);
                        }
                    }//end check changed entry
                }
            }
        }//end for loop

    PRINT ( _L("Exit CCsMsgHandler::HandleEvent") );
    }

// ----------------------------------------------------------------------------
// CCsMsgHandler::CreateAndAddEntryL
// Creates CCsConversationEntry and adds to the list
// ----------------------------------------------------------------------------
//
void CCsMsgHandler::CreateAndAddEntryL(const TDesC& aContact, 
        const TDesC& aDescription, 
        const TMsvEntry& aEntry)
    {
    PRINT ( _L("Enter CCsMsgHandler::CreateAndAddEntryL") );

    HBufC* contact = NULL;
    HBufC* sDescription = NULL;

    if (aContact.Length()>0)
        {
        contact = aContact.AllocLC();
        }
    if (aDescription.Length()>0)
        {
        sDescription = aDescription.AllocLC();
        }

    //use utility to create conversation entry
    CCsConversationEntry *conversationEntry = iMsgPluginUtility->CreateConversationEntryL(
            contact,
            aEntry.Id(),
            aEntry.iDate.Int64(),
            aEntry.Parent(),
            sDescription,
            iMsgPluginUtility->GetSendState(aEntry),
            iMsgPluginUtility->GetMsgAttributes(aEntry),
            ExtractCsType(aEntry));
    CleanupStack::PushL(conversationEntry);

    //add to the list
    iConverstationEntryList->AppendL( conversationEntry );
    CleanupStack::Pop(conversationEntry);

    // cleanup
    if (sDescription)
        {
        CleanupStack::PopAndDestroy(sDescription);
        }
    if (contact)
        {
        CleanupStack::PopAndDestroy(contact);
        }

    PRINT ( _L("Exit CCsMsgHandler::CreateAndAddEntryL") );
    }

// ----------------------------------------------------------------------------
// CCsMsgHandler::ProcessEntryL
// Creates CCsConversationEntry and adds to the list
// ----------------------------------------------------------------------------
//
void CCsMsgHandler::ProcessEntryL( TEventType aEvent, const TDesC& aContact, 
        const TDesC& aDescription, const TMsvEntry& aEntry)
    {
    PRINT ( _L("Enter CCsMsgHandler::ProcessEntryL") );

    // create and add entry to the list
    CreateAndAddEntryL(aContact,aDescription,aEntry);

    // call cs observer interface for each entry
    switch(aEvent)
        {
        case ERead:
            {
            iMsgObserver->HandleReadCompleteL(iConverstationEntryList);
            }
            break;
        case EUpdate:
            {
            iMsgObserver->HandleUpdateCompleteL(iConverstationEntryList);
            }
            break;
        }

    // Cleanup the conversationEntry List, as we are just sending one entry at a time
    iConverstationEntryList->ResetAndDestroy();

    PRINT ( _L("Exit CCsMsgHandler::ProcessEntryL") );
    }

// ----------------------------------------------------------------------------
// CCsMsgHandler::ExtractAddressL
// Extracts the addresses in the to field and updates them to server
// ----------------------------------------------------------------------------
//
void CCsMsgHandler::ExtractAddressesL( 
        TMsvEntry aEntry, 
        TEventType aEvent, 
        RPointerArray<HBufC>& addressList )
    {
    PRINT ( _L("Enter CCsMsgHandler::ExtractAddressesL") );

    // For SMS read the whole body.
    // For bio type SMS read the iDescription
    // For other message types read the iDescription
    // Note: After the LoadMessageL() the iDescription is getting 
    // deleted so need to make a copy before that.
    TPtrC description;
    HBufC* tmpBuffer = NULL;

    if ( (aEntry.iBioType && 
	      aEntry.iBioType != KUidMsgSubTypeMmsAudioMsg.iUid ) || 
		  aEntry.iMtm == KSenduiMtmBtUid ) 
        {
        tmpBuffer = aEntry.iDescription.AllocLC();
        description.Set( tmpBuffer->Des() );         
        }    
    else if ( aEntry.iMtm == KSenduiMtmSmsUid )  
        {
        iSmsMtm->SwitchCurrentEntryL( aEntry.Id() );
        iSmsMtm->LoadMessageL();
            
        CRichText& body = iSmsMtm->Body();
        TInt smsLength = body.DocumentLength();      
        tmpBuffer = HBufC::NewLC(smsLength);
        TPtr ptr(tmpBuffer->Des());
        body.Extract(ptr, 0);
        description.Set( tmpBuffer->Des() );  
        }
    else if ( aEntry.iMtm == KSenduiMtmMmsUid  || 
	          aEntry.iMtm == KSenduiMMSNotificationUid)  
        {
        tmpBuffer = aEntry.iDescription.AllocLC();
        description.Set( tmpBuffer->Des() );         

        iMmsMtm->SwitchCurrentEntryL( aEntry.Id() );
        iMmsMtm->LoadMessageL();

        // Handle addresses
        if ( aEntry.Parent() == KMsvGlobalInBoxIndexEntryIdValue )
            {
            HBufC* address = iMmsMtm->Sender().AllocL();
            addressList.Append(address);
            }
        else if ( aEntry.Parent() == KMsvSentEntryIdValue ||
                  aEntry.Parent() == KMsvGlobalOutBoxIndexEntryIdValue )
            {
            const CMsvRecipientList& addresses = iMmsMtm->AddresseeList();
            TInt count = addresses.Count();

            for( TInt i = 0; i < count; i++)
                {
                HBufC* address = addresses[i].AllocL();
                TPtrC pureAddress = iMsgPluginUtility->PureAddress(*address);
                HBufC* address1 = pureAddress.AllocL();
                addressList.Append(address1);
                delete address;
                }
            }
        }
    else
        {
        return;
        }

    if ( addressList.Count() > 0 )
        {
        for ( int i = 0; i < addressList.Count(); i++ )
            {
            HBufC* address = addressList[i];
            ProcessEntryL(aEvent, *address, description, aEntry);
            }
        }
    else
        {
        // Unknown
        ProcessEntryL(aEvent, KNullDesC, description, aEntry);       
        }
    
    //Cleanup tmpBuffer
    if ( tmpBuffer )
        {
		 CleanupStack::PopAndDestroy();
        }

    PRINT ( _L("Exit CCsMsgHandler::ExtractAddressesL") );
    }

// -----------------------------------------------------------------------------
// CCsMsgHandler::CompareOrder()
// Method for determining the sorting behaviour of RArray of TMsvId's  
// -----------------------------------------------------------------------------
//
static TInt CompareOrder(const TMsvId& aFirst, const TMsvId& aSecond)
    {
    return aSecond - aFirst;
    }
// -----------------------------------------------------------------------------
// CCsMsgHandler::UploadMsgL()
// State machine to upload all messages 
// -----------------------------------------------------------------------------
//
TInt CCsMsgHandler::UploadMsgL() 
    {
    //Release scheduler for scheduling.    
    User::After(1);
    
    switch ( iState ) 
        {
        case EReadInbox:
            {
            UpdateMessageArrayL(KMsvGlobalInBoxIndexEntryId);
            iState = EReadSent;
            return 1;
            }

        case EReadSent:
            {
            UpdateMessageArrayL(KMsvSentEntryId);
            iState = EReadOutbox;
            return 1;
            }

        case EReadOutbox:
            {
            UpdateMessageArrayL(KMsvGlobalOutBoxIndexEntryId);
            iState = ESortEntries;            
            return 1;
            }
        case ESortEntries:
            {
            //Sort the elements in the array by descending order of TMsvId's
            TLinearOrder<TMsvId> order(CompareOrder);
            iMessageArray->Sort(order);
            iState = EProcessEntries;
            return 1;
            }            
        case EProcessEntries:
            { 
            //Process one entry at a time in sequence
            //Process the first element in the array on each call, till the end
            if(iMessageArray->Count())
                {
                CMsvEntry* msvEntry= iSession->
                        GetEntryL(iMessageArray->operator[](0));
                CleanupStack::PushL(msvEntry);
                ProcessResultsL(msvEntry->Entry());
                CleanupStack::PopAndDestroy(msvEntry);
                iMessageArray->Remove(0);
                }
            else
                {
                iMessageArray->Reset();
                iMsgObserver->HandleCachingCompleted();
                return 0; //DONE 
                }            
            iState = EProcessEntries;
            return 1; 
        }
    }
    return 0;    
}

// -----------------------------------------------------------------------------
// CCsMsgHandler::UploadMsg()
// CIdle callback 
// -----------------------------------------------------------------------------
//
TInt CCsMsgHandler::UploadMsg(TAny* aArg)
    {
    CCsMsgHandler* handler = (CCsMsgHandler*) aArg; 
    TInt ok = 0;
    TRAPD(err, ok = handler->UploadMsgL());
    return ((err == KErrNone) && ok);
    }

// -----------------------------------------------------------------------------
// CCsMsgHandler::StartL()
// This function starts the state machine to fetch sms data from msvserver
// -----------------------------------------------------------------------------
//
void CCsMsgHandler::StartL()
    {
    PRINT ( _L("Enter CCsMsgHandler::Start") );

    iState = EReadInbox;
    TCallBack callback = TCallBack(UploadMsg, (TAny*) this);
    
    if(iIdle)
        {
        delete iIdle;
        iIdle = NULL;
        }
    iIdle = CIdle::NewL(CActive::EPriorityIdle);
    iIdle->Start(callback);

    PRINT ( _L("End CCsMsgHandler::Start") );
    }

// -----------------------------------------------------------------------------
// CCsMsgHandler::IsMtmSupported()
// 
// -----------------------------------------------------------------------------
//
TBool CCsMsgHandler::IsMtmSupported(long uid)	
    {
    if ( KSenduiMtmSmsUidValue == uid || \
         KSenduiMtmBtUidValue == uid || \
         KSenduiMtmMmsUidValue == uid || \
         KSenduiMtmBioUidValue == uid  || \
         KSenduiMMSNotificationUidValue == uid || \
         KUidMtmWapPush ==  TUid::Uid(uid) )
        {
        return ETrue;
        }	
    return EFalse;
    }

// -----------------------------------------------------------------------------
// CCsMsgHandler::ExtractCsType()
// Extracts the Message type based on the MTM Uid 
// -----------------------------------------------------------------------------
//
TCsType CCsMsgHandler::ExtractCsType( const TMsvEntry& aEntry)
    {
    TCsType type = ECsUnknown;
    switch(aEntry.iMtm.iUid)
        {
        case KSenduiMtmSmsUidValue:            
            type = ECsSMS;
            if (aEntry.iBioType == KMsgBioUidVCard.iUid)
                {
                type = ECsBioMsg_VCard;
                }
            break;
        case KSenduiMtmBtUidValue:
            type = ECsBlueTooth;
            break;
        case KSenduiMtmMmsUidValue:        
           if(aEntry.iBioType == KUidMsgSubTypeMmsAudioMsg.iUid)
                {
                type = ECsAudio;
                }
            else
                {
                type = ECsMMS;
                }
            break;
        case KSenduiMMSNotificationUidValue:            
            type = ECsMmsNotification;
            break;
        case KSenduiMtmBioUidValue:
            { 
            type = ECsBioMsg; 
            
            // based on the biotype uid set message type
            if(aEntry.iBioType == KMsgBioUidRingingTone.iUid)
                {
                type = ECsRingingTone;
                }
            else if(aEntry.iBioType == KMsgBioProvisioningMessage.iUid)
                {
                type = ECsProvisioning;
                }
		    else if (aEntry.iBioType == KMsgBioUidVCard.iUid)
                {
                type = ECsBioMsg_VCard;
                }
            else if (aEntry.iBioType == KMsgBioUidVCalendar.iUid)
                {
                type = ECsBioMsg_VCal;
       		 }
       		 else if (aEntry.iBioType == KMsgBioNokiaServiceMessage.iUid) {
            	type = ECsBioMgs_NokiaService;
        }
    }
        break;
    default:
        type = ECsUnknown;
        break;
    }
    return (type);
}
// -----------------------------------------------------------------------------
// void CCsMsgHandler::UpdateMessageArrayL(const TMsvId& aFolderId)
// Update iMessageArray with the message ID before upload message to csserver. 
// -----------------------------------------------------------------------------
//
void CCsMsgHandler::UpdateMessageArrayL(const TMsvId& aFolderId)
    {
    CMsvEntry* msvEntry = iSession->GetEntryL(aFolderId);
    CleanupStack::PushL(msvEntry);
    // Set sort order
    TMsvSelectionOrdering order;
    order.SetSorting(EMsvSortById);
    msvEntry->SetSortTypeL(order);

    CMsvEntrySelection* messages = msvEntry->ChildrenL();  
    // Cleanup msvEntry
    CleanupStack::PopAndDestroy(msvEntry);
    CleanupStack::PushL(messages);

    TInt messageCount = messages->Count();
    
    // In case of large number of message caching
    // to avoid multiple reallocation alloc memory
    // one shot.
    if( messageCount > KMessageArrayGranularity)
        iMessageArray->Reserve(messageCount);
    
    for(int i = 0; i < messageCount; i ++)
        {
        TMsvId msgId=messages->At(i);
        iMessageArray->Append(msgId);
        }
    // Cleanup messages
    CleanupStack::PopAndDestroy(messages); 
    }
// End of file

