/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include <QtTest>

#include "TransformEngine.h"
#include "JsonUtils.h"

class TestTransformEngine : public QObject {
    Q_OBJECT

private slots:
    void testFunctions() {
        QDate currentDate = QDate::currentDate();
        QTime currentTime = QTime::currentTime();
        TransformEngine transformEngine(currentDate, currentTime);
        QString result;

        QString dateString = currentDate.toString("yyyy-MM-dd");
        QString timeString = currentTime.toString("HH:mm:ss");
        QString yearString = currentDate.toString("yyyy");
        QString monthString = currentDate.toString("M");
        QString dayString = currentDate.toString("d");

        result = transformEngine.transformToString("$date()$", JsonUtils::stringToJSON(R"json({ })json"));
        QCOMPARE(result, dateString);

        result = transformEngine.transformToString("$year()$", JsonUtils::stringToJSON(R"json({ })json"));
        QCOMPARE(result, yearString);

        result = transformEngine.transformToString("$month()$", JsonUtils::stringToJSON(R"json({ })json"));
        QCOMPARE(result, monthString);

        result = transformEngine.transformToString("$day()$", JsonUtils::stringToJSON(R"json({ })json"));
        QCOMPARE(result, dayString);

        result = transformEngine.transformToString("$time()$", JsonUtils::stringToJSON(R"json({ })json"));
        QCOMPARE(result, timeString);

        result = transformEngine.transformToString("@for@ $e$ in $array$ $index(e)$:$e$ @endfor@", JsonUtils::stringToJSON(R"json({ "array": [ "a", "b", "c", "d", "e" ] })json"));
        QCOMPARE(result, " 0:a 1:b 2:c 3:d 4:e");

        result = transformEngine.transformToString("$title(t)$", JsonUtils::stringToJSON(R"json({ "t" : "my title   ! (test)" })json"));
        QCOMPARE(result, "My Title ! (test)");

        result = transformEngine.transformToString("$title(class)$", JsonUtils::stringToJSON(R"json({ "class" : "my class   name! 123 (test)" })json"));
        QCOMPARE(result, "My Class Name! 123 (test)");

        result = transformEngine.transformToString("$upperSnakeCase(class)$", JsonUtils::stringToJSON(R"json({ "class" : "my class   name! 123 (test)" })json"));
        QCOMPARE(result, "MY_CLASS_NAME_123_TEST");

        result = transformEngine.transformToString("$lowerSnakeCase(class)$", JsonUtils::stringToJSON(R"json({ "class" : "my class   name! 123 (test)" })json"));
        QCOMPARE(result, "my_class_name_123_test");

        result = transformEngine.transformToString("$upperCamelCase(class)$", JsonUtils::stringToJSON(R"json({ "class" : "my class   name! 123 (test)" })json"));
        QCOMPARE(result, "MyClassName123Test");

        result = transformEngine.transformToString("$lowerCamelCase(class)$", JsonUtils::stringToJSON(R"json({ "class" : "my class   name! 123 (test)" })json"));
        QCOMPARE(result, "myClassName123Test");

        result = transformEngine.transformToString("$kebabCase(class)$", JsonUtils::stringToJSON(R"json({ "class" : "my class   name! 123 (test)" })json"));
        QCOMPARE(result, "my-class-name-123-test");

        result = transformEngine.transformToString("$joinKebabCase(class)$", JsonUtils::stringToJSON(R"json({ "class" : "my class   name! 123 (test)" })json"));
        QCOMPARE(result, "myclassname123test");
    }

    void testIfStatement() {
        TransformEngine transformEngine;
        QString result;
        QString templateStr;

        // if statement test evaluate value if possible otherwise evaluate presence
        templateStr = "@if@ $field1$ Field 1 is $field1$ @else@ no field1 or not true @endif@";
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1" : "true" })json"));
        QCOMPARE(result, "Field 1 is true ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1" : "false" })json"));
        QCOMPARE(result, "no field1 or not true ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1": "something" })json"));
        QCOMPARE(result, "Field 1 is something ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1": "" })json"));
        QCOMPARE(result, "no field1 or not true ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1" : true })json"));
        QCOMPARE(result, "Field 1 is true ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1" : false })json"));
        QCOMPARE(result, "no field1 or not true ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1" : 0.0 })json"));
        QCOMPARE(result, "no field1 or not true ");

        result = transformEngine.transformToString(
                     "@if@ $field2$ Field 2 is $field2$ @else@ no field2 or not true @endif@",
                     JsonUtils::stringToJSON(R"json({ "field1" : 0.0 })json"));
        QCOMPARE(result, "no field2 or not true ");

        templateStr = "@if@ contains($field1$) data contains field1 @else@ data does not contain field1 @endif@";
        result = transformEngine.transformToString(
                     templateStr,
                     JsonUtils::stringToJSON(R"json({ "field1": "" })json"));
        QCOMPARE(result, "data contains field1 ");

        result = transformEngine.transformToString(
                     templateStr,
                     JsonUtils::stringToJSON(R"json({ })json"));
        QCOMPARE(result, "data does not contain field1 ");

        templateStr = R"(@if@ contains($field1.field2$) data["field1"] contains field2 @else@ data["field1"] does not contain field2 @endif@)";
        result = transformEngine.transformToString(
                     templateStr,
                     JsonUtils::stringToJSON(R"json({ "field1": { "field2": false} })json"));
        QCOMPARE(result, "data[\"field1\"] contains field2 ");

        result = transformEngine.transformToString(
                     templateStr,
                     JsonUtils::stringToJSON(R"json({ "field1": { "field2": 0} })json"));
        QCOMPARE(result, "data[\"field1\"] contains field2 ");

        result = transformEngine.transformToString(
                     R"(@if@ $field1.field2$ data["field1"] contains field2 @else@ data["field1"] does not contain field2 @endif@)",
                     JsonUtils::stringToJSON(R"json({ "field1": { "field2": 0} })json"));
        QCOMPARE(result, "data[\"field1\"] does not contain field2 ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({"field1": { } })json"));
        QCOMPARE(result, "data[\"field1\"] does not contain field2 ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "field1": { "field2": 0} })json"));
        QCOMPARE(result, "data[\"field1\"] contains field2 ");

        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({"field1": { } })json"));
        QCOMPARE(result, "data[\"field1\"] does not contain field2 ");

        // inside loops
        // there is a difference between a test with and without contains
        result = transformEngine.transformToString(
                     R"(@for@ $i$ in $a$ @if@ contains($i.b$) $index(i)$ contains "b" ($i.b$), @else@ $index(i)$ does not contain "b", @endif@ @endfor@)",
                     JsonUtils::stringToJSON(R"json({ "a": [ { "b": 0 }, { "b": false }, {"b" : "val"}, {} ] })json"));
        QCOMPARE(result, " 0 contains \"b\" (0), 1 contains \"b\" (false), 2 contains \"b\" (val), 3 does not contain \"b\",");

        result = transformEngine.transformToString(
                     R"(@for@ $i$ in $a$ @if@ $i.b$ $index(i)$ has true "b" ($i.b$), @else@ $index(i)$ does not have true "b" ($i.b$), @endif@ @endfor@)",
                     JsonUtils::stringToJSON(R"json({ "a": [ { "b": 0 }, { "b": false }, {"b" : "val"}, {} ] })json"));
        QCOMPARE(result, " 0 does not have true \"b\" (0), 1 does not have true \"b\" (false), 2 has true \"b\" (val), 3 does not have true \"b\" (),");

        result = transformEngine.transformToString(
                     R"(@if@ equals($a$,"yes") yes @else@ no @endif@)",
                     JsonUtils::stringToJSON(R"json({ "a": "yes" })json"));
        QCOMPARE(result, "yes ");
        result = transformEngine.transformToString(
                     R"(@if@ equals($a$,"") yes @else@ no @endif@)",
                     JsonUtils::stringToJSON(R"json({ "a": "yes" })json"));
        QCOMPARE(result, "no ");
        result = transformEngine.transformToString(
                     R"(@if@ equals($a$,"x") yes @else@ no @endif@)",
                     JsonUtils::stringToJSON(R"json({ "a": "yes" })json"));
        QCOMPARE(result, "no ");
        result = transformEngine.transformToString(
                     R"(@if@ equals($a$,"yes") yes @else@ no @endif@)",
                     JsonUtils::stringToJSON(R"json({ "a": [ "yes" ] })json"));
        QCOMPARE(result, "no ");
        result = transformEngine.transformToString(
                     R"(@for@ $i$ in $a$ @if@ equals($i.b$,"foo") $index(i)$ has b==foo ($i.b$), @else@ $index(i)$ does not have b==foo, @endif@ @endfor@)",
                     JsonUtils::stringToJSON(R"json({ "a": [ { "b": "foo" }, { "b": "bar" }, {"b" : "foo"}, {} ] })json"));
        QCOMPARE(result, " 0 has b==foo (foo), 1 does not have b==foo, 2 has b==foo (foo), 3 does not have b==foo,");
    }

    void testNumericLoopVariable() {
        TransformEngine transformEngine;
        QString result;
        QString templateStr;

        QJsonObject t;
        t["double"] = 1.0;
        t["int"] = 1;
        QCOMPARE(t["double"].isDouble(), true);
        QCOMPARE(t["int"].isDouble(), true); // BEWARE in JSON there is no such things as double/int (an int value is also considered a double). It's called numeric

        templateStr = R"(value=$var$)";
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "var": "15" })json"));
        QCOMPARE(result, "value=15");
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "var": "-45.0001" })json"));
        QCOMPARE(result, "value=-45.0001");
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "var": -1 })json"));
        QCOMPARE(result, "value=-1");
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "var": 0.5 })json"));
        QCOMPARE(result, "value=0.5");
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "var": 45 })json"));
        QCOMPARE(result, "value=45");
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "var": 45.0 })json"));
        QCOMPARE(result, "value=45"); // BEWARE removed the decimals!
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({ "var": 12356.7890123456 })json"));
        QCOMPARE(result, "value=12356.7890123456");

        result = transformEngine.transformToString(R"(@for@ $p$ in $array$ value=$p$ @endfor@)", JsonUtils::stringToJSON(R"json({"array": [ "15", "-45.0001", -1, 0.5, 45, 12356.7890123456, 17.0 ]})json"));
        QCOMPARE(result, " value=15 value=-45.0001 value=-1 value=0.5 value=45 value=12356.7890123456 value=17");
    }

    void testLoopVariable() {
        TransformEngine transformEngine;
        QJsonObject data;
        QString result;
        QString templateStr;

        templateStr = R"(@for@ $e$ in $array$
        inside loop for $e.name$
        @endfor@)";
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({"array": [ { "name": "a" }, { "name": "b" }, { "name": "c" } ] })json"));
        QCOMPARE(result, "\n        inside loop for a\n        inside loop for b\n        inside loop for c");

        templateStr = R"(@for@ $e$ in $array$
        inside loop for $e.name$
        @if@ $e.sub$
            $upperCamelCase(e.name)$ / $lowerCamelCase(e.name)$ has "sub"            
        @endif@
        @if@ $e.sub$
            @subfor@ $f$ in $e.sub$
                item $f.test$ 
            @endsubfor@
        @endif@
        @endfor@)";
        result = transformEngine.transformToString(templateStr, JsonUtils::stringToJSON(R"json({"array":[{"name":"a"},{"name":"b 2","sub":[{"test":1},{"test":2},{"test":3}]},{"name":"c"}]})json"));
        QCOMPARE(result, "\n        inside loop for a\n        inside loop for b 2\n        B2 / b2 has \"sub\"\n        item 1\n                item 2\n                item 3\n        inside loop for c");
    }

    void testCppGeneration() {
        QDate currentDate = QDate::currentDate();
        QTime currentTime = QTime::currentTime();
        TransformEngine transformEngine(currentDate, currentTime);
        QJsonObject data;
        QString result;
        QString templateStr;

        templateStr = R"(
// code for $name$ (class $upperCamelCase(name)$) defined $upperSnakeCase(name)$
// generated on $date()$ $time()$
@for@ $param$ in $parameters$
   param = addParameter(new Property("$param.name$", $param.defaultValue$, tr("$param.description$"), "$param.unit$"));
   @if@ $param.readOnly$
       param->setReadOnly(true);
   @else@
       // param is not read only
   @endif@
   @subfor@ $ev$ in $param.enumValues$
       // value: $ev$
   @endsubfor@
@endfor@
@if@ $name$
    // given name: $name$
@else@
    // no given name
@endif@
@if@ $option$
    // option: $option$
@else@
    // no option
@endif@
@if@ $optionList$
    // optionList is given
    @for@ $o$ in $optionList$
    // option: $o$
    @endfor@
@else@
    // no optionlist
@endif@
)"; 
        QString dataString = R"json({
  "name": "test data",
  "parameters": [
        {
            "defaultValue": 100.0,
            "description": "Testing double",
            "name": "testDouble",
            "readOnly": false,
            "type": "double",
            "unit": ""
        },
        {
            "defaultValue": "QVector3D($year()$, $month()$, $day()$)",
              "description": "test 3D vector"
                      ,
              "name": "testVector3D"
                      ,
              "readOnly":
                      true,
              "type": "QVector3D"
                      ,
              "unit": "mm"
    }, {
        "defaultValue": 1,
"description": "test enum"
        ,
"name": "testenum"
        ,
"readOnly":
        false,
"type": "enum"
        ,
"enumValues":
        [ "V1", "V2", "V3"],
"unit": "mm"
    }
    ],
"optionList":
    [ "option1", "option2", false, -5.0, 0 ]
})json";
data = JsonUtils::stringToJSON(dataString.toUtf8());

//printJSON(data);
QString dateString = currentDate.toString("yyyy-MM-dd");
QString timeString = currentTime.toString("HH:mm:ss");
QString yearString = currentDate.toString("yyyy");
QString monthString = currentDate.toString("M");
QString dayString = currentDate.toString("d");

result = transformEngine.transformToString(templateStr, data);
// qDebug() << result;
QCOMPARE(result, "\n// code for test data (class TestData) defined TEST_DATA\n// generated on " + dateString + " " + timeString + "\n\n   param = addParameter(new Property(\"testDouble\", 100, tr(\"Testing double\"), \"\"));\n   // param is not read only\n   param = addParameter(new Property(\"testVector3D\", QVector3D(" + yearString + ", " + monthString + ", " + dayString + "), tr(\"test 3D vector\"), \"mm\"));\n   param->setReadOnly(true);\n   param = addParameter(new Property(\"testenum\", 1, tr(\"test enum\"), \"mm\"));\n   // param is not read only\n   \n       // value: V1\n       // value: V2\n       // value: V3\n// given name: test data\n\n// no option\n\n// optionList is given\n    \n    // option: option1\n    // option: option2\n    // option: false\n    // option: -5\n    // option: 0\n");
}

void testDoc() {
    TransformEngine transformEngine;
    QString result = transformEngine.transformToString(R"(@for@ $e$ in $a$
    @if@ equals($e.b$,"foo") 
        Element number $index(e)$: b key has value foo, c key has value $e.c$,
    @else@ 
        Element number $index(e)$: b key is not foo, 
    @endif@
    @endfor@)", JsonUtils::stringToJSON(R"json({ "a": [ { "b": "foo", "c":"baz" }, { "b": "bar" }, {"b" : "foo", "c":"faz"}, {} ] })json"));
    QCOMPARE(result, "\n    Element number 0: b key has value foo, c key has value baz,\n    Element number 1: b key is not foo,\n    Element number 2: b key has value foo, c key has value faz,\n    Element number 3: b key is not foo,");
}
};

// Manual coverage:
// cd build
// ./bin/test-transformengine
// or ctest -R test-transformengine-1
// lcov --capture --directory . --output-file coverage.info
// genhtml coverage.info --output-directory coverage_report
// firefox coverage_report/index.html

QTEST_APPLESS_MAIN(TestTransformEngine)

#include "TestTransformEngine.moc"