/*
* 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:  Freestyle message iterator implementation for Symbian Message
*                store
*
*/


#include "emailtrace.h"
#include "ipsplgheaders.h"

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
CIpsPlgMsgIterator* CIpsPlgMsgIterator::NewL(
    CIpsPlgSosBasePlugin& aPlugin,
    CMsvSession& aMsvSession,
    const TFSMailMsgId& aMailboxId,
    const TFSMailMsgId aFolderId,
    const TFSMailDetails aDetails,
    const RArray<TFSMailSortCriteria>& aSorting)
    {
    FUNC_LOG;
    CIpsPlgMsgIterator* self = 
        new( ELeave ) CIpsPlgMsgIterator( aPlugin, aMailboxId, aDetails, 
            aSorting );
    CleanupStack::PushL( self );
    self->ConstructL( aMsvSession, aFolderId, aSorting );
    CleanupStack::Pop( self );
    return self;
    }

// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
//
CIpsPlgMsgIterator* CIpsPlgMsgIterator::NewL(
    CIpsPlgSosBasePlugin& aPlugin,
    CMsvEntry* aFolderEntry, 
    const TFSMailMsgId& aMailboxId,
    const TFSMailDetails aDetails,
    const RArray<TFSMailSortCriteria>& aSorting)
    {
    FUNC_LOG;
    CIpsPlgMsgIterator* self = 
        new( ELeave ) CIpsPlgMsgIterator( aPlugin, aMailboxId, aDetails, 
            aSorting );
    CleanupStack::PushL( self );
    self->ConstructL( aFolderEntry, aSorting );
    CleanupStack::Pop( self );
    return self;
    }


// ---------------------------------------------------------------------------
// Basic destructor
// ---------------------------------------------------------------------------
//
CIpsPlgMsgIterator::~CIpsPlgMsgIterator()
    {
    FUNC_LOG;
    delete iFolderEntry;
    delete iMsgMapper;
    delete iMsgSortKey;
    delete iMsgSwapper;
    }

// ---------------------------------------------------------------------------
// Searches the message matching with aCurrentMessageId and requests the
// chunk of messages following the matching message
// ---------------------------------------------------------------------------
//
TBool CIpsPlgMsgIterator::NextL(
    TFSMailMsgId aCurrentMessageId, 
    TUint aCount, 
    RPointerArray<CFSMailMessage>& aMessages)
    {
    FUNC_LOG;
    TBool result = EFalse;
    TInt baseIndex;
    
    // Messages are sorted always before reading the messages 
    Sort();
    
    CMsvEntrySelection* messages = FilterMessagesL();
    CleanupStack::PushL( messages );

    if ( !aCurrentMessageId.IsNullId() )
        {
        baseIndex = messages->Find( aCurrentMessageId.Id() );
        
        // aCurrentMessageId is not included to the result set
        if ( baseIndex != KErrNotFound )
            {
            baseIndex += 1;
            }
        }
    else
        {
        // start from the beginning of the message list
        baseIndex = 0;
        }
        
    if ( ( baseIndex != KErrNotFound ) && 
         ( baseIndex < messages->Count() ) )
        {
        result = NextL(baseIndex, messages, aCount, aMessages);
        }

    CleanupStack::PopAndDestroy(messages);
    return result;
    }

// ---------------------------------------------------------------------------
// Searches message matching with the search string and returns a chunk of
// messages starting from the 1st matched message
// ---------------------------------------------------------------------------
//
TBool CIpsPlgMsgIterator::NextL(
    const TDesC&  aStartWith, 
    TUint aCount, 
    RPointerArray<CFSMailMessage>& aMessages)
    {
    FUNC_LOG;
    TBool result = EFalse;
    TInt status;
    TInt baseIndex;
    
    // Messages are sorted always before reading the messages 
    Sort();

    CMsvEntrySelection* messages = FilterMessagesL();
    CleanupStack::PushL( messages );

    status = SearchL( messages, aStartWith, baseIndex );
    
    if ( status == KErrNone )
        {
        result = NextL(baseIndex, messages, aCount, aMessages);
        }

    CleanupStack::PopAndDestroy(messages);
    return result;
    }

// ---------------------------------------------------------------------------
// Like NextL() with similar parameters, the method searches the message 
// matching with aCurrentMessageId and requests a chunk of messages preceding 
// the matching message
// ---------------------------------------------------------------------------
//
TBool CIpsPlgMsgIterator::PreviousL(
    TFSMailMsgId aCurrentMessageId, 
    TUint aCount, 
    RPointerArray<CFSMailMessage>& aMessages)
    {
    FUNC_LOG;
    TBool result = EFalse;
    TInt baseIndex;
    
    // Messages are sorted always before reading the messages 
    Sort();

    CMsvEntrySelection* messages = FilterMessagesL();
    CleanupStack::PushL(messages);

    if ( !aCurrentMessageId.IsNullId() )
        {
        baseIndex = messages->Find(aCurrentMessageId.Id());
        
        // aCurrentMessageId is not included to the result set
        if ( baseIndex != KErrNotFound )
            {
            baseIndex -= 1;
            }
        }
    else
        {
        // check whether we should start from the end of 
        // the message list in the case of a NULL ID
        baseIndex = messages->Count() - 1;
        }

    // Actually, if the matching message is the first one, baseIndex is equal
    // to -1 which is also the value of KErrNotFound. So, the condition could be
    // simpler, but it would not inappropriate to trust that the numerical value 
    // of the error code is known.
    if ( ( baseIndex != KErrNotFound ) &&
         ( baseIndex >= 0 ) )
        {
        result = PreviousL(baseIndex, messages, aCount, aMessages);
        }

    CleanupStack::PopAndDestroy(messages);
    return result;
    }

// ---------------------------------------------------------------------------
// Searches the first message matching with the search string and requests
// a chunk of messages including the matching message and messages preceding 
// it.
// The return value tells currently only, if there are more messages, but it
// is not checked whether they are deleted. It is probably more important
// to return as fast as possible than check the remaining messages. The same
// concerns PreviousL().
// ---------------------------------------------------------------------------
//
TBool CIpsPlgMsgIterator::PreviousL(
    const TDesC&  aStartWith, 
    TUint aCount, 
    RPointerArray<CFSMailMessage>& aMessages)
    {
    FUNC_LOG;
    TBool result = EFalse;
    TInt status;
    TInt baseIndex;
    
    // Messages are sorted always before reading the messages 
    Sort();

    CMsvEntrySelection* messages = FilterMessagesL();
    CleanupStack::PushL( messages );

    status = SearchL( messages, aStartWith, baseIndex );
    
    if (  status == KErrNone ) 
        {
        result = PreviousL(baseIndex, messages, aCount, aMessages);
        }

    CleanupStack::PopAndDestroy(messages);
    return result;
    }

// ---------------------------------------------------------------------------
// Loops over the requested messages noticing the ends of the message array
// ---------------------------------------------------------------------------
//
TBool CIpsPlgMsgIterator::NextL(
    TInt aStart,
    CMsvEntrySelection* aMessageEntries,
    TUint aCount,
    RPointerArray<CFSMailMessage>& aMessages)
    {
    FUNC_LOG;
    TInt i = aStart;
    TInt counter( 0 );
    CFSMailMessage* fsMsg;
        
    while (( counter < aCount ) && ( i < aMessageEntries->Count() ) )
        {
        const TMsvEmailEntry& entry( 
            iFolderEntry->ChildDataL( aMessageEntries->At(i) ) );
        if ( ( EDisconnectedDeleteOperation != entry.DisconnectedOperation() ) &&
        	(( entry.iMtm != KSenduiMtmImap4Uid )  || !entry.DeletedIMAP4Flag() ) &&
        	 ( entry.iType == KUidMsvMessageEntry ) )
            {
            fsMsg = iMsgMapper->GetMailMessageL( iMailboxId, entry, 
                iRequestedDetails );
            aMessages.Append( fsMsg );
            counter++;
            }
        i++;
        }
    
    return ( i < aMessageEntries->Count() );
    }

// ---------------------------------------------------------------------------
// Loops over the requested messages noticing the ends of the message array.
// The implementation is somewhat more complex than the corresponding NextL()
// because the messages are processed in the order they are inserted to the
// result array.
// ---------------------------------------------------------------------------
//
TBool CIpsPlgMsgIterator::PreviousL(
    TInt aEnd,
    CMsvEntrySelection* aMessageEntries,
    TUint aCount,
    RPointerArray<CFSMailMessage>& aMessages)
    {
    FUNC_LOG;
    __ASSERT_DEBUG( ( aEnd < aMessageEntries->Count() ), 
        User::Panic( KIpsPlgPanicCategory, EIpsPlgInvalidMessageIndex ) );
     
    TInt i = aEnd;
    TInt counter( 0 );
    CFSMailMessage* fsMsg; 
    
    while (( counter < aCount ) && ( i >= 0 ) )
        {
        const TMsvEmailEntry& entry( 
            iFolderEntry->ChildDataL( aMessageEntries->At(i) ) );
        if ( ( EDisconnectedDeleteOperation != entry.DisconnectedOperation() ) &&
        	(( entry.iMtm != KSenduiMtmImap4Uid )  || !entry.DeletedIMAP4Flag() ) &&	
   	 		 ( entry.iType == KUidMsvMessageEntry ) )
            {
            fsMsg = iMsgMapper->GetMailMessageL( iMailboxId, entry, 
                iRequestedDetails );
            aMessages.Insert( fsMsg, 0 );
            counter++;
            }
        i--;
        }
    
    return ( i > 0 );
    }

// ---------------------------------------------------------------------------
// Constructor. Initializes the static data members.
// ---------------------------------------------------------------------------
//
CIpsPlgMsgIterator::CIpsPlgMsgIterator( 
    CIpsPlgSosBasePlugin& aPlugin,
    const TFSMailMsgId& aMailboxId,
    const TFSMailDetails aDetails,
    const RArray<TFSMailSortCriteria>& aSorting )
    : iPlugin( aPlugin ), iRequestedDetails( aDetails ), 
      iSortingCriteria( aSorting ), iMailboxId( aMailboxId )
    {
    FUNC_LOG;
    // none
    }

// ---------------------------------------------------------------------------
// Second level constructor. Creates the data members to the heap.
// Creates also the folder CMsvEntry.
// ---------------------------------------------------------------------------
//
void CIpsPlgMsgIterator::ConstructL(        
    CMsvSession& aMsvSession,
    const TFSMailMsgId aFolderId,
    const RArray<TFSMailSortCriteria>& aSorting )
    {
    FUNC_LOG;
    iFolderEntry  = aMsvSession.GetEntryL( aFolderId.Id() );
    iMsgSortKey   = new (ELeave) TIpsPlgMsgKey( *iFolderEntry, aSorting );
    iMsgSwapper   = new (ELeave) TIpsPlgMsgSwap( *iFolderEntry );
    iSortingOn    = 
        ( aSorting.Count() > 0 ) && ( aSorting[0].iField != EFSMailDontCare);
    iMsgMapper    = CIpsPlgMsgMapper::NewL( aMsvSession, iPlugin );
    iMsvSession   = &aMsvSession;
    }
    
// ---------------------------------------------------------------------------
// Second level constructor. Creates the data members to the heap.
// Version which gets the folder CMsvEntry as a parameter.
// ---------------------------------------------------------------------------
//
void CIpsPlgMsgIterator::ConstructL(        
    CMsvEntry* aFolderEntry, 
    const RArray<TFSMailSortCriteria>& aSorting )
    {
    FUNC_LOG;
    iFolderEntry  = aFolderEntry;
    iMsgSortKey   = new (ELeave) TIpsPlgMsgKey( *iFolderEntry, aSorting );
    iMsgSwapper   = new (ELeave) TIpsPlgMsgSwap( *iFolderEntry );
    iSortingOn    = 
        ( aSorting.Count() > 0 ) && ( aSorting[0].iField != EFSMailDontCare);
    iMsgMapper = CIpsPlgMsgMapper::NewL( aFolderEntry->Session(), iPlugin );
    iMsvSession = NULL;
    }
    
// ---------------------------------------------------------------------------
// Sorting is based on the quick sort algorithm implemented in User module
// ---------------------------------------------------------------------------

void CIpsPlgMsgIterator::Sort()
    {
    FUNC_LOG;
    if ( iSortingOn )
        {
        User::QuickSort( iFolderEntry->Count(), *iMsgSortKey, *iMsgSwapper );
        }
    }
    
// ---------------------------------------------------------------------------
// Searches the first message matching the search string.
// This function is supported only when the messages are sorted by the subject
// or by the sender (or the receiver in Drafts/Outbox/Sent folders) as 
// a primary sorting key. The fields to be compared is selected on the basis 
// of the sorting key.
// ---------------------------------------------------------------------------

TInt CIpsPlgMsgIterator::SearchL( 
    CMsvEntrySelection* aMessageEntries,
    const TDesC& aStartWith,
    TInt& aIndex )
    {
    FUNC_LOG;
    TInt result( KErrNotFound );
    TInt i;
    TInt findStatus;
    TInt offset;
    TFSMailSortField primaryKey = EFSMailDontCare;
    TPtrC subject;

    // Check whether the search is supported
    if ( iSortingCriteria.Count() > 0 )
        {
        primaryKey = iSortingCriteria[0].iField;
        }
    if ( !iSortingOn ||
         ( ( primaryKey != EFSMailSortBySender ) &&
           ( primaryKey != EFSMailSortBySubject ) ) )
        {
        result = KErrNotSupported;
        }
        
    // Actual search        
    i = 0;
    while ( ( result == KErrNotFound ) && ( i < aMessageEntries->Count() ) )
        {
        const TMsvEntry& entry( 
            iFolderEntry->ChildDataL( aMessageEntries->At(i) ) );

        findStatus = KErrNotFound;

        if ( primaryKey == EFSMailSortBySender )
            {
            findStatus = entry.iDetails.FindF( aStartWith );
            }
        if ( primaryKey == EFSMailSortBySubject )
            {
            // Strip the prefixes (Re:, Fwd: etc) from the subject before
            // matching
            offset = iMsgSortKey->FindSubjectStart( entry.iDescription );
            subject.Set( 
                entry.iDescription.Ptr() + offset, 
                entry.iDescription.Length() - offset );
            findStatus = subject.FindF( aStartWith );
            }
        
        // Checks whether a matching message has been found
        if ( findStatus == 0 ) 
            {
            result = KErrNone;
            aIndex = i;
            }

        i++;
        }
        
    return result;
    }
// ---------------------------------------------------------------------------
// Filters messages in global folders (Drafts/Sent/Outbox) that belongs to
// specified mailbox. When messages in inbox asked, returns selection from 
// wanted mailbox.
// ---------------------------------------------------------------------------
CMsvEntrySelection* CIpsPlgMsgIterator::FilterMessagesL()
	{
    FUNC_LOG;
	TMsvId serviceId;
	TMsvEntry tEntry;
	TMsvEntry mailboxServiceEntry;
	CMsvEntrySelection* filteredEntries;
	
	if( iMsvSession && iFolderEntry->Entry().Parent() == KMsvLocalServiceIndexEntryIdValue )
		{
		User::LeaveIfError(
			iMsvSession->GetEntry( iMailboxId.Id(), serviceId, tEntry ) );
		User::LeaveIfError(
		    iMsvSession->GetEntry( tEntry.iRelatedId, serviceId, mailboxServiceEntry ) );
			
		filteredEntries = iFolderEntry->ChildrenWithServiceL( serviceId );
		}
	else
		{
 		filteredEntries = iFolderEntry->ChildrenWithServiceL( iMailboxId.Id() );
		}
	return filteredEntries;
	}

