#ifndef QT_GUI_LIB
#define QT_GUI_LIB
#endif

#include <ctime>
#include <QtTest>
#include <QLocale>
#include <QEventLoop>
#include <util/log.h>
#include <util/error.h>
#include <util/bitset.h>
#include <util/functions.h>
#include <util/fileops.h>
#include <torrent/torrentcontrol.h>
#include <interfaces/piecedownloader.h>
#include <download/streamingchunkselector.h>
#include <download/downloader.h>
#include <diskio/chunkmanager.h>
#include "testlib/dummytorrentcreator.h"




using namespace bt;

const bt::Uint64 TEST_FILE_SIZE = 15 * 1024 * 1024;

class DummyDownloader : public PieceDownloader
{
public:
    virtual ~DummyDownloader() {}

    virtual bool canAddRequest() const {return true;}
    virtual void cancel(const bt::Request&) {}
    virtual void cancelAll() {}
    virtual bool canDownloadChunk() const {return getNumGrabbed() == 0;}
    virtual void download(const bt::Request&) {}
    virtual void checkTimeouts() {}
    virtual Uint32 getDownloadRate() const {return 0;}
    virtual QString getName() const {return "foobar";}
    virtual bool isChoked() const {return false;}
};

class ExtendedStreamingChunkSelector : public bt::StreamingChunkSelector
{
public:
    ExtendedStreamingChunkSelector() {}
    virtual ~ExtendedStreamingChunkSelector() {}

    void markDownloaded(Uint32 i)
    {
        cman->chunkDownloaded(i);
    }

    Downloader* downloader() {return downer;}
};

class StreamingChunkSelectorTest : public QEventLoop
{
    Q_OBJECT

public:
    StreamingChunkSelectorTest()
    {}

    StreamingChunkSelectorTest(QObject* parent) : QEventLoop(parent)
    {}

private slots:
    void initTestCase()
    {
        QLocale::setDefault(QLocale("main"));
        bt::InitLibKTorrent();
        bt::InitLog("streamingchunkselectortest.log", false, true);
        qsrand(time(0));
    }

    void testSimple()
    {
        DummyTorrentCreator creator;
        bt::TorrentControl tc;
        QVERIFY(creator.createSingleFileTorrent(TEST_FILE_SIZE, "test.avi"));

        Out(SYS_GEN | LOG_DEBUG) << "Created " << creator.torrentPath() << endl;
        try
        {
            tc.init(0, bt::LoadFile(creator.torrentPath()), creator.tempPath() + "tor0", creator.tempPath() + "data/");
            tc.createFiles();
        }
        catch (bt::Error& err)
        {
            Out(SYS_GEN | LOG_DEBUG) << "Failed to load torrent: " << creator.torrentPath() << endl;
            QFAIL("Torrent load failure");
        }


        ExtendedStreamingChunkSelector* csel = new ExtendedStreamingChunkSelector();
        tc.setChunkSelector(csel);
        QVERIFY(csel != 0);
        csel->setSequentialRange(0, 50);

        for (Uint32 i = 0; i < 50; i++)
        {
            DummyDownloader dd;
            Uint32 selected = 0xFFFFFFFF;
            QVERIFY(csel->select(&dd, selected));
            Out(SYS_GEN) << "i = " << i << ", selected = " << selected << endl;
            QVERIFY(selected == i);
            csel->markDownloaded(i);
        }

        // cleanup
        tc.setChunkSelector(0);
    }

    void testCriticalChunkSpread()
    {
        DummyTorrentCreator creator;
        bt::TorrentControl tc;
        QVERIFY(creator.createSingleFileTorrent(2 * TEST_FILE_SIZE, "test2.avi"));

        Out(SYS_GEN | LOG_DEBUG) << "Created " << creator.torrentPath() << endl;
        try
        {
            tc.init(0, bt::LoadFile(creator.torrentPath()), creator.tempPath() + "tor0", creator.tempPath() + "data/");
            tc.createFiles();
        }
        catch (bt::Error& err)
        {
            Out(SYS_GEN | LOG_DEBUG) << "Failed to load torrent: " << creator.torrentPath() << endl;
            QFAIL("Torrent load failure");
        }

        ExtendedStreamingChunkSelector* csel = new ExtendedStreamingChunkSelector();
        tc.setChunkSelector(csel);
        QVERIFY(csel != 0);
        Downloader* downer = csel->downloader();
        QVERIFY(downer != 0);

        // Check that critical chunks are spread over multiple peers
        csel->setSequentialRange(20, 60);
        DummyDownloader dd[32];
        for (Uint32 i = 0; i < 32; i++)
        {
            downer->addPieceDownloader(&dd[i]);
        }

        // Check the spread of the downloaders
        downer->update();
        for (Uint32 i = 20; i < csel->criticialWindowSize(); i++)
        {
            QVERIFY(downer->downloading(i));
            QVERIFY(downer->download(i)->getNumDownloaders() == 32 / csel->criticialWindowSize());
        }

        for (Uint32 i = 0; i < 32; i++)
        {
            QVERIFY(dd[i].getNumGrabbed() == 1);
        }

        for (Uint32 i = 0; i < 32; i++)
        {
            downer->removePieceDownloader(&dd[i]);
        }
        // cleanup
        tc.setChunkSelector(0);
    }
};

QTEST_MAIN(StreamingChunkSelectorTest)

#include "streamingchunkselectortest.moc"

