/* This file is part of the KDE project
   SPDX-FileCopyrightText: 2015 Luiz Romário Santana Rios <luizromario@gmail.com>

   SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "kcompressiondevicetest.h"
#include "kcompressiondevice_p.h"

#include <config-compression.h>

#include <QBuffer>
#include <QDir>
#include <QDirIterator>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTemporaryDir>
#include <QTest>
#include <QVector>

QTEST_MAIN(KCompressionDeviceTest)

static QString archiveFileName(const QString &extension)
{
    return QFINDTESTDATA(QString("kcompressiondevice_test.%1").arg(extension));
}

QNetworkReply *KCompressionDeviceTest::getArchive(const QString &extension)
{
    const QString kcompressionTest = archiveFileName(extension);
    QNetworkReply *r = qnam.get(QNetworkRequest(QUrl::fromLocalFile(kcompressionTest)));

    QEventLoop l;
    connect(&qnam, &QNetworkAccessManager::finished, &l, &QEventLoop::quit);
    l.exec();

    return r;
}

QString KCompressionDeviceTest::formatExtension(KCompressionDevice::CompressionType type) const
{
    switch (type) {
    case KCompressionDevice::GZip:
        return "tar.gz";
    case KCompressionDevice::BZip2:
        return "tar.bz2";
    case KCompressionDevice::Xz:
        return "tar.xz";
    case KCompressionDevice::Zstd:
        return "tar.zst";
    case KCompressionDevice::None:
        return QString();
    }
    return QString(); // silence compiler warning
}

void KCompressionDeviceTest::setDeviceToArchive(QIODevice *d, KCompressionDevice::CompressionType type)
{
    KCompressionDevice *devRawPtr = new KCompressionDevice(d, true, type);
    archive.reset(new KTar(devRawPtr));
    device.reset(devRawPtr);
}

void KCompressionDeviceTest::testBufferedDevice(KCompressionDevice::CompressionType type)
{
    QNetworkReply *r = getArchive(formatExtension(type));
    const QByteArray data = r->readAll();
    QVERIFY(!data.isEmpty());
    const int expectedSize = QFileInfo(archiveFileName(formatExtension(type))).size();
    QVERIFY(expectedSize > 0);
    QCOMPARE(data.size(), expectedSize);
    QBuffer *b = new QBuffer;
    b->setData(data);

    setDeviceToArchive(b, type);
    testExtraction();
}

void KCompressionDeviceTest::testExtraction()
{
    QTemporaryDir temp;
    QString oldCurrentDir = QDir::currentPath();
    QDir::setCurrent(temp.path());

    QVERIFY(archive->open(QIODevice::ReadOnly));
    QVERIFY(archive->directory()->copyTo("."));
    QVERIFY(QDir("examples").exists());
    QVERIFY(QDir("examples/bzip2gzip").exists());
    QVERIFY(QDir("examples/helloworld").exists());
    QVERIFY(QDir("examples/tarlocalfiles").exists());
    QVERIFY(QDir("examples/unzipper").exists());

    const QStringList fileList = {QStringLiteral("examples/bzip2gzip/CMakeLists.txt"),
                                  QStringLiteral("examples/bzip2gzip/main.cpp"),
                                  QStringLiteral("examples/helloworld/CMakeLists.txt"),
                                  QStringLiteral("examples/helloworld/helloworld.pro"),
                                  QStringLiteral("examples/helloworld/main.cpp"),
                                  QStringLiteral("examples/tarlocalfiles/CMakeLists.txt"),
                                  QStringLiteral("examples/tarlocalfiles/main.cpp"),
                                  QStringLiteral("examples/unzipper/CMakeLists.txt"),
                                  QStringLiteral("examples/unzipper/main.cpp")};

    for (const QString &s : fileList) {
        QFileInfo extractedFile(s);
        QFileInfo sourceFile(QFINDTESTDATA("../" + s));

        QVERIFY(extractedFile.exists());
        QCOMPARE(extractedFile.size(), sourceFile.size());
    }
    QDir::setCurrent(oldCurrentDir);
}

void KCompressionDeviceTest::regularKTarUsage()
{
    archive.reset(new KTar(QFINDTESTDATA("kcompressiondevice_test.tar.gz")));
    device.reset();

    testExtraction();
}

void KCompressionDeviceTest::testGZipBufferedDevice()
{
    testBufferedDevice(KCompressionDevice::GZip);
}

void KCompressionDeviceTest::testBZip2BufferedDevice()
{
#if HAVE_BZIP2_SUPPORT
    testBufferedDevice(KCompressionDevice::BZip2);
#else
    QSKIP("This test needs bzip2 support");
#endif
}

void KCompressionDeviceTest::testXzBufferedDevice()
{
#if HAVE_XZ_SUPPORT
    testBufferedDevice(KCompressionDevice::Xz);
#else
    QSKIP("This test needs xz support");
#endif
}

void KCompressionDeviceTest::testZstdBufferedDevice()
{
#ifdef HAVE_ZSTD_SUPPORT_FILE
    testBufferedDevice(KCompressionDevice::Zstd);
#else
    QSKIP("This test needs zstd support");
#endif
}

void KCompressionDeviceTest::testWriteErrorOnOpen()
{
    // GIVEN
    QString fileName("/I/dont/exist/kcompressiondevicetest-write.gz");
    KCompressionDevice dev(fileName, KCompressionDevice::GZip);
    // WHEN
    QVERIFY(!dev.open(QIODevice::WriteOnly));
    // THEN
    QCOMPARE(dev.error(), QFileDevice::OpenError);
#ifdef Q_OS_WIN
    QCOMPARE(dev.errorString(), QStringLiteral("The system cannot find the path specified."));
#else
    QCOMPARE(dev.errorString(), QStringLiteral("No such file or directory"));
#endif
}

void KCompressionDeviceTest::testWriteErrorOnClose()
{
    // GIVEN
    QFile file("kcompressiondevicetest-write.gz");
    KCompressionDevice dev(&file, false, KCompressionDevice::GZip);
    QVERIFY(dev.open(QIODevice::WriteOnly));
    const QByteArray data = "Hello world";
    QCOMPARE(dev.write(data), data.size());
    // This is nasty, it's just a way to try and trigger an error on flush, without filling up a partition first ;)
    file.close();
    QVERIFY(file.open(QIODevice::ReadOnly));
    QTest::ignoreMessage(QtWarningMsg, "QIODevice::write (QFile, \"kcompressiondevicetest-write.gz\"): ReadOnly device");

    // WHEN
    dev.close(); // I want a QVERIFY here... https://bugreports.qt.io/browse/QTBUG-70033

    // THEN
    QCOMPARE(int(dev.error()), int(QFileDevice::WriteError));
}

void KCompressionDeviceTest::testSeekReadUncompressedBuffer_data()
{
    QTest::addColumn<int>("dataSize");
    QTest::addColumn<int>("realDataPos");
    QTest::newRow("1.5buffer") << BUFFER_SIZE + BUFFER_SIZE / 2 << BUFFER_SIZE;
    QTest::newRow("5seekbuffer") << 5 * SEEK_BUFFER_SIZE << 4 * SEEK_BUFFER_SIZE;
}

void KCompressionDeviceTest::testSeekReadUncompressedBuffer()
{
    QFETCH(int, dataSize);
    QFETCH(int, realDataPos);

    QByteArray ba(dataSize, 0);

    // all data is zero except after realDataPos that it's 0 to 9
    for (int i = 0; i < 10; ++i) {
        ba[realDataPos + i] = i;
    }

    QBuffer b;
    b.setData(ba);
    QVERIFY(b.open(QIODevice::ReadOnly));

    KCompressionDevice kcd(&b, false, KCompressionDevice::GZip);
    QVERIFY(kcd.open(QIODevice::ReadOnly));
    QVERIFY(kcd.seek(realDataPos));

    // the 10 bytes after realDataPos should be 0 to 9
    const QByteArray kcdData = kcd.read(10);
    QCOMPARE(kcdData.size(), 10);
    for (int i = 0; i < kcdData.size(); ++i) {
        QCOMPARE(kcdData[i], i);
    }
}
