// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#ifndef TST_QMLDOMPATH_H
#define TST_QMLDOMPATH_H
#include <QtQmlDom/private/qqmldompath_p.h>
#include <QtQmlDom/private/qqmldomitem_p.h>

#include <QtTest/QTest>

QT_BEGIN_NAMESPACE
namespace QQmlJS {
namespace Dom {
namespace PathEls {

class TestPaths: public QObject {
    Q_OBJECT
public:
    void testPathInternals(const Path &p1)
    {
        QCOMPARE(p1.component(0).kind(), Kind::Root);
        QCOMPARE(p1.component(1).kind(), Kind::Current);

        Path p11 = Path::fromField(u"test");
        QString s = QLatin1String("test");
        Path p2 = Path::fromField(s);
        Path p3 = Path::fromField(QLatin1String("test"));
        QCOMPARE(p11, p2);
        QCOMPARE(p11, p3);
        QVERIFY(p11.m_data->strData.isEmpty());
        QCOMPARE(p2.m_data->strData.size(), 1);
        QCOMPARE(p2.m_data->strData.first(), s);
        QCOMPARE(p3.m_data->strData.size(), 1);
        QCOMPARE(p3.m_data->strData.first(), s);
    }

private slots:
    void pathComponentTestInternalAlloc() {
        PathComponent c;
        QCOMPARE(c.kind(), Kind::Empty);
        PathComponent c1{Current()};
        QCOMPARE(c1.kind(), Kind::Current);
        QVERIFY(c!=c1);
        QVERIFY(c<c1);
        QVERIFY(c1>c);
        PathComponent c1_1{Current(PathCurrent::Ids)};
        QCOMPARE(c1_1.kind(), Kind::Current);
        QVERIFY(c1 != c1_1);
        QVERIFY(c < c1_1);
        QCOMPARE(c1_1.name(), QLatin1String("@ids"));
        PathComponent c1_2{Current(u"ids")};
        QCOMPARE(c1_1, c1_2);
        PathComponent c2 = c1;
        QCOMPARE(c2.kind(), Kind::Current);
        QCOMPARE(c2, c1);
        PathComponent c3;
        QCOMPARE(c, c3);
        QCOMPARE(c3.kind(), Kind::Empty);
        c3 = c1;
        QCOMPARE(c3.kind(), Kind::Current);
        QCOMPARE(c3, c1);
        PathComponent c4{Field(u"bla")};
        QCOMPARE(c4.kind(), Kind::Field);
        QCOMPARE(c4.name(), QLatin1String("bla"));
        auto c5=PathComponent(Index(42));
        QCOMPARE(c5.kind(), Kind::Index);
        QCOMPARE(c5.index(), 42);
        auto c6 = PathComponent(Key(QStringLiteral(u"bla")));
        QCOMPARE(c6.kind(), Kind::Key);
        QCOMPARE(c6.name(), QLatin1String("bla"));
        auto c7 = PathComponent(Key(QStringLiteral(u" ugly\n \t \\string\"'bla")));
        QCOMPARE(c7.kind(), Kind::Key);
        QCOMPARE(c7.name(), QLatin1String(" ugly\n \t \\string\"'bla"));
        auto c8=PathComponent(Root(u"pippo"));
        QCOMPARE(c8.kind(), Kind::Root);
        QCOMPARE(c8.name(), QLatin1String("$pippo"));
        auto c8_1=PathComponent(Root(PathRoot::Env));
        QCOMPARE(c8_1.kind(), Kind::Root);
        QCOMPARE(c8_1.name(), QLatin1String("$env"));
        auto c8_2=PathComponent(Root(u"env"));
        QCOMPARE(c8_1, c8_2);
        auto c9=PathComponent(Current(u"ippo"));
        QCOMPARE(c9.kind(), Kind::Current);
        QCOMPARE(c9.name(), QLatin1String("@ippo"));
        auto c10=PathComponent(Any());
        QCOMPARE(c10.kind(), Kind::Any);
        QVERIFY(c9!=c10);
        auto c11=PathComponent(Filter([](const DomItem &){ return true; }));
        auto c12=c11;
        auto c13=PathComponent(Filter([](const DomItem &){ return false; }));
        auto c14=PathComponent(Filter([](const DomItem &){ return false; }, u"skipAll"));
        auto c15=PathComponent(Filter([](const DomItem &){ return true; }, u"skipAll"));
        QCOMPARE(c11.kind(), Kind::Filter);
        QCOMPARE(c11, c11);
        QVERIFY(c11 != c12); // native code assumed to be non comparable and different even if they are the same
        QVERIFY(c11 != c13);
        QVERIFY(c13 != c14);
        QVERIFY(c11 != c14);
        QCOMPARE(c14, c14);
        QCOMPARE(c14, c15); // same description (without < at the beginning) assumes same function even if different
    }

    void testPaths() {
        Path p;
        QCOMPARE(p.length(), 0);
        QCOMPARE(p.length(), 0);
        Path p0 = Path::fromRoot();
        QCOMPARE(p0[0].headKind(), Kind::Root);
        QCOMPARE(p0.length(), 1);
        Path p1 = p0.withCurrent();
        QCOMPARE(p1.length(), 2);
        testPathInternals(p1);
        QCOMPARE(p1[0].headKind(), Kind::Root);
        QCOMPARE(p1[1].headKind(), Kind::Current);
        auto p2 = p1.withField(u"aa");
        QCOMPARE(p2[2].headKind(), Kind::Field);
        auto p2b = p1.withComponent(PathEls::Field(u"aa"));
        QCOMPARE(p2b.length(), 3);
        QCOMPARE(p2b[2].headKind(), Kind::Field);
        QCOMPARE(p2b, p2);
        auto p3a = p2.withComponent(PathEls::Index(4));
        QCOMPARE(p3a[3].headKind(), Kind::Index);
        auto p3 = p2.withIndex(4);
        QCOMPARE(p3.length(), 4);
        QCOMPARE(p3[3].headKind(), Kind::Index);
        QCOMPARE(p3, p3a);
        auto p4 = p3.withKey(u"bla");
        QCOMPARE(p4[4].headKind(), Kind::Key);
        auto p5 = p4.withAny();
        QCOMPARE(p5[5].headKind(), Kind::Any);
        auto p6 = p5.withEmpty();
        QCOMPARE(p6[6].headKind(), Kind::Empty);
        auto rString = u"$.@.aa[4][\"bla\"][*].";
        QCOMPARE(p6.toString(), rString);
        auto p7 = p6.withFilter([](const DomItem &){ return true; }, u"true");
        auto p7Str = p7.toString();
        QCOMPARE(p7Str, u"$.@.aa[4][\"bla\"][*].[?(true)]");
        auto p8 = p7.dropTail();
        QCOMPARE(p8.length(), 7);
        QCOMPARE(p8.toString(), rString);
        QCOMPARE(p8, p6);
        auto p9 = Path::fromString(rString);
        QCOMPARE(p9.length(), 7);
        auto p9Str = p9.toString();
        QCOMPARE(p9Str, rString);
        QCOMPARE(p9, p6);
        auto p10 = p6.dropFront();
        QCOMPARE(p10.length(), 6);
        auto p10Str = p10.toString();
        auto r2Str = u"@.aa[4][\"bla\"][*].";
        QCOMPARE(p10Str, r2Str);
        auto p11 = Path::fromString(r2Str);
        auto p11Str = p11.toString();
        QCOMPARE(p11Str, r2Str);
        QCOMPARE(p10, p11);
        auto p12 = p7.mid(1,6);
        auto p12Str = p12.toString();
        QCOMPARE(p12Str, r2Str);
        QCOMPARE(p10, p12);
    }

    void testPathSplit()
    {
        const QList<Path> paths({Path(),
            Path::fromRoot(PathRoot::Env).withField(u"pippo").withKey(u"pluto").withIndex(4),
            Path::fromRoot(PathRoot::Env).withField(u"pippo").withKey(u"pluto"),
            Path::fromRoot(PathRoot::Env).withField(u"pippo"),
            Path::fromRoot(PathRoot::Env).withField(u"pippo").withField(u"pp"),
            Path::fromRoot(PathRoot::Env),
            Path::fromField(u"pippo").withIndex(4),
            Path::fromField(u"pippo").withKey(u"pluto").withIndex(4),
            Path::fromField(u"pippo").withKey(u"pluto"),
            Path::fromField(u"pippo"),
            Path::fromField(u"pippo").withField(u"pp"),
            Path::fromIndex(4),
            Path::fromKey(u"zz")
            });
        for (const Path &p : paths) {
            Source s = p.split();
            QCOMPARE(p, s.pathToSource.withPath(s.pathFromSource));
            if (!s.pathFromSource)
                QVERIFY(!s.pathToSource);
        }
        QCOMPARE(paths.at(1).split().pathToSource, Path::fromRoot(PathRoot::Env));
        QCOMPARE(paths.at(2).split().pathToSource, Path::fromRoot(PathRoot::Env));
        QCOMPARE(paths.at(3).split().pathToSource, Path::fromRoot(PathRoot::Env));
        QCOMPARE(paths.at(4).split().pathToSource, Path::fromRoot(PathRoot::Env).withField(u"pippo"));
        QVERIFY(!paths.at(5).split().pathToSource);
        QVERIFY(!paths.at(6).split().pathToSource);
        QVERIFY(!paths.at(7).split().pathToSource);
        QVERIFY(!paths.at(8).split().pathToSource);
        QVERIFY(!paths.at(9).split().pathToSource);
        QCOMPARE(paths.at(10).split().pathToSource, Path::fromField(u"pippo"));
        QVERIFY(!paths.at(11).split().pathToSource);
        QVERIFY(!paths.at(12).split().pathToSource);
    }

    void withPath_data()
    {
        QTest::addColumn<Path>("lhs");
        QTest::addColumn<Path>("rhs");
        QTest::addColumn<Path>("expected");

        const Path mainPath = Path::fromField("a").withField("b").withField("c").withField("d");
        const Path slicedMainPath = mainPath.mid(1, 2);
        const Path suffixPath = Path::fromField("append")
                                        .withField(QLatin1String("me"))
                                        .withField(QString(u"to"))
                                        .withField("path");
        const Path slicedSuffixPath = suffixPath.mid(1, 2);

        QTest::addRow("normal") << mainPath << suffixPath
                                << Path::fromField(u"a")
                                           .withField(u"b")
                                           .withField(u"c")
                                           .withField(u"d")
                                           .withField(u"append")
                                           .withField(u"me")
                                           .withField(u"to")
                                           .withField(u"path");

        QTest::addRow("appendToSliced") << slicedMainPath << suffixPath
                                        << Path::fromField(u"b")
                                                   .withField(u"c")
                                                   .withField(u"append")
                                                   .withField(u"me")
                                                   .withField(u"to")
                                                   .withField(u"path");

        QTest::addRow("appendSlicedToSliced")
                << slicedMainPath << slicedSuffixPath
                << Path::fromField(u"b").withField(u"c").withField(u"me").withField(u"to");

        QTest::addRow("appendTwice")
                << slicedMainPath << slicedSuffixPath.withPath(slicedSuffixPath)
                << Path::fromField(u"b")
                           .withField(u"c")
                           .withField(u"me")
                           .withField(u"to")
                           .withField(u"me")
                           .withField(u"to");
    }

    void withPath()
    {
        QFETCH(Path, lhs);
        QFETCH(Path, rhs);
        QFETCH(Path, expected);

        Path p = lhs.withPath(rhs);
        QCOMPARE(p, expected);

        // sanity check: make sure that the newly appended components do have QStringViews pointing
        // to the components own strData
        for (int i = 0, end = rhs.length(); i < end; ++i) {
            QCOMPARE(std::get<Field>(p.m_data->components[i].m_data).fieldName,
                     p.m_data->strData[end - 1 - i]);
            QCOMPARE(std::get<Field>(p.m_data->components[i].m_data).fieldName.constData(),
                     p.m_data->strData[end - 1 - i].constData());
        }
    }
};

} // namespace PathEls
} // namespace Dom
} // namespace QQmlJS
QT_END_NAMESPACE

#endif
