/*
* Copyright (c) 2006-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:  A class for finding contact duplicates from stores
*
*/


#include <CPbk2DuplicateContactFinder.h>

// From Phonebook2
#include <MPbk2DuplicateContactObserver.h>
#include <MPbk2ContactNameFormatter.h>

// From Virtual Phonebook
#include <MVPbkFieldType.h>
#include <MVPbkBaseContactFieldCollection.h>
#include <MVPbkStoreContact.h>
#include <MVPbkContactFieldTextData.h>
#include <MVPbkContactStore.h>
#include <CVPbkContactManager.h>
#include <MVPbkContactOperation.h>
#include <MVPbkContactLinkArray.h>

#include <VPbkEng.rsg>
namespace
    {
    #ifdef _DEBUG
        enum TPanicCode
            {
            EPanic_RetrieveContactL_OOB = 1
            };
            
        void Panic( TPanicCode aPanic )
            {
            _LIT( KPanicCat, "CPbk2DuplicateContactFinder" );
            User::Panic( KPanicCat(), aPanic );
            }
    #endif // _DEBUG    
    }
 
enum TPbk2DuplicateContactFinderState
    {
    ENoDuplicatesFound,
    EStartFind,
    ERetrieveContact,
    EComplete
    };

const TInt KNameFormattingFlags = 
    MPbk2ContactNameFormatter::EPreserveLeadingSpaces;
    
// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::CPbk2DuplicateContactFinder
// ---------------------------------------------------------------------------
//
CPbk2DuplicateContactFinder::CPbk2DuplicateContactFinder( 
        CVPbkContactManager& aContactManager, 
        MPbk2ContactNameFormatter& aNameFormatter,
        const MVPbkFieldTypeList& aFieldTypesForFind,
        RPointerArray<MVPbkStoreContact>& aDuplicateContacts ):
            CActive( EPriorityStandard ),
            iContactManager( aContactManager ),
            iNameFormatter( aNameFormatter ),
            iFieldTypesForFind( aFieldTypesForFind ),
            iFindText( KNullDesC ),
            iDuplicates( aDuplicateContacts )
    {
    }


// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::ConstructL
// ---------------------------------------------------------------------------
//
void CPbk2DuplicateContactFinder::ConstructL()
    {
    CActiveScheduler::Add( this );
    }

// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::NewL
// ---------------------------------------------------------------------------
//
EXPORT_C CPbk2DuplicateContactFinder* CPbk2DuplicateContactFinder::NewL(
        CVPbkContactManager& aContactManager, 
        MPbk2ContactNameFormatter& aNameFormatter,
        const MVPbkFieldTypeList& aFieldTypesForFind,
        RPointerArray<MVPbkStoreContact>& aDuplicateContacts )
    {
    CPbk2DuplicateContactFinder* self = 
        new( ELeave ) CPbk2DuplicateContactFinder( aContactManager, 
            aNameFormatter, aFieldTypesForFind, aDuplicateContacts );
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop( self );
    return self;
    }

// ---------------------------------------------------------------------------
// destructor
// ---------------------------------------------------------------------------
//
CPbk2DuplicateContactFinder::~CPbk2DuplicateContactFinder()
    {
    Cancel();
    delete iContactOperation;
    delete iFindResults;
    delete iContactTitle;
    }

// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::StartL
// ---------------------------------------------------------------------------
//
EXPORT_C void CPbk2DuplicateContactFinder::StartL( 
        const MVPbkBaseContact& aContact,
        MVPbkContactStore& aStore, 
        MPbk2DuplicateContactObserver& aObserver,
        TInt aMaxDuplicatesToFind )
    {
    iStore = &aStore;
    StartL( aContact, aObserver, aMaxDuplicatesToFind );
    }
    
// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::StartL
// ---------------------------------------------------------------------------
//
EXPORT_C void CPbk2DuplicateContactFinder::StartL( 
        const MVPbkBaseContact& aContact,
        MPbk2DuplicateContactObserver& aObserver,
        TInt aMaxDuplicatesToFind )
    {
    iObserver = &aObserver;
    iContactToCompare = &aContact;
    iContactIndex = 0;
    iMaxDuplicatesToFind = aMaxDuplicatesToFind;
    
    iState = ENoDuplicatesFound;
    iFindText.Set( FindText() );
    if ( iFindText.Length() > 0 )
        {
        iState = EStartFind;
        }
    IssueRequest();
    }
    
// ---------------------------------------------------------------------------
// From class CActive.
// CPbk2DuplicateContactFinder::RunL
// ---------------------------------------------------------------------------
//
void CPbk2DuplicateContactFinder::RunL()
    {
    switch ( iState )
        {
        case EStartFind:
            {
            StartFindL();
            break;
            }
        case ERetrieveContact:
            {
            RetrieveContactL();
            break;
            }
        case EComplete:
            {
            CompleteL();
            break;
            }
        case ENoDuplicatesFound: // FALLTHROUGH
        default:
            {
            iObserver->DuplicateFindComplete();
            break;
            }
        }
    }

// ---------------------------------------------------------------------------
// From class CActive.
// CPbk2DuplicateContactFinder::DoCancel
// ---------------------------------------------------------------------------
//    
void CPbk2DuplicateContactFinder::DoCancel()
    {
    DestroyOperation();
    }

// ---------------------------------------------------------------------------
// From class CActive.
// CPbk2DuplicateContactFinder::RunError
// ---------------------------------------------------------------------------
//        
TInt CPbk2DuplicateContactFinder::RunError( TInt aError )
    {
    iObserver->DuplicateFindFailed( aError );
    return KErrNone;
    }
    
// ---------------------------------------------------------------------------
// From class MVPbkContactFindObserver.
// CPbk2DuplicateContactFinder::FindCompleteL
// ---------------------------------------------------------------------------
//
void CPbk2DuplicateContactFinder::FindCompleteL( 
        MVPbkContactLinkArray* aResults )
    {
    iFindResults = aResults;
    iState = ENoDuplicatesFound;
    if ( !LastContactRetrieved() )
        {
        iState = ERetrieveContact;
        }
    
    IssueRequest();
    }

// ---------------------------------------------------------------------------
// From class MVPbkContactFindObserver.
// CPbk2DuplicateContactFinder::FindFailed
// ---------------------------------------------------------------------------
//
void CPbk2DuplicateContactFinder::FindFailed( TInt aError )
    {
    iObserver->DuplicateFindFailed( aError );
    }

// ---------------------------------------------------------------------------
// From class MVPbkSingleContactOperationObserver.
// CPbk2DuplicateContactFinder::VPbkSingleContactOperationComplete
// ---------------------------------------------------------------------------
//    
void CPbk2DuplicateContactFinder::VPbkSingleContactOperationComplete(
        MVPbkContactOperationBase& /*aOperation*/, 
        MVPbkStoreContact* aContact )
    {
    TRAPD( res, CheckDuplicateL( aContact ) );
        
    ++iContactIndex;
    if ( res != KErrNone )
        {
        iObserver->DuplicateFindFailed( res );
        }
    else if ( LastContactRetrieved() )
        {
        iState = EComplete;
        IssueRequest();
        }
    else
        {
        // Continue retrieving
        IssueRequest();
        }
    }

// ---------------------------------------------------------------------------
// From class MVPbkSingleContactOperationObserver.
// CPbk2DuplicateContactFinder::VPbkSingleContactOperationFailed
// ---------------------------------------------------------------------------
//    
void CPbk2DuplicateContactFinder::VPbkSingleContactOperationFailed( 
        MVPbkContactOperationBase& /*aOperation*/, TInt aError )
    {
    iObserver->DuplicateFindFailed( aError );
    }
    
// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::IssueRequest
// ---------------------------------------------------------------------------
//
void CPbk2DuplicateContactFinder::IssueRequest()
    {
    TRequestStatus* st = &iStatus;
    User::RequestComplete( st, KErrNone );
    SetActive();
    }
    
// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::FindText
// ---------------------------------------------------------------------------
//
TPtrC CPbk2DuplicateContactFinder::FindText()
    {
    const MVPbkBaseContactFieldCollection& fields = 
        iContactToCompare->Fields();
    // Find a text from the contact in the order of iFieldTypesForFind.
    // the first data that is found is used as a find text
    const TInt findTypeCount = iFieldTypesForFind.FieldTypeCount();
    for ( TInt i = 0; i < findTypeCount; ++i )
        {
        const MVPbkFieldType& findType = iFieldTypesForFind.FieldTypeAt( i );
        const TInt fieldCount = fields.FieldCount();
        for ( TInt j = 0; j < fieldCount; ++j )
            {
            const MVPbkFieldType* type = 
                fields.FieldAt( j ).BestMatchingFieldType();
            if ( type && findType.IsSame( *type ) )
                {
                const MVPbkContactFieldData& data = 
                    fields.FieldAt( j ).FieldData();
                if ( data.DataType() == EVPbkFieldStorageTypeText )
                    {
                    TPtrC text( MVPbkContactFieldTextData::Cast( 
                        data ).Text() );
                    if ( text.Length() > 0 )
                        {
                        return text;
                        }
                    }
                }
            }    
        }
    // No find text -> no duplicates for contact.
    return TPtrC( KNullDesC );
    }
    
// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::FindOperationL
// ---------------------------------------------------------------------------
//
MVPbkContactOperationBase* CPbk2DuplicateContactFinder::FindOperationL( 
        const TDesC& aFindText )
    {
    if ( iStore )
        {
        MVPbkContactOperation* op = iStore->CreateFindOperationL( aFindText, 
            iFieldTypesForFind, *this );
        CleanupDeletePushL( op );
        op->StartL();
        CleanupStack::Pop(); // op
        return op;
        }
    else
        {
        return iContactManager.FindL( aFindText,
            iFieldTypesForFind, *this );
        }
    }

// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::StartFindL
// ---------------------------------------------------------------------------
//
void CPbk2DuplicateContactFinder::StartFindL()
    {
    delete iFindResults;
    iFindResults = NULL;
    
    delete iContactTitle;
    iContactTitle = NULL;
    iContactTitle = iNameFormatter.GetContactTitleL( 
        iContactToCompare->Fields(), KNameFormattingFlags );
    
    DestroyOperation();
    iContactOperation = FindOperationL( iFindText );
    }

// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::RetrieveContactL
// ---------------------------------------------------------------------------
//    
void CPbk2DuplicateContactFinder::RetrieveContactL()
    {
    DestroyOperation();
    __ASSERT_DEBUG( iFindResults->Count() > iContactIndex,
        Panic( EPanic_RetrieveContactL_OOB ) );
    iContactOperation = iContactManager.RetrieveContactL(
        iFindResults->At( iContactIndex ), *this );
    }

// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::CompleteL
// ---------------------------------------------------------------------------
//        
void CPbk2DuplicateContactFinder::CompleteL()
    {
    iObserver->DuplicateFindComplete();
    }

// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::LastContactRetrieved
// ---------------------------------------------------------------------------
//        
TBool CPbk2DuplicateContactFinder::LastContactRetrieved()
    {
    if ( iDuplicates.Count() >= iMaxDuplicatesToFind ||
         iContactIndex >= iFindResults->Count() )
        {
        return ETrue;
        }
    return EFalse;
    }
    
// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::DestroyOperation
// ---------------------------------------------------------------------------
//        
void CPbk2DuplicateContactFinder::DestroyOperation()
    {
    delete iContactOperation;
    iContactOperation = NULL;
    }

// ---------------------------------------------------------------------------
// CPbk2DuplicateContactFinder::CheckDuplicateL
// ---------------------------------------------------------------------------
//            
void CPbk2DuplicateContactFinder::CheckDuplicateL( 
        MVPbkStoreContact* aContact )
    {    
    aContact->PushL();
    
    TBool duplicate = EFalse;
    HBufC* candidateTitle = iNameFormatter.GetContactTitleOrNullL( 
        aContact->Fields(), KNameFormattingFlags );
    // If the aContact is not a group and it has a title, compare it with iContactTitle.
    // This is because some group has the same name with the contact and they can't be
    // considered as duplicate.
    if ( !aContact->Group() && candidateTitle )
        {
        CleanupStack::PushL( candidateTitle );
        if ( iContactTitle->CompareF( *candidateTitle ) == 0 )
            {
            // Compare the first and last name.
            if ( IsFieldMatched( aContact, R_VPBK_FIELD_TYPE_FIRSTNAME ) && 
                    IsFieldMatched( aContact, R_VPBK_FIELD_TYPE_LASTNAME ) )
                {
                duplicate = ETrue;
                }
            }
        CleanupStack::PopAndDestroy( candidateTitle );
        }
    
    if ( duplicate )
        {
        iDuplicates.AppendL( aContact );
        CleanupStack::Pop(); // aContact
        }
    else
        {
        CleanupStack::PopAndDestroy(); // aContact
        }
    }

TBool CPbk2DuplicateContactFinder::IsFieldMatched( const MVPbkBaseContact* aContact, TInt aFieldId )
    {
    TBool result = EFalse;
    
    const MVPbkBaseContactField* compareContactField = FindFieldById( iContactToCompare, aFieldId );
    const MVPbkBaseContactField* contactField = FindFieldById( aContact, aFieldId );
    if ( NULL == compareContactField && NULL == contactField )
        {
        result = ETrue;
        }
    else if ( compareContactField && contactField )
        {
        const TDesC& compareContactFieldText = 
                MVPbkContactFieldTextData::Cast(compareContactField->FieldData()).Text();
        const TDesC& contactFieldText = 
                MVPbkContactFieldTextData::Cast(compareContactField->FieldData()).Text();
        if ( compareContactFieldText.CompareF( contactFieldText ) == 0 )
            {
            result = ETrue;
            }
        }
    
    return result;
    }

const MVPbkBaseContactField* CPbk2DuplicateContactFinder::FindFieldById( const MVPbkBaseContact* aContact,
        TInt aFieldId )
    {
    const TInt fieldCount = aContact->Fields().FieldCount();
    const MVPbkBaseContactFieldCollection& fieldSet = aContact->Fields();
    for ( TInt i = 0; i < fieldCount; ++i )
        {
        const MVPbkBaseContactField& field = fieldSet.FieldAt(i);
        TInt fieldTypeId = field.BestMatchingFieldType()->FieldTypeResId();
        
        if ( fieldTypeId == aFieldId )
            {
            return &field;
            }
        }
    return NULL;
    }

