/*
* Copyright (c) 2006 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:  Message store sort result row set implementation.
*
*/


#include "MsgStoreSortResultRowSet.h"
#include "ContainerStoreDefs.h"

_LIT( KEqual,          " = "  );
_LIT( KNotEqual,       " <> " );
_LIT( KGreaterOrEqual, " >= " );
_LIT( KLessThan,       " < "  );

const TUint KQuerrySize = 200;

// ==================================
// CLASS: CMsgStoreSortResultRowSet
// ==================================

// ==========================================================================
// FUNCTION: NewL
// ==========================================================================
CMsgStoreSortResultRowSet* CMsgStoreSortResultRowSet::NewL( const TMsgStoreSortCriteria& aSortCriteria,
                                                            TInt aMessageIdColNum,
                                                            TInt aReceivedDateColNum,
                                                            TInt aReadUnreadColNum,
                                                            TInt aPriotiryColNum,
                                                            TInt aFlagStatusColNum,
                                                            TInt aSizeColNum,
                                                            TInt aAttachmentFlagColNum )
	{
	CMsgStoreSortResultRowSet* self = new(ELeave) CMsgStoreSortResultRowSet( aSortCriteria, 
	                                                                         aMessageIdColNum,
	                                                                         aReceivedDateColNum,
	                                                                         aReadUnreadColNum,
	                                                                         aPriotiryColNum,
	                                                                         aFlagStatusColNum,
	                                                                         aSizeColNum,
	                                                                         aAttachmentFlagColNum );
	CleanupStack::PushL( self );
	self->ConstructL();
	CleanupStack::Pop( self );
	return self;
	}
		
// ==========================================================================
// FUNCTION: Constructor
// ==========================================================================
CMsgStoreSortResultRowSet::CMsgStoreSortResultRowSet( const TMsgStoreSortCriteria& aSortCriteria,
                                                      TInt aMessageIdColNum,
                                                      TInt aReceivedDateColNum,
                                                      TInt aReadUnreadColNum,
                                                      TInt aPriotiryColNum,
                                                      TInt aFlagStatusColNum,
                                                      TInt aSizeColNum,
                                                      TInt aAttachmentFlagColNum )
: iIsAutoRefresh( ETrue ), iMessageIdColNum( aMessageIdColNum ), iReceivedDateColNum(aReceivedDateColNum), 
  iReadUnreadColNum(aReadUnreadColNum), iPriotiryColNum(aPriotiryColNum), iFlagStatusColNum(aFlagStatusColNum), iSizeColNum(aSizeColNum), 
  iAttachmentFlagColNum(aAttachmentFlagColNum)
	{
    __LOG_CONSTRUCT( "msg", "CMsgStoreSortResultRowSet" )
    __LOG_CLOSE_BETWEEN_WRITES
    
    iMailBoxId = aSortCriteria.iMailBoxId;
    iFolderId = aSortCriteria.iFolderId;
    iSortBy = aSortCriteria.iSortBy; 
    iSortOrder = aSortCriteria.iSortOrder;
	}

// ==========================================================================
// FUNCTION: Destructor
// ==========================================================================
CMsgStoreSortResultRowSet::~CMsgStoreSortResultRowSet()
	{
    iPropertyNames.ResetAndDestroy();
    //iSubjectPrefixToIgnore.ResetAndDestroy();
	iDbView.Close();
    __LOG_DESTRUCT  
	}

// ==========================================================================
// FUNCTION: ConstructL
// ==========================================================================
void CMsgStoreSortResultRowSet::ConstructL()
	{
	}

// ==========================================================================
// FUNCTION: ToTopL
// ==========================================================================
void CMsgStoreSortResultRowSet::ToTopL()
	{
	iDbView.BeginningL();
	}

// ==========================================================================
// FUNCTION: ToEndL
// ==========================================================================
void CMsgStoreSortResultRowSet::ToEndL()
	{
	iDbView.EndL();
	}

// ==========================================================================
// FUNCTION: HasMorePreviousL
// ==========================================================================
TBool CMsgStoreSortResultRowSet::HasMorePreviousL()
	{
	TBool hasMorePrevious = !iDbView.AtBeginning();
	if ( hasMorePrevious )
	    {
	    //if PreviousL() returns false, that means no more previous
	    if ( !iDbView.PreviousL() )
	        {
	        hasMorePrevious = EFalse;
	        }
	    //move it back
	    iDbView.NextL();
	    }    
	return hasMorePrevious;
	}

// ==========================================================================
// FUNCTION: HasMoreNextL
// ==========================================================================
TBool CMsgStoreSortResultRowSet::HasMoreNextL()
	{
	TBool hasMoreNext = !iDbView.AtEnd();
    if ( hasMoreNext )
        {
        //if NextL() returns false, that means no more next
        if ( !iDbView.NextL() )
            {
            hasMoreNext = EFalse;
            }
        //move it back
        iDbView.PreviousL();
        }    
    return hasMoreNext;
	}

// ==========================================================================
// FUNCTION: NextL
// ==========================================================================
TMsgStoreId CMsgStoreSortResultRowSet::NextL()
	{
	iDbView.NextL();
    if ( iDbView.AtEnd() )
        {
        User::Leave( KErrOverflow );
        }
	return GetRowL();
	}

// ==========================================================================
// FUNCTION: PreviousL
// ==========================================================================
TMsgStoreId CMsgStoreSortResultRowSet::PreviousL()
	{
	iDbView.PreviousL();
    if ( iDbView.AtBeginning() )
        {
        User::Leave( KErrUnderflow );
        }
	return GetRowL();
	}

// ==========================================================================
// FUNCTION: GotoL
// ==========================================================================
void CMsgStoreSortResultRowSet::GotoL( TContainerId aMessageId )
	{
    
    //check if the aMessageId is right on the cursor
    TBool found = EFalse;
    if ( iDbView.AtRow() )
        {
        iDbView.GetL();
        if ( iDbView.ColUint32( iMessageIdColNum ) == aMessageId )
            {
            found = ETrue;
            }
        }
    
    if ( !found )
        {
        //have to search it from the begining
        
        TBuf<KQuerrySize> query;
        query.Copy( KSortingTableMessageIdCol );
        query.Append( KEqual );
        query.AppendNum( aMessageId );
        
        iDbView.FirstL();
        
        TInt rc = iDbView.FindL( RDbRowSet::EForwards, query );
        if ( rc < 0 )
            {
            User::Leave( rc );
            }
        }
	}

// ==========================================================================
// FUNCTION: GotoL
// ==========================================================================
void CMsgStoreSortResultRowSet::GotoL( const TDesC& /*aStartWith*/, TMsgStoreIteratorDirection /*aDirection*/ )
    {
    User::Leave( KErrNotSupported );
    }

// ==========================================================================
// FUNCTION: SkipCurrentGroupL - for UI "group" support
// ==========================================================================
TBool CMsgStoreSortResultRowSet::SkipCurrentGroupL( TMsgStoreIteratorDirection aDirection, TUint& aItemsInPreviousGroup )
    {
    __LOG_ENTER_SUPPRESS( "SkipCurrentGroupL" )
    //if we are at the begining or end, return right away because there is NO "current group"
    if ( iDbView.AtBeginning() || iDbView.AtEnd() )
        {
        return ETrue;
        }
    
    TBuf<KQuerrySize> query;
    TInt8   curValue = 0;
    TUint32 curSizeValue = 0;
    TInt64  curDateValue = 0;
    
    iDbView.GetL();
    
    switch( iSortBy )
        {
        case EMsgStoreSortByReceivedDate:
            query.Copy( KSortingTableReceivedDateCol );
            curDateValue = iDbView.ColInt64( iReceivedDateColNum );
            break;
        
        case EMsgStoreSortByPriority:
            query.Copy( KSortingTablePriorityCol );
            curValue = iDbView.ColInt8( iPriotiryColNum );
            break;
        
        case EMsgStoreSortByFlagStatus:
            query.Copy( KSortingTableFlagStatusCol );
            curValue = iDbView.ColInt8( iFlagStatusColNum );
            break;
        
        case EMsgStoreSortByUnRead:
            query.Copy( KSortingTableReadUnreadCol );
            curValue = iDbView.ColInt8( iReadUnreadColNum );
            break;
        
        case EMsgStoreSortBySize:
            query.Copy( KSortingTableSizeCol );
            curSizeValue = iDbView.ColUint32( iSizeColNum );
            break;
        
        case EMsgStoreSortByAttachment:
            query.Copy( KSortingTableAttachmentFlagCol );
            curValue = iDbView.ColInt8( iAttachmentFlagColNum );
            break;
        
        default:
            User::Leave( KErrCorrupt );
            break;
        }
    
   
    if ( iSortBy == EMsgStoreSortByReceivedDate )
        {
        TTime curTime(curDateValue);
        const TUint bufSize = 100;
        TBuf<bufSize> timeString;
        _LIT( KDateTimeFormat, "%F%M/%D/%Y %H:%T:%S" );
        curTime.FormatL( timeString, KDateTimeFormat() );
        __LOG_WRITE_FORMAT1_INFO( "cur msg time=%S", &timeString )
        
        TLocale locale;
        TTimeIntervalSeconds offset = locale.UniversalTimeOffset();
        
        
        //get the local time
        curTime += offset;
        //change it to 0 o'clock
        TDateTime curDT = curTime.DateTime();
        TDateTime dayBoundary( curDT.Year(), curDT.Month(), curDT.Day(), 0, 0, 0, 0 );
        TTime timeBoundary( dayBoundary );
        //change it back to UTC
        timeBoundary -= offset;
        
        timeBoundary.FormatL( timeString, KDateTimeFormat() );
        __LOG_WRITE_FORMAT1_INFO( "timeBoundary=%S", &timeString )
        
        //We are search from earlier date to find the next date if 
        //    1) sort order is ASCENDING and movinf FORWARD, or
        //    2) sort order is DESCENDING and moving BACKWARD
        //We are search from later date to find the previous date if
        //    1) sort order is DESCENDING and moving FORWARD, or
        //    2) sort order is ASCENDING and moving BACKWARD
        if ( ( iSortOrder == EMsgStoreSortAscending && aDirection == EMsgStoreIteratorForward ) ||
             ( iSortOrder == EMsgStoreSortDescending && aDirection == EMsgStoreIteratorBackward ) )
            {
            //search the next date, 
            //so add one day to the search criteria and
            TTimeIntervalDays day(1);
            timeBoundary += day;
            query.Append( KGreaterOrEqual );
            query.AppendNum( timeBoundary.Int64() );
            }
        else
            {
            //search for previous date, use less than current date
            query.Append( KLessThan );
            query.AppendNum( timeBoundary.Int64() );
            }        
        }
    else if ( iSortBy == EMsgStoreSortBySize )
        {
        query.Append( KNotEqual );
        query.AppendNum( curSizeValue );
        }
    else
        {
        query.Append( KNotEqual );
        query.AppendNum( curValue );
        }
    
    TInt rc = KErrNone;
    TBool hasMore = ETrue;
    
    if ( aDirection == EMsgStoreIteratorForward )
        {
        rc = iDbView.FindL( RDbRowSet::EForwards, query );
        }
    else
        {
        rc = iDbView.FindL( RDbRowSet::EBackwards, query );
        }
    
    if ( rc >= 0 )
        {
        aItemsInPreviousGroup = rc;
        if ( aDirection == EMsgStoreIteratorForward )
            {
            PreviousL();
            }
        else
            {
            NextL();
            }
        }
    else
        {
        hasMore = EFalse;
        }
    
    return hasMore;    
    }

// ==========================================================================
// FUNCTION: GroupCountL
// ==========================================================================
TInt CMsgStoreSortResultRowSet::GroupCountL( RArray<TUint>& aItemsInGroup )
    {
    TInt count = 0;
    TUint itemsInPreviousGroup;
    TBool hasMore = EFalse;
    
    iDbView.BeginningL();
    
    TInt curTotal = 0;
    
    do { 
        if ( !iDbView.NextL() )
            {
            break;
            }
    
        itemsInPreviousGroup = 0;
        hasMore = SkipCurrentGroupL( EMsgStoreIteratorForward, itemsInPreviousGroup );
        
        if ( itemsInPreviousGroup > 0 )
            {
            ++count;
            curTotal += itemsInPreviousGroup;
            aItemsInGroup.Append( itemsInPreviousGroup );
            }
        } while ( hasMore );        
    
    //since we can't get the item counts in the last group, we need to check if there is a last group
    // the we may have missed
    TInt lastGroupCount = iDbView.CountL() - curTotal;
    if ( lastGroupCount > 0 )
        {
        ++count;
        aItemsInGroup.Append( lastGroupCount );
        }
    
    return count;
    }


// ==========================================================================
// FUNCTION: SortedIdsL
// ==========================================================================
void CMsgStoreSortResultRowSet::SortedIdsL( RArray<TContainerId>& aIdArray )
	{
	aIdArray.Reset();
	TInt count = iDbView.CountL();
	iDbView.BeginningL();
	for ( TInt i = 0 ; i < count ; i++  )
		{
		iDbView.NextL();
		aIdArray.Append( GetRowL() );
		}
	}

// ==========================================================================
// FUNCTION: IndexOfL
// ==========================================================================
TInt CMsgStoreSortResultRowSet::IndexOfL( TContainerId aMessageId )
    {
    //have to search it from the begining
    const TUint bufSize = 200;
    TBuf<bufSize> query;
    query.Copy( KSortingTableMessageIdCol );
    query.Append( KEqual );
    query.AppendNum( aMessageId );
    
    iDbView.FirstL();
    
    return iDbView.FindL( RDbRowSet::EForwards, query );
    }

// ==========================================================================
// FUNCTION: SortedIdsAndGroupCountL
// ==========================================================================
void CMsgStoreSortResultRowSet::SortedIdsAndGroupCountL( RArray<TContainerId>& aIdArray, RArray<TUint>& aItemsInGroup )
    {
    SortedIdsL( aIdArray );
    GroupCountL( aItemsInGroup );
    }

// ==========================================================================
// FUNCTION: SetPropertyNamesL
// ==========================================================================
void CMsgStoreSortResultRowSet::SetPropertyNamesL( RPointerArray<HBufC8>& aPropertyNames )
    {
    iPropertyNames.ResetAndDestroy();
    for ( int i = 0 ; i < aPropertyNames.Count() ; i++ )
        {
        iPropertyNames.Append( aPropertyNames[i]->Des().AllocL() );
        }
    }

// ==========================================================================
// FUNCTION: GetRowL
// ==========================================================================
TMsgStoreId CMsgStoreSortResultRowSet::GetRowL()
	{
	iDbView.GetL();
	TContainerId msgId = iDbView.ColUint32( iMessageIdColNum );  
	return msgId;
	}

// ==========================================================================
// FUNCTION: MessageUpdate
// ==========================================================================
void CMsgStoreSortResultRowSet::MessageUpdate ( TContainerId       /*aMessageId*/, 
                                                TContainerId       aFolderId, 
                                                TMsgStoreOperation aOperation,               //EMsgStoreAdd, EMsgStoreDelete, EMsgStoreUpdateProperties
                                                TUint              aFieldsChanged,       //For EMsgStoreUpdateProperties only, combinations of TSortableFieldsMasks
                                                const TDesC&       /*aFrom*/, 
                                                const TDesC&       /*aTo*/, 
                                                const TDesC&       /*aSubject*/,
                                                TInt64             /*aDate*/)
    {
    TBool reEvaluate = EFalse;
    if ( iIsAutoRefresh && iFolderId == aFolderId )
        {
        if ( aOperation == EMsgStoreAdd || aOperation == EMsgStoreDelete )
            {
            reEvaluate = ETrue;
            }
        else if ( aOperation == EMsgStoreUpdateProperties )
            {
            //if received date changed, always refresh because it's always used
            if ( aFieldsChanged & EMaskReceivedDate > 0 )
                {
                reEvaluate = ETrue;
                }
            else
                {
                switch( iSortBy )
                    {
                    case EMsgStoreSortByPriority:
                        if ( aFieldsChanged & EMaskPriotiry )
                            {
                            reEvaluate = ETrue;
                            }
                        break;
                        
                    case EMsgStoreSortByFlagStatus:
                        if ( aFieldsChanged & EMaskFlagStatus )
                            {
                            reEvaluate = ETrue;
                            }
                        break;
                    
                    case EMsgStoreSortByUnRead:
                        if ( aFieldsChanged & EMaskReadUnread )
                            {
                            reEvaluate = ETrue;
                            }
                        break;
                    
                    case EMsgStoreSortBySize:
                        if ( aFieldsChanged & EMaskSize )
                            {
                            reEvaluate = ETrue;
                            }
                        break;
                    
                    case EMsgStoreSortByAttachment:
                        if ( aFieldsChanged & EMaskAttachmentFlag )
                            {
                            reEvaluate = ETrue;
                            }
                        break;
                    
                    default:
                        //do nothing
                        break;
                    }
                }
            }
        }
    if ( reEvaluate )
        {
        ReEvaluate();     //ignore error
        }
    }

// ==========================================================================
// FUNCTION: FolderDeleted
// ==========================================================================
void CMsgStoreSortResultRowSet::FolderDeleted ( TContainerId aFolderId )
    {
    if ( iIsAutoRefresh && iFolderId == aFolderId )
        {
        //this folder does not exist anymore.
        //reset and re-evaluate the view
        ReEvaluate();     //ignore error
        }
    }

// ==========================================================================
// FUNCTION: MailBoxDeleted
// ==========================================================================
void CMsgStoreSortResultRowSet::MailBoxDeleted ( TContainerId aMailBoxId )
    {
    if ( iIsAutoRefresh && iMailBoxId == aMailBoxId )
        {
        //this mail box has been deleted, so the folder does not exist anymore.
        //reset and re-evaluate the view
        ReEvaluate();     //ignore error
        }
    }

// ==========================================================================
// FUNCTION: ReEvaluate
// ==========================================================================
TInt CMsgStoreSortResultRowSet::ReEvaluate()
    {
    __LOG_ENTER_SUPPRESS( "ReEvaluate" )
    iDbView.Reset();
    TInt rc = iDbView.EvaluateAll();
    if ( rc != KErrNone )
        {
        __LOG_WRITE_FORMAT1_ERROR("EvaluateAll() failed. rc=%d", rc)
        }
    return rc;
    }
