/*
* Copyright (c) 2002-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:  Contacts Model store filtered contact view implementation.
*
*/


// INCLUDES
#include "CFindViewBase.h"

// VPbkCntModel
#include "CContactStore.h"
#include "CRefineView.h"
#include "CViewBase.h"
#include "CContactLink.h"
#include "CContactBookmark.h"

// VPbkEng
#include <CVPbkFieldTypeRefsList.h>
#include <CVPbkContactFieldIterator.h>
#include <VPbkError.h>
#include <CVPbkAsyncCallback.h>
#include <MVPbkContactNameConstructionPolicy.h>
#include <MVPbkContactFindPolicy.h>
#include <VPbkSendEventUtility.h>

// System includes
#include <cntviewbase.h>

namespace{
    #ifdef _DEBUG
        enum TPanic
            {
            EPreCond_SetFindStringsL = 1
            };

        void Panic(TPanic aPanic)
            {
            _LIT(KPanicCat, "CFindViewBase");
            User::Panic(KPanicCat, aPanic);
            }
    #endif // _DEBUG
} //namespace

namespace VPbkCntModel {

// CONSTANTS
const TInt KGranularity( 4 );

// --------------------------------------------------------------------------
// CCompareView::CCompareView
// --------------------------------------------------------------------------
//
CCompareView::CCompareView( const CContactDatabase& aDb ) :
        CContactViewBase( aDb )
    {
    }

// --------------------------------------------------------------------------
// CCompareView::CompareFieldsL
// --------------------------------------------------------------------------
//
TInt CCompareView::CompareFieldsL( const ::CViewContact &aFirst,
            const ::CViewContact &aSecond )
    {
    return CContactViewBase::CompareFieldsL( aFirst, aSecond );
    }

// --------------------------------------------------------------------------
// CFindViewBase::CFindViewBase
// --------------------------------------------------------------------------
//
CFindViewBase::CFindViewBase(
        MParentViewForFiltering& aParentView,
        CViewBase& aBaseView,
        TBool aOwnsContacts ) :
            iParentView( aParentView ),
            iBaseView( aBaseView ),
            iOwnsContacts( aOwnsContacts )
    {
    }

// --------------------------------------------------------------------------
// CFindViewBase::BaseConstructL
// --------------------------------------------------------------------------
//
void CFindViewBase::BaseConstructL( const MDesCArray& aFindStrings,
        MVPbkContactFindPolicy& aFindPolicy,
        MVPbkContactViewObserver& aExternalViewObserver )
    {
    iObserverOp =
        CVPbkAsyncObjectOperation<MVPbkContactViewObserver>::NewL();
    iFilterObsOp =
        CVPbkAsyncObjectOperation<MFilteredViewSupportObserver>::NewL();

    iFindStrings = new(ELeave)CDesCArrayFlat( KGranularity );
    SetFindStringsL( aFindStrings );

    // Set contact find policy
    iContactFindPolicy = &aFindPolicy;

    // Add the external observer
    iObservers.AppendL( &aExternalViewObserver );

    //Create field type list
    iFieldTypeRefsList = CVPbkFieldTypeRefsList::NewL();

    iViewContact = CViewContact::NewL( iBaseView, SortOrder() );
    }

// --------------------------------------------------------------------------
// CFindViewBase::~CFindViewBase
// --------------------------------------------------------------------------
//
CFindViewBase::~CFindViewBase()
    {
    delete iObserverOp;
    delete iFilterObsOp;
    ResetContacts();
    iMatchedContacts.Close();
    iParentView.RemoveFilteringObserver( *this );
    iObservers.Close();
    iFilteringObservers.Close();

    delete iViewContact;
    delete iFieldTypeRefsList;
    delete iFindStrings;
    }

// --------------------------------------------------------------------------
// CFindViewBase::ActivateContactMatchL
// --------------------------------------------------------------------------
//
void CFindViewBase::ActivateContactMatchL()
    {
    // Remove first if this called second time when updating.
    iParentView.RemoveFilteringObserver( *this );
    // This will cause an asynchrnous view event to this view from the
    // parent view.
    iParentView.AddFilteringObserverL( *this );
    }

// --------------------------------------------------------------------------
// CFindViewBase::ParentObject
// --------------------------------------------------------------------------
//
MVPbkObjectHierarchy& CFindViewBase::ParentObject() const
    {
    return iParentView.ParentObject();
    }

// --------------------------------------------------------------------------
// CFindViewBase::RefreshL
// --------------------------------------------------------------------------
//
void CFindViewBase::RefreshL()
    {
    iParentView.RefreshL();
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactCountL
// --------------------------------------------------------------------------
//
TInt CFindViewBase::ContactCountL() const
    {
    return iMatchedContacts.Count();
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactAtL
// --------------------------------------------------------------------------
//
const MVPbkViewContact& CFindViewBase::ContactAtL(
        TInt aIndex ) const
    {
    __ASSERT_ALWAYS( aIndex >= 0,
        VPbkError::Panic( VPbkError::EInvalidContactIndex ) );

    if ( aIndex >= ContactCountL() )
        {
        User::Leave( KErrArgument );
        }

    const ::CViewContact& viewContact = *iMatchedContacts[ aIndex ];
    iViewContact->SetViewContact(viewContact);
    return *iViewContact;
    }

// --------------------------------------------------------------------------
// CFindViewBase::CreateLinkLC
// --------------------------------------------------------------------------
//
MVPbkContactLink* CFindViewBase::CreateLinkLC(
        TInt aIndex ) const
    {
    __ASSERT_ALWAYS( aIndex >= 0,
        VPbkError::Panic( VPbkError::EInvalidContactIndex ) );

    if ( aIndex >= ContactCountL() )
        {
        User::Leave( KErrArgument );
        }

    TContactItemId contactId = iMatchedContacts[aIndex]->Id();
    return CContactLink::NewLC( iBaseView.Store(), contactId );
    }

// --------------------------------------------------------------------------
// CFindViewBase::IndexOfLinkL
// --------------------------------------------------------------------------
//
TInt CFindViewBase::IndexOfLinkL(
        const MVPbkContactLink& aContactLink ) const
    {
    if (&aContactLink.ContactStore() == &iParentView.ContactStore())
        {
        const CContactLink& link =
            static_cast<const CContactLink&>(aContactLink);
        const TInt count( iMatchedContacts.Count() );
        for ( TInt i(0); i < count; ++i )
            {
            if ( iMatchedContacts[ i ]->Id() == link.ContactId() )
                {
                return i;
                }
            }
        }

    return KErrNotFound;
    }

// --------------------------------------------------------------------------
// CFindViewBase::Type
// --------------------------------------------------------------------------
//
TVPbkContactViewType CFindViewBase::Type() const
    {
    return iParentView.Type();
    }

// --------------------------------------------------------------------------
// CFindViewBase::ChangeSortOrderL
// --------------------------------------------------------------------------
//
void CFindViewBase::ChangeSortOrderL(const MVPbkFieldTypeList& aSortOrder)
    {
    iParentView.ChangeSortOrderL( aSortOrder );
    }

// --------------------------------------------------------------------------
// CFindViewBase::SortOrder
// --------------------------------------------------------------------------
//
const MVPbkFieldTypeList& CFindViewBase::SortOrder() const
    {
    return iParentView.SortOrder();
    }

// --------------------------------------------------------------------------
// CFindViewBase::AddObserverL
// --------------------------------------------------------------------------
//
void CFindViewBase::AddObserverL( MVPbkContactViewObserver& aObserver )
    {
    CVPbkAsyncObjectCallback<MVPbkContactViewObserver>* callback =
        VPbkEngUtils::CreateAsyncObjectCallbackLC(
            *this,
            &CFindViewBase::DoAddObserver,
            &CFindViewBase::DoAddObserverError,
            aObserver);
    iObserverOp->CallbackL( callback );
    CleanupStack::Pop( callback );

    // Insert to first position because events are send in reverse order.
    iObservers.InsertL( &aObserver, 0 );
    }

// --------------------------------------------------------------------------
// CFindViewBase::RemoveObserver
// --------------------------------------------------------------------------
//
void CFindViewBase::RemoveObserver(MVPbkContactViewObserver& aObserver)
    {
    const TInt index = iObservers.Find( &aObserver );
    if ( index != KErrNotFound )
        {
        iObservers.Remove( index );
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::MatchContactStore
// --------------------------------------------------------------------------
//
TBool CFindViewBase::MatchContactStore(const TDesC& aContactStoreUri) const
    {
    return iParentView.MatchContactStore( aContactStoreUri );
    }

// --------------------------------------------------------------------------
// CFindViewBase::MatchContactStoreDomain
// --------------------------------------------------------------------------
//
TBool CFindViewBase::MatchContactStoreDomain(const TDesC& aContactStoreDomain) const
    {
    return iParentView.MatchContactStoreDomain( aContactStoreDomain );
    }

// --------------------------------------------------------------------------
// CFindViewBase::CreateBookmarkLC
// --------------------------------------------------------------------------
//
MVPbkContactBookmark* CFindViewBase::CreateBookmarkLC(
        TInt aIndex ) const
    {
    __ASSERT_ALWAYS( aIndex >= 0,
        VPbkError::Panic( VPbkError::EInvalidContactIndex ) );

    TContactItemId contactId = iMatchedContacts[aIndex]->Id();
    // Link implements also the bookmark interface in this store
    return CContactBookmark::NewLC( contactId, iBaseView.Store() );
    }

// --------------------------------------------------------------------------
// CFindViewBase::IndexOfBookmarkL
// --------------------------------------------------------------------------
//
TInt CFindViewBase::IndexOfBookmarkL(
        const MVPbkContactBookmark& aContactBookmark ) const
    {
    // Bookmark is implemented in this store
    const CContactBookmark* bookmark =
        dynamic_cast<const CContactBookmark*>(&aContactBookmark);
    if (bookmark &&
        &bookmark->ContactStore() == &iParentView.ContactStore() )
        {
        const TInt count( iMatchedContacts.Count() );
        for ( TInt i(0); i < count; ++i )
            {
            if ( iMatchedContacts[ i ]->Id() == bookmark->ContactId() )
                {
                return i;
                }
            }
        }
    return KErrNotFound;
    }

// --------------------------------------------------------------------------
// CFindViewBase::AddFilteringObserverL
// --------------------------------------------------------------------------
//
void CFindViewBase::AddFilteringObserverL(
        MFilteredViewSupportObserver& aObserver )
    {
    // Insert observer in callback function. That ensures that the observer
    // will always get the event asynchronously.

    CVPbkAsyncObjectCallback<MFilteredViewSupportObserver>* callback =
        VPbkEngUtils::CreateAsyncObjectCallbackLC(
            *this,
            &CFindViewBase::DoAddFilteringObserverL,
            &CFindViewBase::DoAddFilteringObserverError,
            aObserver);

    iFilterObsOp->CallbackL( callback );
    CleanupStack::Pop( callback );
    }

// --------------------------------------------------------------------------
// CFindViewBase::RemoveFilteringObserver
// --------------------------------------------------------------------------
//
void CFindViewBase::RemoveFilteringObserver(
        MFilteredViewSupportObserver& aObserver )
    {
    iFilterObsOp->CancelCallback( &aObserver );
    const TInt index( iFilteringObservers.Find( &aObserver ) );
    if ( index != KErrNotFound )
        {
        iFilteringObservers.Remove( index );
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::ViewFiltering
// --------------------------------------------------------------------------
//
MVPbkContactViewFiltering* CFindViewBase::ViewFiltering()
    {
    // This view supports further filtering.
    return this;
    }

// --------------------------------------------------------------------------
// CFindViewBase::CreateFilteredViewLC
// --------------------------------------------------------------------------
//
MVPbkContactViewBase* CFindViewBase::CreateFilteredViewLC(
        MVPbkContactViewObserver& aObserver,
        const MDesCArray& aFindWords,
        const MVPbkContactBookmarkCollection* /*aAlwaysIncludedContacts*/ )
    {
    // NOTE: aAlwaysIncludedContacts is commented because in VPbkCntModel
    // filtered views are linked. CFindView->CRefineView->CRefineView etc.
    // Only the CFindView saves the always included contacts and they
    // are used also by all CRefineView instances through
    // MAlwaysIncludedContacts interface.
    CRefineView* refineView = CRefineView::NewLC( aFindWords, *this,
        iBaseView, aObserver, *iContactFindPolicy,
        *this );
    refineView->ActivateContactMatchL();
    return refineView;
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactViewReady
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactViewReady(
        MVPbkContactViewBase& /*aView*/ )
    {
    SendViewStateEventToObservers();
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactViewUnavailable
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactViewUnavailable(
        MVPbkContactViewBase& /*aView*/ )
    {
    SendViewStateEventToObservers();
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactAddedToView
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactAddedToView(
        MVPbkContactViewBase& aView,
        TInt aIndex,
        const MVPbkContactLink& aContactLink )
    {
    TRAPD( error, HandleContactAddedToViewL( aView, aIndex, aContactLink ) );
    if ( error != KErrNone )
        {
        ContactViewError( aView, error, EFalse );
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactRemovedFromView
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactRemovedFromView(
        MVPbkContactViewBase& aView,
        TInt aIndex,
        const MVPbkContactLink& aContactLink )
    {
    TRAPD( error, HandleContactRemovedFromViewL( aView, aIndex, aContactLink ) );
    if ( error != KErrNone )
        {
        ContactViewError( aView, error, EFalse );
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactViewError
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactViewError( MVPbkContactViewBase& /*aView*/,
        TInt aError, TBool aErrorNotified )
    {
    iViewReady = EFalse;
    ResetContacts();
    SendContactViewErrorEvent( aError, aErrorNotified );
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactViewReadyForFiltering
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactViewReadyForFiltering(
        MParentViewForFiltering& aView )
    {
    TRAPD( error, DoContactViewReadyForFilteringL( aView ) );
    if ( error != KErrNone )
        {
        ContactViewError( aView, error, EFalse );
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactViewUnavailableForFiltering
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactViewUnavailableForFiltering(
        MParentViewForFiltering& /*aView*/ )
    {
    iViewReady = EFalse;
    ResetContacts();
    VPbkEng::SendViewEventToObservers( *this, iFilteringObservers,
        &MFilteredViewSupportObserver::ContactViewUnavailableForFiltering,
        &MVPbkContactViewObserver::ContactViewError );
    }

// --------------------------------------------------------------------------
// CFindViewBase::IsMatchL
// --------------------------------------------------------------------------
//
TBool CFindViewBase::IsMatchL( const MVPbkViewContact& aViewContact )
    {
    return iContactFindPolicy->MatchContactNameL( *iFindStrings,
                                                  aViewContact );
    }

// --------------------------------------------------------------------------
// CFindViewBase::FindStrings
// --------------------------------------------------------------------------
//
const MDesCArray& CFindViewBase::FindStrings() const
    {
    return *iFindStrings;
    }

// -------------------------------------------------------------------------
// CFindViewBase::SetFindStringsL
// --------------------------------------------------------------------------
//
void CFindViewBase::SetFindStringsL( const MDesCArray& aFindStrings )
    {
    __ASSERT_DEBUG( iFindStrings, Panic( EPreCond_SetFindStringsL ) );

    iFindStrings->Reset();
    const TInt count = aFindStrings.MdcaCount();
    for ( TInt i = 0; i < count; ++i )
        {
        iFindStrings->AppendL( aFindStrings.MdcaPoint( i ) );
        }
    }

// -------------------------------------------------------------------------
// CFindViewBase::SendViewStateEventToObservers
// --------------------------------------------------------------------------
//
void CFindViewBase::SendViewStateEventToObservers()
    {
    // Cancel any new AddObserverL callbacks to avoid duplicate event
    // sending.
    iObserverOp->Purge();

    void (MVPbkContactViewObserver::*notifyFunc)(MVPbkContactViewBase&);
    notifyFunc = &MVPbkContactViewObserver::ContactViewReady;

    if ( !iViewReady )
        {
        notifyFunc = &MVPbkContactViewObserver::ContactViewUnavailable;
        }

    VPbkEng::SendViewEventToObservers( *this, iObservers,
        notifyFunc, &MVPbkContactViewObserver::ContactViewError );
    VPbkEng::SendViewEventToObservers( *this, iFilteringObservers,
        notifyFunc, &MVPbkContactViewObserver::ContactViewError );
    }

// -------------------------------------------------------------------------
// CFindViewBase::DoContactViewReadyForFilteringL
// --------------------------------------------------------------------------
//
void CFindViewBase::DoContactViewReadyForFilteringL(
        MVPbkContactViewBase& /*aView*/ )
    {
    ContactMatchL();
    iViewReady = ETrue;
    // Send ContactViewReadyForFiltering event so that other views in
    // stack can rebuild themselves before external observer are notified.
    VPbkEng::SendViewEventToObservers( *this, iFilteringObservers,
        &MFilteredViewSupportObserver::ContactViewReadyForFiltering,
        &MVPbkContactViewObserver::ContactViewError );
    }

// --------------------------------------------------------------------------
// CFindViewBase::HandleContactAddedToViewL
// --------------------------------------------------------------------------
//
void CFindViewBase::HandleContactAddedToViewL( MVPbkContactViewBase& aView,
        TInt aIndex, const MVPbkContactLink& aContactLink )
    {
    if ( &iParentView == &aView )
        {
        // Let sub class do the addition if needed
        DoContactAddedToViewL( aView, aIndex, aContactLink,
            iMatchedContacts );

        // Get the index of the new contact in this view.
        TInt index( IndexOfLinkL( aContactLink ) );
        if ( index != KErrNotFound )
            {
            VPbkEng::SendViewEventToObservers( *this, index, aContactLink,
                iObservers,
                &MVPbkContactViewObserver::ContactAddedToView,
                &MVPbkContactViewObserver::ContactViewError );
            VPbkEng::SendViewEventToObservers( *this, index, aContactLink,
                iFilteringObservers,
                &MVPbkContactViewObserver::ContactAddedToView,
                &MVPbkContactViewObserver::ContactViewError );
            }
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::HandleContactRemovedFromViewL
// --------------------------------------------------------------------------
//
void CFindViewBase::HandleContactRemovedFromViewL(
        MVPbkContactViewBase& aView, TInt /*aIndex*/,
        const MVPbkContactLink& aContactLink )
    {
    if ( &iParentView == &aView )
        {
        TInt index = KErrNotFound;

        CCntModelViewContact* removedContact = NULL;
        CViewContact* viewContact = CViewContact::NewL( iBaseView, SortOrder() );
        CleanupStack::PushL( viewContact );
        const TInt count( iMatchedContacts.Count() );
        for ( TInt i= 0; i < count && !removedContact; ++i  )
            {
            CCntModelViewContact* contact = iMatchedContacts[i];
            viewContact->SetViewContact( *contact );

            // Filter away the removed contact
            if ( aContactLink.RefersTo( *viewContact ) )
                {
                // Remove contact from the array
                iMatchedContacts.Remove( i );
                // Save the removed index for the observers
                index = i;
                // At this point nobody owns removedContact.
                removedContact = contact;
                }
            }
        CleanupStack::PopAndDestroy( viewContact );
        TBool takeCareOfOwnership = (removedContact && iOwnsContacts);

        // Notice: we can not delete the contact instance before
        // all observers have received the event. This is because
        // in case of CRefineView, iMatchedContacts contains references
        // to CCntModelViewContact instances. If we delete the contact
        // before notifying observer then the there will be invalid
        // pointer.
        if ( takeCareOfOwnership )
            {
            CleanupStack::PushL( removedContact );
            }

        if ( index != KErrNotFound )
            {
            VPbkEng::SendViewEventToObservers( *this, index, aContactLink,
                iObservers,
                &MVPbkContactViewObserver::ContactRemovedFromView,
                &MVPbkContactViewObserver::ContactViewError );
            VPbkEng::SendViewEventToObservers( *this, index, aContactLink,
                iFilteringObservers,
                &MVPbkContactViewObserver::ContactRemovedFromView,
                &MVPbkContactViewObserver::ContactViewError );
            }

        if ( takeCareOfOwnership)
            {
            // After all observers have received the Removed -event, it's
            // safe to actually destroy the contact.
            CleanupStack::PopAndDestroy( removedContact );
            }
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::DoAddObserver
// --------------------------------------------------------------------------
//
void CFindViewBase::DoAddObserver( MVPbkContactViewObserver& aObserver )
    {
    if (iViewReady)
        {
        // If this view is ready and there was no error,
        // tell it to the observer
        aObserver.ContactViewReady( *this );
        }
    else
        {
        aObserver.ContactViewUnavailable( *this );
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::DoAddObserverError
// --------------------------------------------------------------------------
//
void CFindViewBase::DoAddObserverError(
        MVPbkContactViewObserver& /*aObserver*/, TInt /*aError*/ )
    {
    // DoAddObserver doesn't leave so nothing to implement here
    }

// --------------------------------------------------------------------------
// CFindViewBase::DoAddFilteringObserverL
// --------------------------------------------------------------------------
//
void CFindViewBase::DoAddFilteringObserverL(
        MFilteredViewSupportObserver& aObserver )
    {
    // Insert to first position because events are send in reverse order.
    iFilteringObservers.InsertL( &aObserver, 0 );

    if (iViewReady)
        {
        aObserver.ContactViewReadyForFiltering( *this );
        aObserver.ContactViewReady( *this );
        }
    else
        {
        aObserver.ContactViewUnavailableForFiltering( *this );
        aObserver.ContactViewUnavailable( *this );
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::DoAddFilteringObserverError
// --------------------------------------------------------------------------
//
void CFindViewBase::DoAddFilteringObserverError(
        MFilteredViewSupportObserver& aObserver, TInt aError )
    {
    // See DoAddFilteringObserverL. If it leaves then adding the aObserver
    // failed.
    aObserver.ContactViewError( *this, aError, EFalse );
    }

// --------------------------------------------------------------------------
// CFindViewBase::ContactMatchL
// --------------------------------------------------------------------------
//
void CFindViewBase::ContactMatchL()
    {
    // Reset current content
    ResetContacts();
    // Subclass matches
    MatchL( iMatchedContacts );
    }


// --------------------------------------------------------------------------
// CFindViewBase::ResetContacts
// --------------------------------------------------------------------------
//
void CFindViewBase::ResetContacts()
    {
    if ( iOwnsContacts )
        {
        iMatchedContacts.ResetAndDestroy();
        }
    else
        {
        iMatchedContacts.Reset();
        }
    }

// --------------------------------------------------------------------------
// CFindViewBase::SendContactViewErrorEvent
// --------------------------------------------------------------------------
//
void CFindViewBase::SendContactViewErrorEvent( TInt aError,
        TBool aErrorNotified )
    {
    // Send first to external observers...
    VPbkEng::SendEventToObservers( *this, aError, aErrorNotified, iObservers,
        &MVPbkContactViewObserver::ContactViewError );
    // ...then to internal. This ensures that events come first from lower
    // level find view.
    VPbkEng::SendEventToObservers( *this, aError, aErrorNotified,
        iFilteringObservers, &MVPbkContactViewObserver::ContactViewError );
    }
} // namespace VPbkCntModel
// End of File
