#include <QtTest/QtTest>
#include <QDirIterator>
#include <QFileInfo>

#include "autotest_cod.h"
#include "compression.h"

class AutoTest_COD12_PC : public AutoTest_COD {
    Q_OBJECT

    const QString EXPORT_DIR = "./exports/cod12/pc";

private slots:
    void initTestCase();

    void testDecompression_data();
    void testDecompression();

    void testCompression_data();
    void testCompression();

    void testFactory_data();
    void testFactory();

    void cleanupTestCase();
};

void AutoTest_COD12_PC::initTestCase() {
    // Ensure the exports directory exists.
    createDirectory(EXPORT_DIR);
}

void AutoTest_COD12_PC::testDecompression_data() {
    AutoTest_COD::testDecompression_data();
}

void AutoTest_COD12_PC::testDecompression() {
    QFETCH(QString, fastFilePath);

    const QString testName = "Decompress: " + fastFilePath;

    // Open the original .ff file.
    QFile testFastFile(fastFilePath);
    bool fastFileOpened = testFastFile.open(QIODevice::ReadOnly);
    if (!fastFileOpened) {
        recordResult(testName, false);
    }
    QVERIFY2(fastFileOpened,
             qPrintable("Failed to open test fastfile: " + fastFilePath));
    const QByteArray testFFData = testFastFile.readAll();
    testFastFile.close();

    // Assume the first 12 bytes are a header; the rest is zlib-compressed zone data.
    XDataStream fastFileStream(testFFData);
    fastFileStream.setByteOrder(XDataStream::LittleEndian);

    QByteArray magic(8, Qt::Uninitialized);
    fastFileStream.readRawData(magic.data(), 8);
    bool properMagic = magic == "TAff0000";
    if (!properMagic) {
        recordResult(testName, false);
    }
    QVERIFY2(properMagic,
             qPrintable("Invalid fastfile magic: " + magic));

    quint32 version;
    fastFileStream >> version;
    bool properVersion = version == 593;
    if (!properVersion) {
        recordResult(testName, false);
    }
    QVERIFY2(properVersion,
             qPrintable("Invalid fastfile version: " + QString::number(properVersion)));

    fastFileStream.skipRawData(1);

    quint8 compressionFlag, platformFlag, encryptionFlag;
    fastFileStream >> compressionFlag >> platformFlag >> encryptionFlag;

    bool properCompression = compressionFlag == 1;
    if (!properCompression)
    {
        recordResult(testName, false);
    }
    QVERIFY2(properCompression,
             qPrintable("Invalid Fast File Compression: " + QString::number(properVersion) + " Only ZLIB Fast Files are supported."));

    bool properPlatform = platformFlag == 0;
    if (!properPlatform)
    {
        recordResult(testName, false);
    }
    QVERIFY2(properPlatform,
             qPrintable("Invalid Fast File Platform: " + QString::number(properVersion) + " Only PC Fast Files are supported."));

    bool properEncryption = encryptionFlag == 0;
    if (!properEncryption)
    {
        recordResult(testName, false);
    }
    QVERIFY2(properEncryption,
             qPrintable("Encrypted Fast Files are not supported"));

    fastFileStream.device()->seek(144);

    quint64 size;
    fastFileStream >> size;

    fastFileStream.device()->seek(584);

    QByteArray testZoneData;
    int consumed = 0;
    while (consumed < size) {
        quint32 compressedSize, decompressedSize, blockSize, blockPosition;
        fastFileStream >> compressedSize >> decompressedSize >> blockSize >> blockPosition;

        if (blockPosition != fastFileStream.device()->pos() - 16) {
            qDebug() << "Block position does not match stream position!";
            break;
        }

        if (decompressedSize == 0) {
            fastFileStream.skipRawData((((fastFileStream.device()->pos()) + ((0x800000) - 1)) & ~((0x800000) - 1)) - fastFileStream.device()->pos());
            continue;
        }

        fastFileStream.skipRawData(2);
        QByteArray compressedData(compressedSize - 2, Qt::Uninitialized);
        fastFileStream.readRawData(compressedData.data(), compressedSize - 2);
        testZoneData.append(Compression::DecompressDeflate(compressedData));

        consumed += decompressedSize;
        fastFileStream.device()->seek(blockPosition + 16 + blockSize);
    }

    // Verify the decompressed data via its embedded zone size.
    XDataStream zoneStream(testZoneData);
    zoneStream.setByteOrder(XDataStream::LittleEndian);
    quint32 zoneSize;
    zoneStream >> zoneSize;
    bool sizeMatches = zoneSize + 44 == testZoneData.size();
    if (!sizeMatches) {
        recordResult(testName, false);
    }
    //QVERIFY2(sizeMatches,
    //         qPrintable("Decompression validation failed for: " + fastFilePath));

    // Write the decompressed zone data to the exports folder with a .zone extension.
    QFileInfo fi(fastFilePath);
    QString outputFileName = fi.completeBaseName() + ".zone";
    QString outputFilePath = QDir(EXPORT_DIR).filePath(outputFileName);
    QFile outputFile(outputFilePath);
    bool zoneFileOpened = outputFile.open(QIODevice::WriteOnly);
    if (!zoneFileOpened) {
        recordResult(testName, false);
    }
    QVERIFY2(zoneFileOpened,
             qPrintable("Failed to open output zone file for writing: " + outputFilePath));
    outputFile.write(testZoneData);
    outputFile.close();
}

void AutoTest_COD12_PC::testCompression_data() {
    AutoTest_COD::testCompression_data();
}

void AutoTest_COD12_PC::testCompression() {
    QFETCH(QString, zoneFilePath);

    QFile zoneFile(zoneFilePath);
    QVERIFY2(zoneFile.open(QIODevice::ReadOnly), qPrintable("Failed to open zone file: " + zoneFilePath));
    QByteArray decompressedData = zoneFile.readAll();
    zoneFile.close();

    QFileInfo fi(zoneFilePath);
    QString originalFFPath = QDir(getFastFileDirectory()).filePath(fi.completeBaseName() + ".ff");

    QFile originalFile(originalFFPath);
    QVERIFY2(originalFile.open(QIODevice::ReadOnly), qPrintable("Failed to open original .ff file: " + originalFFPath));
    QByteArray originalFFData = originalFile.readAll();
    originalFile.close();

    QByteArray header = originalFFData.left(12);
    QByteArray newCompressedData = Compression::CompressZLIBWithSettings(decompressedData, Z_BEST_SPEED);

    int remainder = (newCompressedData.size() + 12) % 32;
    if (remainder != 0) {
        int paddingNeeded = 32 - remainder;
        newCompressedData.append(QByteArray(paddingNeeded, '\0'));
    }

    QByteArray recompressedData = header + newCompressedData;

    QString recompressedFilePath = QDir(EXPORT_DIR).filePath(fi.completeBaseName() + ".ff");
    QFile recompressedFile(recompressedFilePath);
    QVERIFY2(recompressedFile.open(QIODevice::WriteOnly), qPrintable("Failed to write recompressed file."));
    recompressedFile.write(recompressedData);
    recompressedFile.close();

    QCOMPARE(recompressedData, originalFFData);
}

void AutoTest_COD12_PC::testFactory_data() {
    AutoTest_COD::testFactory_data();
}

void AutoTest_COD12_PC::testFactory() {
    QFETCH(QString, fastFilePath);

    const QString testName = "Factory ingest: " + fastFilePath;

    FastFile* fastFile = FastFile::Open(fastFilePath);

    const QString game = fastFile->GetGame();
    bool correctGame = game == "COD12";
    if (!correctGame) {
        recordResult(testName, false);
    }
    QVERIFY2(correctGame
             , qPrintable("Factory parsed wrong game [" + game + "] for fastfile: " + fastFilePath));

    const QString platform = fastFile->GetPlatform();
    bool correctPlatform = platform == "PC";
    if (!correctPlatform) {
        recordResult(testName, false);
    }
    QVERIFY2(correctPlatform
             , qPrintable("Factory parsed wrong platform [" + platform + "] for fastfile: " + fastFilePath));

    recordResult(testName, true);
}

void AutoTest_COD12_PC::cleanupTestCase() {
    // Any cleanup if necessary.
}

// Don't generate a main() function
#include "autotest_cod12_pc.moc"
