/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QNetworkDiskCache>
#include <QNetworkCacheMetaData>
#include <QDir>
#include <QBuffer>
#include <QTextStream>
#include <QDebug>
#include <QtTest/QtTest>
#include <QIODevice>
#include <QStandardPaths>



enum Numbers { NumFakeCacheObjects   = 200,    //entries in pre-populated cache
               NumInsertions  = 100,           //insertions to be timed
               NumRemovals    = 100,           //removals to be timed
               NumReadContent = 100,           //meta requests to be timed
               HugeCacheLimit = 50*1024*1024,  // max size for a big cache
               TinyCacheLimit = 1*512*1024}; //  max size for a tiny cache

const QString fakeURLbase = "http://127.0.0.1/fake/";
//fake HTTP body aka payload
const QByteArray payload("Qt rocks!");

class tst_qnetworkdiskcache : public QObject
{
    Q_OBJECT
private:
    void injectFakeData();
    void insertOneItem();
    bool isUrlCached(quint32 id);
    void cleanRecursive(QString &path);
    void cleanupCacheObject();
    void initCacheObject();
    QString cacheDir;
    QNetworkDiskCache *cache;

public slots:
    void initTestCase();
    void cleanupTestCase();

private slots:

    void timeInsertion_data();
    void timeInsertion();
    void timeRead_data();
    void timeRead();
    void timeRemoval_data();
    void timeRemoval();

    void timeExpiration_data();
    void timeExpiration();
};


void tst_qnetworkdiskcache::initTestCase()
{
    cache = 0;
}


void tst_qnetworkdiskcache::cleanupTestCase()
{
    cleanupCacheObject();
    cleanRecursive(cacheDir);
}

void tst_qnetworkdiskcache::timeInsertion_data()
{
    QTest::addColumn<QString>("cacheRootDirectory");

    QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
    QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}

//This functions times an insert() operation.
//You can run it after populating the cache with
//fake data so that more realistic performance
//estimates are obtained.
void tst_qnetworkdiskcache::timeInsertion()
{

    QFETCH(QString, cacheRootDirectory);

    cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
    QDir d;
    qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);

    //Housekeeping
    cleanRecursive(cacheDir); // slow op.
    initCacheObject();

    cache->setCacheDirectory(cacheDir);
    cache->setMaximumCacheSize(qint64(HugeCacheLimit));
    cache->clear();

    //populate some fake data to simulate partially full cache
    injectFakeData(); // SLOW

    //Sanity-check that the first URL that we insert below isn't already in there.
    QVERIFY(isUrlCached(NumFakeCacheObjects) == false);

    // IMPORTANT: max cache size should be HugeCacheLimit, to avoid evictions below
    //time insertion of previously-uncached URLs.
    QBENCHMARK_ONCE {
        for (quint32 i = NumFakeCacheObjects; i < (NumFakeCacheObjects + NumInsertions); i++) {
            //prepare metata for url
            QNetworkCacheMetaData meta;
            QString fakeURL;
            QTextStream stream(&fakeURL);
            stream << fakeURLbase << i;
            QUrl url(fakeURL);
            meta.setUrl(url);
            meta.setSaveToDisk(true);

            //commit payload and metadata to disk
            QIODevice *device = cache->prepare(meta);
            device->write(payload);
            cache->insert(device);
        }
    }

    //SLOW cleanup
    cleanupCacheObject();
    cleanRecursive(cacheDir);

}

void tst_qnetworkdiskcache::timeRead_data()
{
    QTest::addColumn<QString>("cacheRootDirectory");

    QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
    QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}

//Times metadata as well payload lookup
// i.e metaData(), rawHeaders() and data()
void tst_qnetworkdiskcache::timeRead()
{

    QFETCH(QString, cacheRootDirectory);

    cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
    QDir d;
    qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);

    //Housekeeping
    cleanRecursive(cacheDir); // slow op.
    initCacheObject();
    cache->setCacheDirectory(cacheDir);
    cache->setMaximumCacheSize(qint64(HugeCacheLimit));
    cache->clear();

    //populate some fake data to simulate partially full cache
    injectFakeData();

    //Entries in the cache should be > what we try to remove
    QVERIFY(NumFakeCacheObjects > NumReadContent);

    //time metadata lookup of previously inserted URL.
    QBENCHMARK_ONCE {
        for (quint32 i = 0; i < NumReadContent; i++) {
            QString fakeURL;
            QTextStream stream(&fakeURL);
            stream << fakeURLbase << i;
            QUrl url(fakeURL);

            QNetworkCacheMetaData qndc = cache->metaData(url);
            QVERIFY(qndc.isValid()); // we must have read the metadata

            QNetworkCacheMetaData::RawHeaderList raw(qndc.rawHeaders());
            QVERIFY(raw.size()); // we must have parsed the headers from the meta

            QIODevice *iodevice(cache->data(url));
            QVERIFY(iodevice);    //must not be NULL
            iodevice->close();
            delete iodevice;
        }
    }

    //Cleanup (slow)
    cleanupCacheObject();
    cleanRecursive(cacheDir);

}

void tst_qnetworkdiskcache::timeRemoval_data()
{
    QTest::addColumn<QString>("cacheRootDirectory");

    QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
    QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}

void tst_qnetworkdiskcache::timeRemoval()
{

    QFETCH(QString, cacheRootDirectory);

    cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
    QDir d;
    qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);

    //Housekeeping
    initCacheObject();
    cleanRecursive(cacheDir); // slow op.
    cache->setCacheDirectory(cacheDir);
    // Make max cache size HUGE, so that evictions don't happen below
    cache->setMaximumCacheSize(qint64(HugeCacheLimit));
    cache->clear();

    //populate some fake data to simulate partially full cache
    injectFakeData();

    //Sanity-check that the URL is already in there somewhere
    QVERIFY(isUrlCached(NumRemovals-1) == true);
    //Entries in the cache should be > what we try to remove
    QVERIFY(NumFakeCacheObjects > NumRemovals);

    //time removal of previously-inserted URL.
    QBENCHMARK_ONCE {
        for (quint32 i = 0; i < NumRemovals; i++) {
            QString fakeURL;
            QTextStream stream(&fakeURL);
            stream << fakeURLbase << i;
            QUrl url(fakeURL);
            cache->remove(url);
        }
    }

    //Cleanup (slow)
    cleanupCacheObject();
    cleanRecursive(cacheDir);

}

void tst_qnetworkdiskcache::timeExpiration_data()
{
    QTest::addColumn<QString>("cacheRootDirectory");

    QString cacheLoc = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
    QTest::newRow("QStandardPaths Cache Location") << cacheLoc;
}

void tst_qnetworkdiskcache::timeExpiration()
{

    QFETCH(QString, cacheRootDirectory);

    cacheDir = QString( cacheRootDirectory + QDir::separator() + "man_qndc");
    QDir d;
    qDebug() << "Setting cache directory to = " << d.absoluteFilePath(cacheDir);

    //Housekeeping
    initCacheObject();
    cleanRecursive(cacheDir); // slow op.
    cache->setCacheDirectory(cacheDir);
    // Make max cache size HUGE, so that evictions don't happen below
    cache->setMaximumCacheSize(qint64(HugeCacheLimit));
    cache->clear();

    //populate some fake data to simulate partially full cache
    injectFakeData();

    //Sanity-check that the URL is already in there somewhere
    QVERIFY(isUrlCached(NumRemovals-1) == true);
    //Entries in the cache should be > what we try to remove
    QVERIFY(NumFakeCacheObjects > NumRemovals);


    //Set cache limit lower, so this force 1 round of eviction
    cache->setMaximumCacheSize(qint64(TinyCacheLimit));

    //time insertions of additional content, which is likely to internally cause evictions
    QBENCHMARK_ONCE {
        for (quint32 i = NumFakeCacheObjects; i < (NumFakeCacheObjects + NumInsertions); i++) {
            //prepare metata for url
            QNetworkCacheMetaData meta;
            QString fakeURL;
            QTextStream stream(&fakeURL);
            stream << fakeURLbase << i;//codescanner::leave
            QUrl url(fakeURL);
            meta.setUrl(url);
            meta.setSaveToDisk(true);

            //commit payload and metadata to disk
            QIODevice *device = cache->prepare(meta);
            device->write(payload);
            cache->insert(device); // this should trigger evictions, if TinyCacheLimit is small enough
        }
    }

    //Cleanup (slow)
    cleanupCacheObject();
    cleanRecursive(cacheDir);

}
// This function simulates a partially or fully occupied disk cache
// like a normal user of a cache might encounter is real-life browsing.
// The point of this is to trigger degradation in file-system and media performance
// that occur due to the quantity and layout of data.
void tst_qnetworkdiskcache::injectFakeData()
{

    QNetworkCacheMetaData::RawHeaderList headers;
    headers.append(qMakePair(QByteArray("X-TestHeader"),QByteArray("HeaderValue")));


    //Prep cache dir with fake data using QNetworkDiskCache APIs
    for (quint32 i = 0; i < NumFakeCacheObjects; i++) {

        //prepare metata for url
        QNetworkCacheMetaData meta;
        QString fakeURL;
        QTextStream stream(&fakeURL);
        stream << fakeURLbase << i;
        QUrl url(fakeURL);
        meta.setUrl(url);
        meta.setRawHeaders(headers);
        meta.setSaveToDisk(true);

        //commit payload and metadata to disk
        QIODevice *device = cache->prepare(meta);
        device->write(payload);
        cache->insert(device);
    }

}


// Checks if the fake URL #id is already cached or not.
bool tst_qnetworkdiskcache::isUrlCached(quint32 id)
{
    QString str;
    QTextStream stream(&str);
    stream << fakeURLbase << id;
    QUrl url(str);
    QIODevice *iod = cache->data(url);
    return ((iod == 0) ? false : true) ;

}


// Utility function for recursive directory cleanup.
void tst_qnetworkdiskcache::cleanRecursive(QString &path)
{
    QDirIterator it(path, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
    while (it.hasNext()) {
        QFile f(it.next());
        bool err = f.remove();
        Q_UNUSED(err);
    }

    QDirIterator it2(path, QDir::AllDirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
    while (it2.hasNext()) {
        QString s(it2.next());
        QDir dir(s);
        dir.rmdir(s);
    }
}

void tst_qnetworkdiskcache::cleanupCacheObject()
{
    delete cache;
    cache = 0;
}

void tst_qnetworkdiskcache::initCacheObject()
{

   cache = new QNetworkDiskCache();

}
QTEST_MAIN(tst_qnetworkdiskcache)
#include "tst_qnetworkdiskcache.moc"
