/*
  This file is part of the KTextTemplate library

  SPDX-FileCopyrightText: 2009, 2010 Stephen Kelly <steveire@gmail.com>

  SPDX-License-Identifier: LGPL-2.1-or-later

*/

#ifndef LOADERTAGSTEST_H
#define LOADERTAGSTEST_H

#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QTest>

#include "context.h"
#include "engine.h"
#include "ktexttemplate_paths.h"
#include "template.h"

using Dict = QHash<QString, QVariant>;

Q_DECLARE_METATYPE(KTextTemplate::Error)

using namespace KTextTemplate;

class TestLoaderTags : public QObject
{
    Q_OBJECT

private Q_SLOTS:
    void initTestCase();
    void cleanupTestCase();

    void testTemplateFromQrc();

    void testIncludeTag_data();
    void testIncludeTag()
    {
        doTest();
    }

    void testExtendsTag_data();
    void testExtendsTag()
    {
        doTest();
    }

    void testIncludeAndExtendsTag_data();
    void testIncludeAndExtendsTag()
    {
        doTest();
    }

    void testBlockTagErrors_data();
    void testBlockTagErrors()
    {
        doTest();
    }

private:
    void doTest();

    QSharedPointer<InMemoryTemplateLoader> m_loader;
    Engine *m_engine;
};

void TestLoaderTags::initTestCase()
{
    Q_INIT_RESOURCE(testresource);

    m_engine = new Engine(this);

    m_loader = QSharedPointer<InMemoryTemplateLoader>(new InMemoryTemplateLoader());
    m_engine->addTemplateLoader(m_loader);

    auto appDirPath = QFileInfo(QCoreApplication::applicationDirPath()).absoluteDir().path();
    m_engine->setPluginPaths({
        QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH),
        QStringLiteral(":/plugins/") // For testtags.qs
    });
#ifdef HAVE_QTQML_LIB
    m_engine->addDefaultLibrary(QStringLiteral("ktexttemplate_scriptabletags"));
#endif
}

void TestLoaderTags::cleanupTestCase()
{
    delete m_engine;
}

void TestLoaderTags::testTemplateFromQrc()
{
    Engine engine;

    auto loader = QSharedPointer<KTextTemplate::FileSystemTemplateLoader>::create();
    loader->setTemplateDirs({QStringLiteral(":/templates/")});
    engine.addTemplateLoader(loader);
    engine.setPluginPaths({QStringLiteral(KTEXTTEMPLATE_PLUGIN_PATH)});

    auto t = engine.loadByName(QStringLiteral("resourcetemplate1.html"));

    if (t->error() != NoError) {
        qDebug() << t->errorString();
        QCOMPARE(t->error(), NoError);
        return;
    }

    Context context;
    context.insert(QStringLiteral("numbertwo"), QStringLiteral("two"));
    context.insert(QStringLiteral("numberfour"), QStringLiteral("four"));

    auto result = t->render(&context);

    if (t->error() != NoError) {
        qDebug() << t->errorString();
        QCOMPARE(t->error(), NoError);
        return;
    }

    QCOMPARE(result, QStringLiteral("one-two-three-four\n\n"));
}

void TestLoaderTags::doTest()
{
    QFETCH(QString, input);
    QFETCH(Dict, dict);
    QFETCH(QString, output);
    QFETCH(KTextTemplate::Error, error);

    auto t = m_engine->newTemplate(input, QLatin1String(QTest::currentDataTag()));

    if (t->error() != NoError) {
        if (t->error() != error)
            qDebug() << t->errorString();
        QCOMPARE(t->error(), error);
        return;
    }

    Context context(dict);

    auto result = t->render(&context);

    if (t->error() != NoError) {
        if (t->error() != error)
            qDebug() << t->errorString();
        QCOMPARE(t->error(), error);
        return;
    }

    // Didn't catch any errors, so make sure I didn't expect any.
    QCOMPARE(NoError, error);

    QCOMPARE(t->error(), NoError);

    QCOMPARE(result, output);
}

void TestLoaderTags::testIncludeTag_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    m_loader->setTemplate(QStringLiteral("basic-syntax01"), QStringLiteral("Something cool"));
    m_loader->setTemplate(QStringLiteral("basic-syntax02"), QStringLiteral("{{ headline }}"));

    QTest::newRow("include01") << "{% include \"basic-syntax01\" %}" << dict << QStringLiteral("Something cool") << NoError;

    dict.insert(QStringLiteral("headline"), QStringLiteral("Included"));

    QTest::newRow("include02") << "{% include \"basic-syntax02\" %}" << dict << QStringLiteral("Included") << NoError;

    dict.insert(QStringLiteral("templateName"), QStringLiteral("basic-syntax02"));

    QTest::newRow("include03") << QStringLiteral("{% include templateName %}") << dict << QStringLiteral("Included") << NoError;

    dict.clear();
    QTest::newRow("include04") << "a{% include \"nonexistent\" %}b" << dict << QStringLiteral("ab") << TagSyntaxError;

    auto incl05 = QStringLiteral("template with a space");
    m_loader->setTemplate(QStringLiteral("include 05"), incl05);

    QTest::newRow("include 05") << incl05 << dict << QStringLiteral("template with a space") << NoError;

    QTest::newRow("include06") << "{% include \"include 05\" %}" << dict << QStringLiteral("template with a space") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("list"), QVariantList{QVariant(), QVariant()});
    QTest::newRow("include07") << "{% for i in list %}{% include \"include 05\" %}{% endfor %}" << dict
                               << QStringLiteral("template with a spacetemplate with a space") << NoError;
}

void TestLoaderTags::testExtendsTag_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;
    // Basic test
    QTest::newRow("namedendblocks01") << QStringLiteral(
        "1{% block first %}_{% block second %}2{% endblock "
        "second %}_{% endblock first %}3")
                                      << dict << QStringLiteral("1_2_3") << NoError;
    // Unbalanced blocks
    QTest::newRow("namedendblocks02") << QStringLiteral(
        "1{% block first %}_{% block second %}2{% endblock "
        "first %}_{% endblock second %}3")
                                      << dict << QString() << InvalidBlockTagError;
    QTest::newRow("namedendblocks03") << QStringLiteral(
        "1{% block first %}_{% block second %}2{% endblock "
        "%}_{% endblock second %}3") << dict
                                      << QString() << InvalidBlockTagError;
    QTest::newRow("namedendblocks04") << QStringLiteral(
        "1{% block first %}_{% block second %}2{% endblock "
        "second %}_{% endblock third %}3")
                                      << dict << QString() << InvalidBlockTagError;
    QTest::newRow("namedendblocks05") << QStringLiteral("1{% block first %}_{% block second %}2{% endblock first %}") << dict << QString()
                                      << InvalidBlockTagError;
    // Mixed named and unnamed endblocks
    QTest::newRow("namedendblocks06") << QStringLiteral(
        "1{% block first %}_{% block second %}2{% endblock "
        "%}_{% endblock first %}3") << dict
                                      << QStringLiteral("1_2_3") << NoError;
    QTest::newRow("namedendblocks07") << QStringLiteral(
        "1{% block first %}_{% block second %}2{% endblock "
        "second %}_{% endblock %}3") << dict
                                      << QStringLiteral("1_2_3") << NoError;

    // ## INHERITANCE ###########################################################
    //  Standard template with no inheritance

    auto inh1 = QStringLiteral("1{% block first %}&{% endblock %}3{% block second %}_{% endblock %}");
    m_loader->setTemplate(QStringLiteral("inheritance01"), inh1);

    QTest::newRow("inheritance01") << inh1 << dict << QStringLiteral("1&3_") << NoError;

    auto inh2 = QStringLiteral(
        "{% extends \"inheritance01\" %}{% block first %}2{% "
        "endblock %}{% block second %}4{% endblock %}");
    m_loader->setTemplate(QStringLiteral("inheritance02"), inh2);

    // Standard two-level inheritance
    QTest::newRow("inheritance02") << inh2 << dict << QStringLiteral("1234") << NoError;
    // Three-level with no redefinitions on third level
    QTest::newRow("inheritance03") << QStringLiteral("{% extends 'inheritance02' %}") << dict << QStringLiteral("1234") << NoError;
    // Two-level with no redefinitions on second level

    auto inh4 = QStringLiteral("{% extends \"inheritance01\" %}");
    m_loader->setTemplate(QStringLiteral("inheritance04"), inh4);

    QTest::newRow("inheritance04") << inh4 << dict << QStringLiteral("1&3_") << NoError;
    // Two-level with double quotes instead of single quotes
    QTest::newRow("inheritance05") << "{% extends \"inheritance02\" %}" << dict << QStringLiteral("1234") << NoError;

    dict.insert(QStringLiteral("foo"), QStringLiteral("inheritance02"));
    // Three-level with variable parent-template name
    QTest::newRow("inheritance06") << QStringLiteral("{% extends foo %}") << dict << QStringLiteral("1234") << NoError;

    auto inh7 = QStringLiteral("{% extends 'inheritance01' %}{% block second %}5{% endblock %}");
    m_loader->setTemplate(QStringLiteral("inheritance07"), inh7);

    dict.clear();
    // Two-level with one block defined, one block not defined
    QTest::newRow("inheritance07") << inh7 << dict << QStringLiteral("1&35") << NoError;
    // Three-level with one block defined on this level, two blocks defined next
    // level
    QTest::newRow("inheritance08") << QStringLiteral("{% extends 'inheritance02' %}{% block second %}5{% endblock %}") << dict << QStringLiteral("1235")
                                   << NoError;

    // Three-level with second and third levels blank
    QTest::newRow("inheritance09") << QStringLiteral("{% extends 'inheritance04' %}") << dict << QStringLiteral("1&3_") << NoError;

    dict.clear();

    // Three-level with space NOT in a block -- should be ignored
    QTest::newRow("inheritance10") << QStringLiteral("{% extends 'inheritance04' %}      ") << dict << QStringLiteral("1&3_") << NoError;
    // Three-level with both blocks defined on this level, but none on second
    // level
    QTest::newRow("inheritance11") << QStringLiteral(
        "{% extends 'inheritance04' %}{% block first %}2{% "
        "endblock %}{% block second %}4{% endblock %}")
                                   << dict << QStringLiteral("1234") << NoError;
    // Three-level with this level providing one and second level providing the
    // other
    QTest::newRow("inheritance12") << QStringLiteral("{% extends 'inheritance07' %}{% block first %}2{% endblock %}") << dict << QStringLiteral("1235")
                                   << NoError;
    // Three-level with this level overriding second level
    QTest::newRow("inheritance13") << QStringLiteral(
        "{% extends 'inheritance02' %}{% block first %}a{% "
        "endblock %}{% block second %}b{% endblock %}")
                                   << dict << QStringLiteral("1a3b") << NoError;
    // A block defined only in a child template shouldn't be displayed
    QTest::newRow("inheritance14") << QStringLiteral(
        "{% extends 'inheritance01' %}{% block newblock %}NO "
        "DISPLAY{% endblock %}") << dict
                                   << QStringLiteral("1&3_") << NoError;

    auto inh15 = QStringLiteral(
        "{% extends 'inheritance01' %}{% block first %}2{% "
        "block inner %}inner{% endblock %}{% endblock %}");
    m_loader->setTemplate(QStringLiteral("inheritance15"), inh15);

    // A block within another block
    QTest::newRow("inheritance15") << inh15 << dict << QStringLiteral("12inner3_") << NoError;
    // A block within another block (level 2)

    QTest::newRow("inheritance16") << QStringLiteral("{% extends 'inheritance15' %}{% block inner %}out{% endblock %}") << dict << QStringLiteral("12out3_")
                                   << NoError;

#ifdef HAVE_QTQML_LIB
    // {% load %} tag (parent -- setup for exception04)
    auto inh17 = QStringLiteral("{% load testtags %}{% block first %}1234{% endblock %}");
    m_loader->setTemplate(QStringLiteral("inheritance17"), inh17);

    dict.clear();
    QTest::newRow("inheritance17") << inh17 << dict << QStringLiteral("1234") << NoError;

    // {% load %} tag (standard usage, without inheritance)
    QTest::newRow("inheritance18") << QStringLiteral("{% load testtags %}{% echo this that theother %}5678") << dict << QStringLiteral("this that theother5678")
                                   << NoError;

    // {% load %} tag (within a child template)
    QTest::newRow("inheritance19") << QStringLiteral(
        "{% extends 'inheritance01' %}{% block first %}{% load "
        "testtags %}{% echo 400 %}5678{% endblock %}")
                                   << dict << QStringLiteral("140056783_") << NoError;
#endif

    auto inh20 = QStringLiteral(
        "{% extends 'inheritance01' %}{% block first "
        "%}{{ block.super }}a{% endblock %}");
    m_loader->setTemplate(QStringLiteral("inheritance20"), inh20);

    // Two-level inheritance with {{ block.super }}
    QTest::newRow("inheritance20") << inh20 << dict << QStringLiteral("1&a3_") << NoError;
    // Three-level inheritance with {{ block.super }} from parent
    QTest::newRow("inheritance21") << QStringLiteral(
        "{% extends 'inheritance02' %}{% block first %}{{ "
        "block.super }}a{% endblock %}")
                                   << dict << QStringLiteral("12a34") << NoError;
    // Three-level inheritance with {{ block.super }} from grandparent
    QTest::newRow("inheritance22") << QStringLiteral(
        "{% extends 'inheritance04' %}{% block first %}{{ "
        "block.super }}a{% endblock %}")
                                   << dict << QStringLiteral("1&a3_") << NoError;
    // Three-level inheritance with {{ block.super }} from parent and
    // grandparent
    QTest::newRow("inheritance23") << QStringLiteral(
        "{% extends 'inheritance20' %}{% block first %}{{ "
        "block.super }}b{% endblock %}")
                                   << dict << QStringLiteral("1&ab3_") << NoError;

    // Inheritance from local context without use of template loader

    auto t = m_engine->newTemplate(QStringLiteral("1{% block first %}_{% endblock %}3{% block second %}_{% "
                                                  "endblock %}"),
                                   QStringLiteral("context_template"));
    dict.insert(QStringLiteral("context_template"), QVariant::fromValue(t));

    QTest::newRow("inheritance24") << QStringLiteral(
        "{% extends context_template %}{% block first %}2{% "
        "endblock %}{% block second %}4{% endblock %}")
                                   << dict << QStringLiteral("1234") << NoError;

    dict.clear();

    auto t1 = m_engine->newTemplate(QStringLiteral("Wrong"), QStringLiteral("context_template_1"));
    auto t2 = m_engine->newTemplate(QStringLiteral("1{% block first %}_{% endblock %}3{% block second %}_{% "
                                                   "endblock %}"),
                                    QStringLiteral("context_template_2"));
    QVariantList list{QVariant::fromValue(t1), QVariant::fromValue(t2)};

    dict.insert(QStringLiteral("context_template"), list);

    // Inheritance from local context with variable parent template
    QTest::newRow("inheritance25") << QStringLiteral(
        "{% extends context_template.1 %}{% block first %}2{% "
        "endblock %}{% block second %}4{% endblock %}")
                                   << dict << QStringLiteral("1234") << NoError;

    dict.clear();

    // Set up a base template to extend
    auto inh26 = QStringLiteral("no tags");
    m_loader->setTemplate(QStringLiteral("inheritance26"), inh26);

    // Inheritance from a template that doesn't have any blocks
    QTest::newRow("inheritance27") << QStringLiteral("{% extends 'inheritance26' %}") << dict << QStringLiteral("no tags") << NoError;

    auto inh28 = QStringLiteral("{% block first %}!{% endblock %}");
    m_loader->setTemplate(QStringLiteral("inheritance 28"), inh28);

    QTest::newRow("inheritance 28") << inh28 << dict << QStringLiteral("!") << NoError;

    // Inheritance from a template with a space in its name should work.
    QTest::newRow("inheritance29") << QStringLiteral("{% extends 'inheritance 28' %}") << dict << QStringLiteral("!") << NoError;

    dict.insert(QStringLiteral("optional"), QStringLiteral("True"));

    auto inh30 = QStringLiteral("1{% if optional %}{% block opt %}2{% endblock %}{% endif %}3");
    m_loader->setTemplate(QStringLiteral("inheritance30"), inh30);

    QTest::newRow("inheritance30") << inh30 << dict << QStringLiteral("123") << NoError;
    QTest::newRow("inheritance31") << QStringLiteral("{% extends 'inheritance30' %}{% block opt %}two{% endblock %}") << dict << QStringLiteral("1two3")
                                   << NoError;
    dict.clear();
    QTest::newRow("inheritance32") << QStringLiteral("{% extends 'inheritance30' %}{% block opt %}two{% endblock %}") << dict << QStringLiteral("13")
                                   << NoError;

    dict.insert(QStringLiteral("optional"), 1);
    auto inh33 = QStringLiteral(
        "1{% ifequal optional 1 %}{% block opt %}2{% "
        "endblock %}{% endifequal %}3");
    m_loader->setTemplate(QStringLiteral("inheritance33"), inh33);

    QTest::newRow("inheritance33") << inh33 << dict << QStringLiteral("123") << NoError;

    QTest::newRow("inheritance34") << QStringLiteral("{% extends 'inheritance33' %}{% block opt %}two{% endblock %}") << dict << QStringLiteral("1two3")
                                   << NoError;
    dict.clear();
    QTest::newRow("inheritance35") << QStringLiteral("{% extends 'inheritance33' %}{% block opt %}two{% endblock %}") << dict << QStringLiteral("13")
                                   << NoError;

    dict.clear();
    dict.insert(QStringLiteral("numbers"), QVariantList{1, 2, 3});

    auto inh36 = QStringLiteral(
        "{% for n in numbers %}_{% block opt %}{{ n }}{% "
        "endblock %}{% endfor %}_");
    m_loader->setTemplate(QStringLiteral("inheritance36"), inh36);

    QTest::newRow("inheritance36") << inh36 << dict << QStringLiteral("_1_2_3_") << NoError;
    QTest::newRow("inheritance37") << QStringLiteral("{% extends 'inheritance36' %}{% block opt %}X{% endblock %}") << dict << QStringLiteral("_X_X_X_")
                                   << NoError;
    dict.clear();
    QTest::newRow("inheritance38") << QStringLiteral("{% extends 'inheritance36' %}{% block opt %}X{% endblock %}") << dict << QStringLiteral("_") << NoError;

    dict.insert(QStringLiteral("optional"), QStringLiteral("True"));

    QTest::newRow("inheritance39") << QStringLiteral(
        "{% extends 'inheritance30' %}{% block opt %}new{{ "
        "block.super }}{% endblock %}")
                                   << dict << QStringLiteral("1new23") << NoError;

    dict.insert(QStringLiteral("optional"), 1);

    QTest::newRow("inheritance40") << QStringLiteral(
        "{% extends 'inheritance33' %}{% block opt %}new{{ "
        "block.super }}{% endblock %}")
                                   << dict << QStringLiteral("1new23") << NoError;

    dict.clear();
    dict.insert(QStringLiteral("numbers"), QVariantList{1, 2, 3});

    QTest::newRow("inheritance41") << QStringLiteral(
        "{% extends 'inheritance36' %}{% block opt %}new{{ "
        "block.super }}{% endblock %}")
                                   << dict << QStringLiteral("_new1_new2_new3_") << NoError;

    QTest::newRow("inheritance42") << QStringLiteral("{% extends 'inheritance02'|cut:' ' %}") << dict << QStringLiteral("1234") << NoError;

    dict.clear();
    // Raise exception for invalid template name
    QTest::newRow("exception01") << QStringLiteral("{% extends 'nonexistent' %}") << dict << QString() << TagSyntaxError;
    // Raise exception for invalid template name (in variable)
    QTest::newRow("exception02") << QStringLiteral("{% extends nonexistent %}") << dict << QString() << TagSyntaxError;
    // Raise exception for extra {% extends %} tags
    QTest::newRow("exception03") << QStringLiteral(
        "{% extends 'inheritance01' %}{% block first %}2{% "
        "endblock %}{% extends 'inheritance16' %}")
                                 << dict << QString() << TagSyntaxError;
    // Raise exception for custom tags used in child with {% load %} tag in
    // parent, not in child
    QTest::newRow("exception04") << QStringLiteral(
        "{% extends 'inheritance17' %}{% block first %}{% echo "
        "400 %}5678{% endblock %}")
                                 << dict << QString() << InvalidBlockTagError;
}

void TestLoaderTags::testBlockTagErrors_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    QTest::newRow("block-error-01") << QStringLiteral("{% block repeat %}{% endblock %}{% block repeat %}{% endblock %}") << dict << QString()
                                    << TagSyntaxError;
    QTest::newRow("block-error-02") << QStringLiteral("{% block %}{% endblock %}") << dict << QString() << TagSyntaxError;
    QTest::newRow("block-error-03") << QStringLiteral("{% block foo bar %}{% endblock %}") << dict << QString() << TagSyntaxError;
}

void TestLoaderTags::testIncludeAndExtendsTag_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<Dict>("dict");
    QTest::addColumn<QString>("output");
    QTest::addColumn<KTextTemplate::Error>("error");

    Dict dict;

    m_loader->setTemplate(QStringLiteral("ext_base"), QStringLiteral("{% block block1 %}block1{% endblock %}"));

    m_loader->setTemplate(QStringLiteral("extender"),
                          QStringLiteral("{% extends 'ext_base' %}{% block block1 "
                                         "%}block1override{% endblock %}"));

    QTest::newRow("include-extender-twice") << QStringLiteral(R"django(
{% include "extender" %}
{% include "extender" %}
)django") << dict << QStringLiteral("\nblock1override\nblock1override\n")
                                            << NoError;

    m_loader->setTemplate(QStringLiteral("anotherextender"),
                          QStringLiteral("{% extends 'extender' %}{% block block1 "
                                         "%}block1overrideagain{% endblock %}"));

    QTest::newRow("include-deeper-extender-twice") << QStringLiteral(R"django(
{% include "anotherextender" %}
{% include "anotherextender" %}
)django") << dict << QStringLiteral("\nblock1overrideagain\nblock1overrideagain\n")
                                                   << NoError;
}

QTEST_MAIN(TestLoaderTags)
#include "testloadertags.moc"

#endif
