/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QObject>
#include <qmobilityglobal.h>
#include <qtcontacts.h>
#include <QtTest/QtTest>
#include <QDebug>
#include <QFile>
#include <qversitreader.h>
#include <qversitcontactimporter.h>

QTM_USE_NAMESPACE

const QString testDataPath("c:/data/others");
const QString resultFileName("c:/data/results/tst_performance_results.csv");

class tst_Performance : public QObject
{
    Q_OBJECT

private slots:  // Init & cleanup
    void init();
    void cleanup();

private slots:  // Test cases
    void validate_data();
    void validate();
    void fetch_data();
    void fetch();

private:
    void addTestRows(QString fileNameFilter);
    bool importContacts(QString vcardFileName, int &count);
    bool detailsExist(QList<QContact> contacts, QStringList definitionNames);
    void startBenchmark(QString benchmarkName);
    bool endBenchmark(const int target);

    QContactManager *m_cm;
    QTime m_timer;
    QString m_benchmarkName;
};


void tst_Performance::init()
{
    m_cm = new QContactManager("symbian");
    m_cm->removeContacts(m_cm->contactIds(), 0);
}

void tst_Performance::cleanup()
{
    m_cm->removeContacts(m_cm->contactIds(), 0);
    delete m_cm;
}

void tst_Performance::validate_data()
{
    // Path to a file containing vcards, constructed dynamically
    QTest::addColumn<QString>("vcardFileName");

   // Add new test row for each vcf file
   addTestRows(QString("tst_performance_validate*.vcf"));
}

void tst_Performance::validate()
{
    QFETCH(QString, vcardFileName);

    // Import contacts from the file into contacts database
    int count(0);
    QVERIFY(importContacts(vcardFileName, count));

    // QContactName
    QContactFetchHint nameHint;
    nameHint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName);
    QList<QContact> contacts = m_cm->contacts(QList<QContactSortOrder>(), nameHint);
    QCOMPARE(contacts.count(), count);
    QVERIFY(detailsExist(contacts, QStringList() << QContactName::DefinitionName));

    // QContactPhoneNumber
    QContactFetchHint numberHint;
    numberHint.setDetailDefinitionsHint(QStringList() << QContactPhoneNumber::DefinitionName);
    contacts = m_cm->contacts(QList<QContactSortOrder>(), numberHint);
    QCOMPARE(contacts.count(), count);
    QVERIFY(detailsExist(contacts, QStringList() << QContactPhoneNumber::DefinitionName));

    // QContactAddress
    QContactFetchHint addressHint;
    addressHint.setDetailDefinitionsHint(QStringList() << QContactAddress::DefinitionName);
    contacts = m_cm->contacts(QList<QContactSortOrder>(), addressHint);
    QCOMPARE(contacts.count(), count);
    QVERIFY(detailsExist(contacts, QStringList() << QContactAddress::DefinitionName));
}

void tst_Performance::fetch_data()
{
    // Path to a file containing vcards, constructed dynamically
    QTest::addColumn<QString>("vcardFileName");

    // Add new test row for each vcf file
    addTestRows(QString("tst_performance_fetch*.vcf"));
}

void tst_Performance::fetch()
{
    QFETCH(QString, vcardFileName);

    // Import contacts from the file into contacts database
    int count(0);
    QVERIFY(importContacts(vcardFileName, count));

    // The actual benchmark measurements begin... We cannot use QBENCHMARK
    // since it does not give separate results for each operation, but shows
    // only the latest result in the log. Also, it does not support writing the
    // result into an external CSV file.

    {
        // measure contact id fetch
        QList<QContactLocalId> ids;
        startBenchmark(vcardFileName + QString(" / QContactManager::contactIds"));
        ids = m_cm->contactIds();
        QVERIFY(endBenchmark(0));
        QCOMPARE(ids.count(), count);
    }

    {
        // measure contact fetch
        QList<QContact> contacts;
        startBenchmark(vcardFileName + QString(" / QContactManager::contacts"));
        contacts = m_cm->contacts();
        QVERIFY(endBenchmark(0));
        QCOMPARE(contacts.count(), count);
    }

    {
        // measure contact fetch with fetch hint (only name)
        QContactFetchHint hint;
        hint.setDetailDefinitionsHint(QStringList() << QContactName::DefinitionName);
        startBenchmark(vcardFileName + QString(" / QContactManager::contacts w/ detail definitions hint"));
        QList<QContact> contacts = m_cm->contacts(QList<QContactSortOrder>(), hint);
        QVERIFY(endBenchmark(0));
        QCOMPARE(contacts.count(), count);
    }

    {
        // measure contact fetch without relationships and binary blobs
        QContactFetchHint hint;
        hint.setOptimizationHints(QContactFetchHint::NoRelationships | QContactFetchHint::NoBinaryBlobs);
        startBenchmark(vcardFileName + QString(" / QContactManager::contacts w/ optimization hints"));
        QList<QContact> contacts = m_cm->contacts(QList<QContactSortOrder>(), hint);
        QVERIFY(endBenchmark(0));
        QCOMPARE(contacts.count(), count);
    }

    // TODO: measure sort
    // TODO: measure filter etc...
}

void tst_Performance::addTestRows(QString fileNameFilter)
{
    // add new test row for each vcard file
    QDir vcfDir(testDataPath);
    QStringList tst_dataFiles = vcfDir.entryList(QStringList() << fileNameFilter);
    foreach (QString tst_dataFile, tst_dataFiles) {
        qDebug() << "Found new vcard file: " << tst_dataFile;
        QTest::newRow(tst_dataFile.toAscii()) << tst_dataFile;
    }
}

bool tst_Performance::importContacts(QString vcardFileName, int &contactCount)
{
    contactCount = 0;

    QFile vcardFile(testDataPath + "/" + vcardFileName);
    if (!vcardFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return false;
    }

    // Read contacts from vcard file
    while (!vcardFile.atEnd()) {
        QBuffer vcard;
        QByteArray line = vcardFile.readLine();
        if (line.startsWith(QByteArray("BEGIN:VCARD"))) {
            vcard.open(QBuffer::ReadWrite);
            vcard.write(line);
            bool complete(false);
            while (!complete && !vcardFile.atEnd()) {
                line = vcardFile.readLine();
                vcard.write(line);
                if (line.contains(QByteArray("END:VCARD"))) {
                    complete = true;
                }
            }

            // Parse the input into QVersitDocuments
            // Note: we could also use the more convenient QVersitReader(QByteArray) constructor.
            QVersitReader reader(vcard.buffer());
            if (!reader.startReading()) {return false;}
            if (!reader.waitForFinished()) {return false;}

            // Convert the QVersitDocuments to QContacts
            QList<QVersitDocument> inputDocuments = reader.results();
            QVersitContactImporter importer;
            if (!importer.importDocuments(inputDocuments)) {
                return false;
            }

            QList<QContact> contacts = importer.contacts();
            if (!m_cm->saveContacts(&contacts, 0)) {
                return false;
            }
            contactCount++;
        }
    }
    //qDebug() << contactCount << "contacts added in total";
    return true;
}

bool tst_Performance::detailsExist(QList<QContact> contacts, QStringList definitionNames)
{
    foreach(QContact contact, contacts) {
        foreach (QString definitionName, definitionNames) {
            QList<QContactDetail> details = contact.details(definitionName);
            if (!details.count()) {
                qDebug() << "Detail not found:" << definitionName;
                return false;
            }
            foreach (QContactDetail detail, details) {
                contact.removeDetail(&detail);
            }
        }

        // Verify that there were no extra details (this is important from
        // performance point of view; we don't want to waste time by giving
        // details that the client does not need.
        foreach (QContactDetail detail, contact.details()) {
            // Skip "always on" details
            if (detail.definitionName() != QContactDisplayLabel::DefinitionName
                && detail.definitionName() != QContactType::DefinitionName
                && detail.definitionName() != QContactGuid::DefinitionName
                && detail.definitionName() != QContactTimestamp::DefinitionName) {
                qDebug() << "Extra detail: " << detail.definitionName();
                // displaylabel, type, guid, timestamp
                return false;
            }
        }
    }
    return true;
}

void tst_Performance::startBenchmark(QString benchmarkName)
{
    qDebug() << "Starting benchmark" << benchmarkName;
    m_benchmarkName = benchmarkName;
    m_timer.start();
}

bool tst_Performance::endBenchmark(const int target)
{
    Q_ASSERT(!m_benchmarkName.isEmpty());
    int result = m_timer.elapsed();
    qDebug() << "The result for benchmark" << m_benchmarkName << "is [" << result << "]ms";
    QFile resultFile(resultFileName);
    if (resultFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) {
        QTextStream out(&resultFile);
        out << m_benchmarkName + QString(";") + QString::number(result) + "\r\n";
        resultFile.close();
    }

    if (target == 0 || result <= target) {
        return true;
    }
    qDebug() << "Target not reached!";
    return false;
}

QTEST_MAIN(tst_Performance);
#include "tst_performance.moc"
