// Copyright (c) 2003-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:
//

#include <e32test.h>
#include <cntitem.h>
#include <cntfldst.h>
#include <cntviewbase.h>

#include "CContactViewEventQueue.h"


// Constants
_LIT(KTestName, "T_ViewNotificationError");

_LIT(KTestDbName, "c:T_ViewNotificationError.cdb");


// Global RTest object
LOCAL_D RTest test(KTestName);


/**
 * A helper class for holding the resources needed in the tests.
 */
class CTestResources :
        public CBase, public MContactViewObserver
    {
    public:
        static CTestResources* NewLC();
        void ConstructL();
        void CreateViewL();
        void RemoveFirstContactL();
        ~CTestResources();

        CContactDatabase* iDb;
        CContactViewEventQueue* iViewObserver1;
        CContactViewEventQueue* iViewObserver2;
        CContactViewEventQueue* iViewObserver3;
        RContactViewSortOrder iViewSortOrder;
        CContactLocalView* iAllContactsView;

    private: // From MContactViewObserver.
	    void HandleContactViewEvent
            (const CContactViewBase& aView, const TContactViewEvent& aEvent);
    };

/**
 * Creates new test resources object.
 */
CTestResources* CTestResources::NewLC()
    {
    CTestResources* self = new(ELeave) CTestResources;
    CleanupStack::PushL(self);
    self->ConstructL();
    return self;
    }

void CTestResources::ConstructL()
    {
    // Open test DB
    iDb = CContactDatabase::OpenL(KTestDbName);
    }

void CTestResources::CreateViewL()
    {
    // Create the sort order
    iViewSortOrder.AppendL(KUidContactFieldFamilyName);
    iViewSortOrder.AppendL(KUidContactFieldGivenName);
    iViewSortOrder.AppendL(KUidContactFieldCompanyName);

    // Create a view, assign this class to it's observer
    iAllContactsView = CContactLocalView::NewL
        (*this, *iDb, iViewSortOrder, EContactsOnly);

    // Create three additional view observers
    iViewObserver1 = CContactViewEventQueue::NewL(iAllContactsView);
    iViewObserver2 = CContactViewEventQueue::NewL(iAllContactsView);
    iViewObserver3 = CContactViewEventQueue::NewL(iAllContactsView);
	iViewObserver1->CloseOnItemRemoved();
	iViewObserver2->CloseOnItemRemoved();
	iViewObserver3->CloseOnItemRemoved();
    }

void CTestResources::RemoveFirstContactL()
    {
    iDb->DeleteContactL(1);
    }

void CTestResources::HandleContactViewEvent
        (const CContactViewBase& /*aView*/, const TContactViewEvent& /*aEvent*/)
    {
    // Do nothing
    }

CTestResources::~CTestResources()
    {
    if (iAllContactsView) iAllContactsView->Close(*this);
    iViewSortOrder.Close();
    delete iViewObserver3;
    delete iViewObserver2;
    delete iViewObserver1;
	User::After(1000000);
    delete iDb;
    }


LOCAL_C TContactItemId CreateTestContactL
        (CContactDatabase& aDb, const TDesC& aFamilyName)
    {
    // Create a contact card
    CContactCard* card = CContactCard::NewLC();
    // Create a name field
    CContactItemField* field = CContactItemField::NewLC
        (KStorageTypeText, KUidContactFieldFamilyName);
    field->TextStorage()->SetTextL(aFamilyName);
    card->AddFieldL(*field);
    CleanupStack::Pop(field);
    // Add the contact to the DB
    const TContactItemId contactId = aDb.AddNewContactL(*card);
    CleanupStack::PopAndDestroy(card);
    return contactId;
    }


LOCAL_C CContactIdArray* PopulateTestDbLC(TInt aNumContacts)
    {
    CContactIdArray* ids = CContactIdArray::NewLC();
    CContactDatabase* db = CContactDatabase::ReplaceL(KTestDbName);
    CleanupStack::PushL(db);
    TBuf<50> name;
    _LIT(KNameFormat, "Test%d");
    for (TInt i=1; i <= aNumContacts; ++i)
        {
        name.Format(KNameFormat,i);
        const TContactItemId id = CreateTestContactL(*db,name);
        ids->AddL(id);
        }
    CleanupStack::PopAndDestroy(db);
    return ids;
    }


/**
 * This test demonstrates how a contact view fails to notify its observers
 * if some of the observers deregisters for receiving events. It does not
 * only fail to do that, it also panics the application.
 * There is a design error in CContactViewBase::NotifyObservers.
 */

LOCAL_C void TestViewNotificationL()
    {
    test.Next(_L("Test contact view notification"));


    // Open the populated test DB
    CTestResources* res = CTestResources::NewLC();

    // Create a contact view and three additional
    // observers (please see CTestResources::CreateViewL
    // implementation for extra comments)
    res->CreateViewL();

    // Wait for the first observer to report the view is ready
    TContactViewEvent event;
    test(res->iViewObserver1->ListenForEvent(10,event));
    test(event.iEventType == TContactViewEvent::EReady);

    // ..then the second observer...
    test(res->iViewObserver2->ListenForEvent(10,event));
    test(event.iEventType == TContactViewEvent::EReady);

    // ..and finally the last observer reports it's ready.
    test(res->iViewObserver3->ListenForEvent(10,event));
    test(event.iEventType == TContactViewEvent::EReady);

    // Remove one contact from the db
    res->RemoveFirstContactL();

    // Wait for the first observer to receive the remove event...
    test(res->iViewObserver1->ListenForEvent(10,event));
/** 
 *  After the above line Contact Model crashes to
 *  USER 130 panic, EBadArrayIndex.
 *
 *  The reason is that the observer deregistered itself
 *  from receiving contact view events when it got
 *  EItemRemoved event. The for loop in
 *  CContactViewBase::NotifyObservers can't handle that
 *  situation.
 */
    test(event.iEventType == TContactViewEvent::EItemRemoved);

    // Then second observer should receive the event
    test(res->iViewObserver2->ListenForEvent(10,event));
    test(event.iEventType == TContactViewEvent::EItemRemoved);

    // And finally the third observer should receive the event
    test(res->iViewObserver3->ListenForEvent(10,event));
    test(event.iEventType == TContactViewEvent::EItemRemoved);
    CleanupStack::PopAndDestroy(res);
    }

/**

@SYMTestCaseID     PIM-T-VIEWNOTIFICATIONERROR-0001

*/


LOCAL_C void DoTestsL()
    {
    // Init
    CleanupClosePushL(test);
	test.Start(_L("@SYMTESTCaseID:PIM-T-VIEWNOTIFICATIONERROR-0001 T_ViewNotificationError"));


    // Populate a test DB
    const TInt KNumTestContacts = 100;
    CContactIdArray* testContactIds = PopulateTestDbLC(KNumTestContacts);

    // Execute the tests
    TestViewNotificationL();

    // Cleanup
    CleanupStack::PopAndDestroy(testContactIds);
    test.End();
    CleanupStack::PopAndDestroy();  // test.Close()
    }

GLDEF_C TInt E32Main()
	{
    // Init
    __UHEAP_MARK;
    CTrapCleanup* cleanupStack = CTrapCleanup::New();
    if (!cleanupStack)
        {
        return KErrNoMemory;
        }

    CActiveScheduler* activeScheduler = new CActiveScheduler;
    if (!activeScheduler)
        {
        return KErrNoMemory;
        }
    CActiveScheduler::Install(activeScheduler);

    // Run the tests
    TRAPD(err, DoTestsL());

    // Cleanup
    delete activeScheduler;
    delete cleanupStack;
    __UHEAP_MARKEND;
	return err;
    }
