/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "tst_qmakelib.h"

#include <ioutils.h>

using namespace QMakeInternal;

void tst_qmakelib::initTestCase()
{
    m_indir = QFINDTESTDATA("testdata");
    m_outdir = m_indir + QLatin1String("_build");
    m_env.insert(QStringLiteral("E1"), QStringLiteral("env var"));
#ifdef Q_OS_WIN
    m_env.insert(QStringLiteral("COMSPEC"), qgetenv("COMSPEC"));
#endif
    m_prop.insert(ProKey("P1"), ProString("prop val"));
    m_prop.insert(ProKey("QT_HOST_DATA/get"), ProString(m_indir));
    m_prop.insert(ProKey("QT_HOST_DATA/src"), ProString(m_indir));

    QVERIFY(!m_indir.isEmpty());
    QVERIFY(QDir(m_outdir).removeRecursively());
    QVERIFY(QDir().mkpath(m_outdir));
}

void tst_qmakelib::cleanupTestCase()
{
    QVERIFY(QDir(m_outdir).removeRecursively());
}

void tst_qmakelib::proString()
{
    QString qs1(QStringLiteral("this is a string"));

    ProString s1(qs1);
    QCOMPARE(s1.toQString(), QStringLiteral("this is a string"));

    ProString s2(qs1, 5, 8);
    QCOMPARE(s2.toQString(), QStringLiteral("is a str"));

    QCOMPARE(s2.hash(), 0x80000000);
    qHash(s2);
    QCOMPARE(s2.hash(), 90404018U);

    QCOMPARE(s2.mid(0, 10).toQString(), QStringLiteral("is a str"));
    QCOMPARE(s2.mid(1, 5).toQString(), QStringLiteral("s a s"));
    QCOMPARE(s2.mid(10, 3).toQString(), QStringLiteral(""));

    QString qs2(QStringLiteral("   spacy  string   "));
    QCOMPARE(ProString(qs2, 3, 13).trimmed().toQString(), QStringLiteral("spacy  string"));
    QCOMPARE(ProString(qs2, 1, 17).trimmed().toQString(), QStringLiteral("spacy  string"));

    QVERIFY(s2.toQStringRef().string()->isSharedWith(qs1));
    s2.prepend(ProString("there "));
    QCOMPARE(s2.toQString(), QStringLiteral("there is a str"));
    QVERIFY(!s2.toQStringRef().string()->isSharedWith(qs1));

    ProString s3("this is a longish string with bells and whistles");
    s3 = s3.mid(18, 17);
    // Prepend to detached string with lots of spare space in it.
    s3.prepend(ProString("another "));
    QCOMPARE(s3.toQString(), QStringLiteral("another string with bells"));

    // Note: The string still has plenty of spare space.
    s3.append(QLatin1Char('.'));
    QCOMPARE(s3.toQString(), QStringLiteral("another string with bells."));
    s3.append(QLatin1String(" eh?"));
    QCOMPARE(s3.toQString(), QStringLiteral("another string with bells. eh?"));

    s3.append(ProString(" yeah!"));
    QCOMPARE(s3.toQString(), QStringLiteral("another string with bells. eh? yeah!"));

    bool pending = false; // Not in string, but joining => add space
    s3.append(ProString("..."), &pending);
    QCOMPARE(s3.toQString(), QStringLiteral("another string with bells. eh? yeah! ..."));
    QVERIFY(pending);

    ProStringList sl1;
    sl1 << ProString("") << ProString("foo") << ProString("barbaz");
    ProString s4a("hallo");
    s4a.append(sl1);
    QCOMPARE(s4a.toQString(), QStringLiteral("hallo foo barbaz"));
    ProString s4b("hallo");
    pending = false;
    s4b.append(sl1, &pending);
    QCOMPARE(s4b.toQString(), QStringLiteral("hallo  foo barbaz"));
    ProString s4c;
    pending = false;
    s4c.append(sl1, &pending);
    QCOMPARE(s4c.toQString(), QStringLiteral(" foo barbaz"));
    // bizarreness
    ProString s4d("hallo");
    pending = false;
    s4d.append(sl1, &pending, true);
    QCOMPARE(s4d.toQString(), QStringLiteral("hallo foo barbaz"));
    ProString s4e;
    pending = false;
    s4e.append(sl1, &pending, true);
    QCOMPARE(s4e.toQString(), QStringLiteral("foo barbaz"));

    ProStringList sl2;
    sl2 << ProString("foo");
    ProString s5;
    s5.append(sl2);
    QCOMPARE(s5.toQString(), QStringLiteral("foo"));
    QVERIFY(s5.toQStringRef().string()->isSharedWith(*sl2.first().toQStringRef().string()));

    QCOMPARE(ProString("one") + ProString(" more"), QStringLiteral("one more"));
}

void tst_qmakelib::proStringList()
{
    ProStringList sl1;
    sl1 << ProString("qt") << ProString(QLatin1String("is"))
        << ProString(QStringLiteral("uncool")).mid(2);

    QCOMPARE(sl1.toQStringList(), QStringList() << "qt" << "is" << "cool");
    QCOMPARE(sl1.join(QStringLiteral("~~")), QStringLiteral("qt~~is~~cool"));

    ProStringList sl2;
    sl2 << ProString("mostly") << ProString("...") << ProString("is") << ProString("...");
    sl1.insertUnique(sl2);
    QCOMPARE(sl1.toQStringList(), QStringList() << "qt" << "is" << "cool" << "mostly" << "...");

    QVERIFY(sl1.contains("cool"));
    QVERIFY(!sl1.contains("COOL"));
    QVERIFY(sl1.contains("COOL", Qt::CaseInsensitive));
}

void tst_qmakelib::quoteArgUnix_data()
{
    QTest::addColumn<QString>("in");
    QTest::addColumn<QString>("out");

    static const struct {
        const char * const in;
        const char * const out;
    } vals[] = {
        { "", "''" },
        { "hallo", "hallo" },
        { "hallo du", "'hallo du'" },
        { "ha'llo", "'ha'\\''llo'" },
    };

    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)
        QTest::newRow(vals[i].in) << QString::fromLatin1(vals[i].in)
                                  << QString::fromLatin1(vals[i].out);
}

void tst_qmakelib::quoteArgUnix()
{
    QFETCH(QString, in);
    QFETCH(QString, out);

    QCOMPARE(IoUtils::shellQuoteUnix(in), out);
}

void tst_qmakelib::quoteArgWin_data()
{
    QTest::addColumn<QString>("in");
    QTest::addColumn<QString>("out");

    static const struct {
        const char * const in;
        const char * const out;
    } vals[] = {
        { "", "\"\"" },
        { "hallo", "hallo" },
        { "hallo du", "\"hallo du\"" },
        { "hallo\\", "hallo\\" },
        { "hallo du\\", "\"hallo du\\\\\"" },
        { "ha\"llo", "\"ha\\\"llo^\"" },
        { "ha\\\"llo", "\"ha\\\\\\\"llo^\"" },
    };

    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)
        QTest::newRow(vals[i].in) << QString::fromLatin1(vals[i].in)
                                  << QString::fromLatin1(vals[i].out);
}

void tst_qmakelib::quoteArgWin()
{
    QFETCH(QString, in);
    QFETCH(QString, out);

    QCOMPARE(IoUtils::shellQuoteWin(in), out);
}

void tst_qmakelib::pathUtils()
{
    QString afp = QCoreApplication::applicationFilePath();
    QVERIFY(IoUtils::exists(afp));
    QVERIFY(!IoUtils::exists(afp + "-tehfail"));
    QCOMPARE(IoUtils::fileType(afp), IoUtils::FileIsRegular);
    QString adp = QCoreApplication::applicationDirPath();
    QCOMPARE(IoUtils::fileType(adp), IoUtils::FileIsDir);

    QString fn0 = "file/path";
    QVERIFY(IoUtils::isRelativePath(fn0));

    QString fn1 = "/a/unix/file/path";
    QCOMPARE(IoUtils::pathName(fn1).toString(), QStringLiteral("/a/unix/file/"));
    QCOMPARE(IoUtils::fileName(fn1).toString(), QStringLiteral("path"));
}

void tst_qmakelib::ioUtilRelativity_data()
{
    QTest::addColumn<QString>("path");
    QTest::addColumn<bool>("relative");

    static const struct {
        const char *name;
        const char *path;
        bool relative;
    } rows[] = {
        { "resource", ":/resource",
#ifdef QMAKE_BUILTIN_PRFS
          false
#else
          true
#endif
        },
#ifdef Q_OS_WIN // all the complications:
        // (except UNC: unsupported)
        { "drive-abs", "c:/path/to/file", false },
        { "drive-abs-bs", "c:\\path\\to\\file", false },
        { "drive-path", "c:path/to/file.txt", true },
        { "drive-path-bs", "c:path\\to\\file.txt", true },
        { "rooted", "/Users/qt/bin/true", true },
        { "rooted-bs", "\\Users\\qt\\bin\\true", true },
        { "drive-rel", "c:file.txt", true },
        { "subdir-bs", "path\\to\\file", true },
#else
        { "rooted", "/usr/bin/false", false },
#endif // Q_OS_WIN
        { "subdir", "path/to/file", true },
        { "simple", "file.name", true },
        { "empty", "", true }
    };

    for (unsigned int i = sizeof(rows) / sizeof(rows[0]); i-- > 0; )
        QTest::newRow(rows[i].name) << QString::fromLatin1(rows[i].path)
                                    << rows[i].relative;
}

void tst_qmakelib::ioUtilRelativity()
{
    QFETCH(QString, path);
    QFETCH(bool, relative);

    QCOMPARE(IoUtils::isRelativePath(path), relative);
}

void tst_qmakelib::ioUtilResolve_data()
{
    QTest::addColumn<QString>("base");
    QTest::addColumn<QString>("path");
    QTest::addColumn<QString>("expect");

    static const struct {
        const char *name;
        const char *base;
        const char *path;
        const char *expect;
    } data[] = {
#ifdef Q_OS_WIN // all the complications:
        { "drive-drive", "a:/ms/dir", "z:/root/file", "z:/root/file" },
        { "drive-drive-bs", "a:\\ms\\dir", "z:\\root\\file", "z:/root/file" },
        { "drive-root", "a:/ms/dir", "/root/file", "a:/root/file" },
        { "drive-root-bs", "a:\\ms\\dir", "\\root\\file", "a:/root/file" },
        { "drive-sub", "a:/ms/dir", "sub/file", "a:/ms/dir/sub/file" },
        { "drive-sub-bs", "a:\\ms\\dir", "sub\\file", "a:/ms/dir/sub/file" },
        { "drive-rel", "a:/ms/dir", "file.txt", "a:/ms/dir/file.txt" },
        { "drive-rel-bs", "a:\\ms\\dir", "file.txt", "a:/ms/dir/file.txt" },
#else
        { "abs-abs", "/a/unix/dir", "/root/file", "/root/file" },
        { "abs-sub", "/a/unix/dir", "sub/file", "/a/unix/dir/sub/file" },
        { "abs-rel", "/a/unix/dir", "file.txt", "/a/unix/dir/file.txt" },
#endif // Q_OS_WIN
    };

    for (unsigned i = sizeof(data) / sizeof(data[0]); i-- > 0; )
        QTest::newRow(data[i].name) << QString::fromLatin1(data[i].base)
                                    << QString::fromLatin1(data[i].path)
                                    << QString::fromLatin1(data[i].expect);
}

void tst_qmakelib::ioUtilResolve()
{
    QFETCH(QString, base);
    QFETCH(QString, path);
    QFETCH(QString, expect);

    QCOMPARE(IoUtils::resolvePath(base, path), expect);
}

void QMakeTestHandler::print(const QString &fileName, int lineNo, int type, const QString &msg)
{
    QString pfx = ((type & QMakeParserHandler::CategoryMask) == QMakeParserHandler::WarningMessage)
                  ? QString::fromLatin1("WARNING: ") : QString();
    if (lineNo)
        doPrint(QStringLiteral("%1%2:%3: %4").arg(pfx, fileName, QString::number(lineNo), msg));
    else
        doPrint(QStringLiteral("%1%2").arg(pfx, msg));
}

void QMakeTestHandler::doPrint(const QString &msg)
{
    if (!expected.isEmpty() && expected.first() == msg) {
        expected.removeAt(0);
    } else {
        qWarning("%s", qPrintable(msg));
        printed = true;
    }
}

QTEST_MAIN(tst_qmakelib)
