/*
* 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:  Responsible for interation with the category tables:
*                Artist, Album, Genre and Composer
*
*/


// INCLUDE FILES
#include <sqldb.h>

#include <mpxlog.h>

#include "mpxdbcommonutil.h"
#include "mpxdbcommondef.h"
#include "mpxdbmanager.h"

#include "mpxcollectiondbdef.h"
#include "mpxdbpluginqueries.h"
#include "mpxdbutil.h"
#include "mpxdbcategory.h"

// CONSTANTS

// maximum number of table name entries per query
const TInt KMaxTableNameCount = 2;

// ============================ MEMBER FUNCTIONS ==============================


// ----------------------------------------------------------------------------
// Destructor
// ----------------------------------------------------------------------------
//
CMPXDbCategory::~CMPXDbCategory()
    {
    MPX_FUNC("CMPXDbCategory::~CMPXDbCategory");
    delete iTableName;
    }

// ----------------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------------
//
CMPXDbCategory::CMPXDbCategory(
    CMPXDbManager& aDbManager,
    TMPXGeneralCategory aCategory) :
    CMPXDbTable(aDbManager),
    iCategory(aCategory)
    {
    MPX_FUNC("CMPXDbCategory::CMPXDbCategory");
    }

// ----------------------------------------------------------------------------
// Second phase constructor.
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::BaseConstructL()
    {
    MPX_FUNC("CMPXDbCategory::BaseConstructL");

    CMPXDbTable::BaseConstructL();
    iTableName = MPXDbUtil::TableNameForCategoryL(iCategory).AllocL();
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::AddItemL
// ----------------------------------------------------------------------------
//
TUint32 CMPXDbCategory::AddItemL(
    const TDesC& aName,
    TInt aDriveId,
    TBool& aNewRecord,
    TBool aCaseSensitive)
    {
    MPX_FUNC("CMPXDbCategory::AddItemL");

    // try to find the item first
    TUint32 rowId(MPXDbCommonUtil::GenerateUniqueIdL(iDbManager.Fs(), iCategory,
        aName, aCaseSensitive));
    aNewRecord = !CategoryItemExistsL(aDriveId, rowId);

    if (aNewRecord)
        {
        // insert new
        HBufC* query = PreProcessStringLC(KQueryCategoryInsert);
        HBufC* name = MPXDbCommonUtil::ProcessSingleQuotesLC(aName);

        iDbManager.ExecuteQueryL(aDriveId, *query, rowId, name, 1);

        CleanupStack::PopAndDestroy(name);
        CleanupStack::PopAndDestroy(query);
        }
    else
        {
        // increment the number of songs for the category
        HBufC* query = PreProcessStringLC(KQueryCategoryIncrementSongCount);
        iDbManager.ExecuteQueryL(aDriveId, *query, rowId);
        CleanupStack::PopAndDestroy(query);
        }

    return rowId;
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::GetNameL
// ----------------------------------------------------------------------------
//
HBufC* CMPXDbCategory::GetNameL(
    TUint32 aId)
    {
    MPX_FUNC("CMPXDbCategory::GetNameL");

    RSqlStatement recordset(GetCategoryRecordL(aId));
    CleanupClosePushL(recordset);

    if (recordset.Next() != KSqlAtRow)
        {
        User::LeaveIfError(KErrNotFound);
        }

    HBufC* name = MPXDbCommonUtil::GetColumnTextL(recordset, ECategoryName).AllocL();
    CleanupStack::PopAndDestroy(&recordset);

    return name;
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::CountL
// ----------------------------------------------------------------------------
//
TInt CMPXDbCategory::CountL()
    {
    MPX_FUNC("CMPXDbCategory::CountL");

    HBufC* query = PreProcessStringLC(KQueryCategoryCount);
    TInt count(ExecuteSumQueryL(*query));
    CleanupStack::PopAndDestroy(query);

    return count;
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::FindAllL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::FindAllL(
    const CMPXMedia& aCriteria,
    const TArray<TMPXAttribute>& aAttrs,
    CMPXMediaArray& aMediaArray)
    {
    MPX_FUNC("CMPXDbCategory::FindAllL");

    TMPXGeneralType type = aCriteria.ValueTObjectL<TMPXGeneralType>(KMPXMediaGeneralType);

    const TArray<TMPXAttribute> criteria = aCriteria.Attributes();
    TInt criteriaCount(criteria.Count());

    // process the criteria and construct the criteria string
    CDesCArrayFlat* criteriaArray = new (ELeave) CDesCArrayFlat(criteriaCount + 1);
    CleanupStack::PushL(criteriaArray);

    for (TInt i = 0; i < criteriaCount; ++i)
        {
        const TMPXAttribute& criterion = criteria[i];
        if ((type == EMPXItem) && (criterion == KMPXMediaGeneralId))
            {
            TUint32 itemId = (aCriteria.ValueTObjectL<TMPXItemId>(KMPXMediaGeneralId)).iId2;
            if (MPX_ITEM_CATEGORY(itemId) != iCategory)
                {
                User::Leave(KErrNotSupported);
                }

            HBufC* critStr = PreProcessStringLC(KCriterionCategoryUniqueId);
            MPXDbCommonUtil::AddSqlCriterionL(*criteriaArray, *critStr, itemId);
            CleanupStack::PopAndDestroy(critStr);
            }
        else if (criterion == KMPXMediaGeneralTitle)
            {
#ifdef RD_MPX_COLLECTION_CACHE

            if (aCriteria.ValueText(KMPXMediaGeneralTitle).Length() <= 0)
                {
                TUint32 itemId = MPXDbCommonUtil::GenerateUniqueIdL(
                                    iDbManager.Fs(), iCategory, KNullDesC, EFalse);
                HBufC* critStr = PreProcessStringLC(KCriterionCategoryUniqueId);
                MPXDbCommonUtil::AddSqlCriterionL(*criteriaArray, *critStr, itemId);
                CleanupStack::PopAndDestroy(critStr);
                }
            else
                {

#endif //RD_MPX_COLLECTION_CACHE
                HBufC* critStr = PreProcessStringLC(KCriterionCategoryName);
                HBufC* title = MPXDbCommonUtil::ProcessPatternCharsLC(
                    aCriteria.ValueText(KMPXMediaGeneralTitle));
                MPXDbCommonUtil::AddSqlCriterionL(*criteriaArray, *critStr, *title);
                CleanupStack::PopAndDestroy(2, critStr); // title & critStr
#ifdef RD_MPX_COLLECTION_CACHE
                }
#endif //RD_MPX_COLLECTION_CACHE
            }
        else
            {
            // ignore attribute
            }
        }

    // construct criteria string
    HBufC* criteriaStr = MPXDbCommonUtil::StringFromArrayLC(*criteriaArray, KMCAndKeyword);

    // either get all items or items filtered based on criteria
    HBufC* query = PreProcessStringLC(criteriaStr->Length() ?
        KQueryCategoryItems() : KQueryCategoryAll());
    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(*query, criteriaStr));
    CleanupStack::PopAndDestroy(3, criteriaArray); // query, criteriaStr, criteriaArray
    CleanupClosePushL(recordset);

    // process the results
    ProcessRecordsetL(aAttrs, recordset, aMediaArray);
    CleanupStack::PopAndDestroy(&recordset);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::DecrementSongsForCategoryL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::DecrementSongsForCategoryL(
    const TUint32 aId,
    TInt aDriveId,
    CMPXMessageArray* aItemChangedMessages,
    TBool& aItemExist)
    {
    MPX_FUNC("CMPXDbCategory::DecrementSongsForCategoryL");

    // if just one song uses this category. Use <= just in case
    if (GetSongsCountL(aDriveId, aId) <= 1)
        {
        aItemExist = EFalse;
        // delete the category
        DeleteCategoryL(aId, aDriveId);

        if (aItemChangedMessages)
            {
            // add the item changed message
            MPXDbCommonUtil::AddItemChangedMessageL(*aItemChangedMessages, aId, EMPXItemDeleted,
                iCategory, KDBPluginUid);
            }
        }
    else
        {
        aItemExist = ETrue;
        // decrement the number of songs for the category
        HBufC* query = PreProcessStringLC(KQueryCategoryDecrementSongCount);
        iDbManager.ExecuteQueryL(aDriveId, *query, aId);
        CleanupStack::PopAndDestroy(query);
        }
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::DeleteCategoryL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::DeleteCategoryL(
    TUint32 aId,
    TInt aDriveId)
    {
    MPX_FUNC("CMPXDbCategory::DeleteCategoryL");

    HBufC* query = PreProcessStringLC(KQueryCategoryDelete);
    iDbManager.ExecuteQueryL(aDriveId, *query, aId);
    CleanupStack::PopAndDestroy(query);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::GetCategoryItemsL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::GetCategoryItemsL(
    const TArray<TMPXAttribute>& aAttrs,
    CMPXMediaArray& aMediaArray)
    {
    MPX_FUNC("CMPXDbCategory::GetCategoryItemsL");

    // have to run one query to get all items as opposed to individual queries
    // because of the sorting

    // construct the unique ID criteria string
    // (UniqueId = %u OR UniqueId = %u ...)
    TInt count(aMediaArray.Count());
    HBufC* criteria = HBufC::NewLC((2 * KCriterionCategoryUniqueId().Length() +
        KMCIntegerLen + KMCOrKeyword().Length() + 2) * count);
    TPtr ptr(criteria->Des());
    ptr.Append(KMCOpenBracket);
    for (TInt index = 0; index < count; ++index)
        {
        CMPXMedia* media = aMediaArray[index];

        HBufC* critStr = PreProcessStringLC(KCriterionCategoryUniqueId);
        HBufC* criterion = MPXDbCommonUtil::SqlCriterionLC(*critStr,
            (media->ValueTObjectL<TMPXItemId>(KMPXMediaGeneralId)).iId1);
        ptr.Append(*criterion);
        CleanupStack::PopAndDestroy(criterion);
        CleanupStack::PopAndDestroy(critStr);

        if (index < (count - 1))
            {
            ptr.Append(KMCOrKeyword);
            }
        }
    ptr.Append(KMCCloseBracket);

    // the array has to be reset as the items have to be returned in a different sort order
    aMediaArray.Reset();

    HBufC* query = PreProcessStringLC(KQueryCategoryItems);
    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(*query, criteria));

    CleanupStack::PopAndDestroy(query);
    CleanupStack::PopAndDestroy(criteria);

    CleanupClosePushL(recordset);
    ProcessRecordsetL(aAttrs, recordset, aMediaArray);
    CleanupStack::PopAndDestroy(&recordset);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::GetAllCategoryItemsL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::GetAllCategoryItemsL(
    const TArray<TMPXAttribute>& aAttrs,
    CMPXMediaArray& aMediaArray)
    {
    MPX_FUNC("CMPXDbCategory::GetAllCategoryItemsL");

    HBufC* query = PreProcessStringLC(KQueryCategoryAll);
    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(*query));
    CleanupStack::PopAndDestroy(query);

    CleanupClosePushL(recordset);
    ProcessRecordsetL(aAttrs, recordset, aMediaArray);
    CleanupStack::PopAndDestroy(&recordset);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::GetCategoryItemL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::GetCategoryItemL(
    TUint32 aId,
    const TArray<TMPXAttribute>& aAttrs,
    CMPXMedia& aMedia)
    {
    MPX_FUNC("CMPXDbCategory::GetCategoryItemL");

    HBufC* query = PreProcessStringLC(KQueryCategoryItem);
    ExecuteMediaQueryL(aAttrs, aMedia, *query, aId);
    CleanupStack::PopAndDestroy(query);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::GetSubCategoryItemsL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::GetSubCategoryItemsL(
    TMPXGeneralCategory aParentCategory,
    TUint32 aParentId,
    const TArray<TMPXAttribute>& aAttrs,
    CMPXMediaArray& aMediaArray)
    {
    MPX_FUNC("CMPXDbCategory::GetSubCategoryItemsL");

    // this is only valid for albums belonging to an artist
    ASSERT((iCategory == EMPXAlbum) && (aParentCategory == EMPXArtist));

    // to handle the UREL warning
    (void)aParentCategory;

    RSqlStatement recordset(iDbManager.ExecuteSelectQueryL(KQueryCategorySubcategoryItems, aParentId));
    CleanupClosePushL(recordset);
    ProcessRecordsetL(aAttrs, recordset, aMediaArray);
    CleanupStack::PopAndDestroy(&recordset);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::CategoryItemExistsL
// The category records must be in the same database as the corresponding
// Music record, otherwise when adding a duplicate of a song on a
// different drive this method will return true and the category record won't
// be created.
// ----------------------------------------------------------------------------
//
TBool CMPXDbCategory::CategoryItemExistsL(
    TInt aDriveId,
    TUint32 aId)
    {
    MPX_FUNC("CMPXDbCategory::CategoryItemExistsL");

    HBufC* query = PreProcessStringLC(KQueryCategoryItem);
    RSqlStatement recordset(
        iDbManager.ExecuteSelectQueryL(aDriveId, *query, aId));

    TBool exists(recordset.Next() == KSqlAtRow);

    recordset.Close();
    CleanupStack::PopAndDestroy(query);

    return exists;
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::GetSongsCountL
// ----------------------------------------------------------------------------
//
TInt CMPXDbCategory::GetSongsCountL(
    TInt aDriveId,
    TUint32 aId)
    {
    MPX_FUNC("CMPXDbCategory::GetSongsCountL");

    HBufC* query = PreProcessStringLC(KQueryCategoryGetSongCount);
    RSqlStatement recordset(
        iDbManager.ExecuteSelectQueryL(aDriveId, *query, aId));
    CleanupClosePushL(recordset);

    TInt err(KErrNone);
    TInt ret(0);
    while ((err = recordset.Next()) == KSqlAtRow)
        {
        ret += recordset.ColumnInt(KMPXTableDefaultIndex);
        }

    if (err != KSqlAtEnd)
        {
        User::Leave(err);
        }

    CleanupStack::PopAndDestroy(&recordset);
    CleanupStack::PopAndDestroy(query);

    return ret;
    }

void CMPXDbCategory::UpdateItemL(
    TUint32 /*aId*/,
    const CMPXMedia& /*aMedia*/,
    TInt /*aDriveId*/,
    CMPXMessageArray* /*aItemChangedMessages*/)
	{
	// nothing
	}

// ----------------------------------------------------------------------------
// CMPXDbCategory::UpdateMediaL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::UpdateMediaL(
    RSqlStatement& aRecord,
    const TArray<TMPXAttribute>& aAttrs,
    CMPXMedia& aMedia)
    {
    MPX_FUNC("CMPXDbCategory::UpdateMediaL");

    TInt count(aAttrs.Count());
    for (TInt i = 0; i < count; ++i)
        {
        TInt contentId(aAttrs[i].ContentId());
        TUint attributeId(aAttrs[i].AttributeId());

        if (contentId == KMPXMediaIdGeneral)
            {
            if (attributeId & EMPXMediaGeneralId)
                {
                aMedia.SetTObjectValueL<TMPXItemId>(KMPXMediaGeneralId,
                    aRecord.ColumnInt64(ECategoryUniqueId));
                }
            if (attributeId & EMPXMediaGeneralTitle)
                {
                aMedia.SetTextValueL(KMPXMediaGeneralTitle,
                    MPXDbCommonUtil::GetColumnTextL(aRecord, ECategoryName));
                }
            if (attributeId & EMPXMediaGeneralCount)
                {
                aMedia.SetTObjectValueL<TInt>(KMPXMediaGeneralCount,
                    GetSongsCountL(KDbManagerAllDrives,
                    aRecord.ColumnInt64(ECategoryUniqueId)));
                }
            } // end if contentId == KMPXMediaIdGeneral
        } // end for

    aMedia.SetTObjectValueL<TMPXGeneralType>(KMPXMediaGeneralType, EMPXItem);
    aMedia.SetTObjectValueL<TMPXGeneralCategory>(KMPXMediaGeneralCategory, iCategory);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::GetCategoryRecordL
// ----------------------------------------------------------------------------
//
RSqlStatement CMPXDbCategory::GetCategoryRecordL(
    TUint32 aId)
    {
    MPX_FUNC("CMPXDbCategory::GetCategoryRecordL");
    HBufC* query = PreProcessStringLC(KQueryCategoryItem);
    RSqlStatement statement(iDbManager.ExecuteSelectQueryL(*query, aId));
    CleanupStack::PopAndDestroy(query);

    return statement;
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::PreProcessStringLC
// ----------------------------------------------------------------------------
//
HBufC* CMPXDbCategory::PreProcessStringLC(
    const TDesC& aQuery)
    {
    MPX_FUNC("CMPXDbCategory::PreProcessStringLC");

    HBufC* query = HBufC::NewLC(aQuery.Length() + KMaxTableNameCount * (iTableName->Length() +
        KCategoryTablePlaceholder().Length()));
    TPtr queryPtr(query->Des());

    // copy the query string
    queryPtr = aQuery;

    // replace all instances of the placeholder with the actual table name
    TInt index(0);
    while ((index = queryPtr.Find(KCategoryTablePlaceholder)) != KErrNotFound)
        {
        queryPtr.Replace(index, KCategoryTablePlaceholder().Length(), *iTableName);
        }

    return query;
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::ProcessRecordsetL
// Unknown item is stored in the database as NULL (name field). This ensures the
// unknown item to be the 1st found record if it exists. This will save time in
// searching for the unknown record among the results and avoid performing
// descriptor comparison. If the 1st record is the unknown item, it won't be
// appended to the array until all other records have been put in the array.
//
// NOTE: putting unknown item to the end of the array only takes place when title
//       field is requested. normal sorting algorithm occurs if title isn't
//       requested.
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::ProcessRecordsetL(
    const TArray<TMPXAttribute>& aAttrs,
    RSqlStatement& aRecordset,
    CMPXMediaArray& aMediaArray)
    {
    // populate the array
    TBool firstRecord(ETrue);
    CMPXMedia* unknownMedia(NULL);
    TInt prevId(0);
    TInt err(KErrNone);

    TInt pPath(0);
    if (aMediaArray.Count())
        {
        CMPXMedia* pMedia = aMediaArray[0];
        if (pMedia->IsSupported(KMPXMediaGeneralValue))
            { // Query excuted by OpenL
            pPath = pMedia->ValueTObjectL<TInt>(KMPXMediaGeneralValue);
            MPX_ASSERT(pPath);
            }
        }
    RArray<TMPXItemId> ids;
    CleanupClosePushL(ids);

    while ((err = aRecordset.Next()) == KSqlAtRow)
        {
        TUint32 rowId(aRecordset.ColumnInt64(ECategoryUniqueId));
        if (prevId == rowId)
            {
            continue;
            }

        prevId = rowId;
        CMPXMedia* media = CMPXMedia::NewL();
        CleanupStack::PushL(media);

        UpdateMediaL(aRecordset, aAttrs, *media);

        if (firstRecord &&
            (MPXDbCommonUtil::GetColumnTextL(aRecordset, ECategoryName).Length() == 0))
            {
            unknownMedia = media;
            }

        if (!firstRecord || !unknownMedia)
            {
            if (media->IsSupported(KMPXMediaGeneralId) && pPath)
                {
                ids.AppendL(media->ValueTObjectL<TMPXItemId>(KMPXMediaGeneralId));
                }
            aMediaArray.AppendL(*media);
            CleanupStack::PopAndDestroy(media);
            }

        firstRecord = EFalse;
        } // end while

    if (err != KSqlAtEnd)
        {
        User::LeaveIfError(err);
        }

    if (unknownMedia)
        {
        if (unknownMedia->IsSupported(KMPXMediaGeneralId) && pPath)
            {
            ids.AppendL(unknownMedia->ValueTObjectL<TMPXItemId>(KMPXMediaGeneralId));
            }
        aMediaArray.AppendL(*unknownMedia);
        CleanupStack::PopAndDestroy(unknownMedia);
        }

    // Append ids to the returned path
    if (pPath)
        {
        ((CMPXCollectionPath*)pPath)->AppendL(ids.Array());
        }
    CleanupStack::PopAndDestroy(&ids);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::CreateTableL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::CreateTableL(
    RSqlDatabase& aDatabase,
    TBool /* aCorruptTable */)
    {
    MPX_FUNC("CMPXDbCategory::CreateTableL");

    // create the table
    HBufC* query = PreProcessStringLC(KCategoryCreateTable);
    User::LeaveIfError(aDatabase.Exec(*query));
    CleanupStack::PopAndDestroy(query);

    // do not create an index on the Name field
    // as it only slows down the insert/update queries overall
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::DropTableL
// ----------------------------------------------------------------------------
//
void CMPXDbCategory::DropTableL(
    RSqlDatabase& aDatabase)
    {
    MPX_FUNC("CMPXDbCategory::DropTableL");

    HBufC* query = PreProcessStringLC(KCategoryDropTable);
    User::LeaveIfError(aDatabase.Exec(*query));
    CleanupStack::PopAndDestroy(query);
    }

// ----------------------------------------------------------------------------
// CMPXDbCategory::CheckTableL
// ----------------------------------------------------------------------------
//
TBool CMPXDbCategory::CheckTableL(
    RSqlDatabase& aDatabase)
    {
    MPX_FUNC("CMPXDbCategory::CheckTableL");

    HBufC* query = PreProcessStringLC(KCategoryCheckTable);
    TBool check(DoCheckTable(aDatabase, *query));
    CleanupStack::PopAndDestroy(query);

    return check;
    }

// End of File
