/*****************************************************************************
 * $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 "VariantDataModel.h"
#include "JsonUtils.h"

#include <iostream>

class TestVariantDataModel : public QObject {
    Q_OBJECT

private slots:
    void json() {
        QString rawInput = R"json({"field0":{"field1":true,"field2":["value2-0",4.567]},"field3":0,"field4":[{"field5":"value4","field6":"value5"},{"field5":"value6","field6":"value7"}]})json";
        QJsonObject inputData = JsonUtils::stringToJSON(rawInput);
        VariantDataModel vdm(inputData.toVariantMap());
        QCOMPARE(vdm.toJsonString(), rawInput);
        vdm = VariantDataModel::fromJsonString(rawInput);
        QCOMPARE(vdm.toJsonString(), rawInput);
        vdm = VariantDataModel::fromJsonObject(inputData);
        QCOMPARE(vdm.toJsonString(), rawInput);
    }

    void variantFull() {
        QVariantMap v0;
        QVariantMap v1; // {"field1": "value1", "field2": ["value2-0", "value2-1"]}
        v1["field1"] = "value1";
        v1["field2"] = QStringList() << "value2-0" << "value2-1";
        v0["field0"] = v1;
        v0["field3"] = "value3";
        QVariantMap v2; // {"field6": "value4", "field7": "value5"}
        v2["field6"] = "value4";
        v2["field7"] = "value5";
        QVariantList vl; // [v2, v3]
        vl.append(v2);
        QVariantMap v3; // {"field6": "value6", "field7": "value7"}
        v3["field6"] = "value6";
        v3["field7"] = "value7";
        vl.append(v3);
        v0["field5"] = vl; // v0 = {"field0": v1, "field3": "value3", "field5": vl}

        VariantDataModel vdm = VariantDataModel(v0);
        QVERIFY(vdm == v0);
        QCOMPARE(vdm, QVariant(v0));

        QJsonDocument jsonDoc = QJsonDocument::fromVariant(v0);
        QJsonObject jsonObject = jsonDoc.object();
        vdm = VariantDataModel(jsonObject.toVariantMap());
        // qDebug() << "VDM value: " << vdm.getValue();
        // qDebug() << "jsonObject tovariantMap: " << jsonObject.toVariantMap();
        QCOMPARE(vdm.getValue(), jsonObject.toVariantMap());

        int index = 1;
        QCOMPARE(vdm.value("field0"), v1);

        QCOMPARE(vdm.value("field0").value("field1"), QVariant("value1"));

        QCOMPARE(vdm.value("field0").value("field2").at(1), QVariant("value2-1"));

        QCOMPARE(vdm.isModified(), false);

    }

    void variantList() {
        QVariantList vl2 = {"value2-0", "value2-1", 2};
        VariantDataModel vdm = VariantDataModel({"value2-0", "value2-1", 2});
        QCOMPARE(vdm.getValue(), vl2);
        QStringList vl2string = {"value2-0", "value2-1"};
        VariantDataModel vdmstring = VariantDataModel({"value2-0", "value2-1"});
        QCOMPARE(vdmstring.getValue().toStringList(), vl2string);
        QCOMPARE(vdmstring, vl2string);

    }

    void variantMap() {
        QVariantMap vm2;
        vm2["key0"] = QStringList({"value2-0", "value2-1"});
        VariantDataModel vdm = VariantDataModel(vm2);
        QVERIFY(vdm == vm2);// Test equality operator
    }

    void modifyModel() {
        QString rawInput = R"json({"field0":{"field1":true,"field2":["value2-0",4.567]},"field3":0,"field4":[{"field5":"value4","field6":"value5"},{"field5":"value6","field6":"value7"}]})json";
        VariantDataModel vdm1 = VariantDataModel::fromJsonString(rawInput);
        QCOMPARE(vdm1.toJsonString(), rawInput);

        vdm1.value("field0").insert("field4", QStringList({"a", "b", "c"})); // will add a list as "field4"
        QCOMPARE(vdm1.value("field0").value("field4").getValue(), QVariantList({"a", "b", "c"}));
        QCOMPARE(vdm1.isModified(), true);

        VariantDataModel vdm2 = vdm1.value("field0").value("field4").at(2);
        QCOMPARE(vdm2.getValue(), QVariant("c"));

        vdm1.value("field0").value("field4").append(QVariant("d"));
        QCOMPARE(vdm1.value("field0").value("field4").getValue().toStringList(), QStringList({"a", "b", "c", "d"}));

        vdm1["field0"]["field4"][1].remove(); // Remove empties [1] but does not remove it from "field4"
        QCOMPARE(vdm1["field0"]["field4"].getValue(), QVariant(QVariantList({"a", "c", "d"})));
        QVERIFY(!vdm1["field0"]["field4"][1].getValue().isValid()); // Value was emptied -> invalid
        QCOMPARE(vdm1["field0"]["field4"][2], QVariant("c"));
        vdm1["field0"]["field4"].removeAt(1);
        QCOMPARE(vdm1["field0"]["field4"][2], QVariant("d"));
        QCOMPARE(vdm1.remove("field3"), 1);
        QCOMPARE(vdm1.contains("field3"), false);

        VariantDataModel vdm3 = QVariantMap();
        vdm3["field6"] = 12.5;
        vdm3["field7"] = false;
        vdm3["field8"] = -50;
        vdm3["field9"] = "value";

        vdm1["field4"].append(vdm3);
        QCOMPARE(vdm1.value("field4").at(3).getValue().isValid(), false);
        QCOMPARE(vdm1.value("field4").at(2).getValue(), vdm3.getValue());
        QCOMPARE(vdm1["field4"][2]["field8"].getValue(), QVariant(-50));
        QCOMPARE(vdm1["field4"]["2"]["field8"].getValue(), QVariant(-50));

        vdm1["field4"].removeOne(vdm3);
        QCOMPARE(vdm1.value("field4").size(), 2);
    }

    void usingRef() {
        QString rawInput = R"json({"field0":{"field1":true,"field2":["value2-0",4.567]},"field3":0,"field4":[{"field5":"value4","field6":"value5"},{"field5":"value6","field6":"value7"}]})json";
        VariantDataModel vdm1 = VariantDataModel::fromJsonString(rawInput);

        // Using reference to a part of vn to modify it
        VariantDataModel& refToField4 = vdm1["field4"];

        refToField4[1]["field6"] = 42;
        QCOMPARE(vdm1["field4"]["1"]["field6"].getValue(), QVariant(42));

        VariantDataModel vdm2 = QVariantMap();
        vdm2["field6"] = 12.5;
        vdm2["field7"] = false;
        vdm2["field8"] = -50;
        vdm2["field9"] = "value";
        // Add the vdm2 variant map to the list referenced by refToField4 (points to vdm1["field4"])
        refToField4.append(vdm2);

        QStringList list = { "x", "y", "z"};
        // Replacing false value by the QStringList using the reference
        refToField4[2]["field7"] = list;
        // Check that the list was put into the real object
        QCOMPARE(vdm1["field4"][2]["field7"].getValue().toStringList(), list);
        // Add 'w' to this list
        vdm1["field4"][2]["field7"].append(QVariant("w"));
        // Now check that the list was updated inside the object (careful, the type must be QStringList, not QVariantList of only QStrings)
        QCOMPARE(vdm1["field4"][2]["field7"].getValue().toStringList(), QStringList({ "x", "y", "z", "w"}));

        QVariantMap vdm3;
        vdm3["key1"] = 1;
        vdm3["key2"] = "vm3";
        vdm1["field4"][2]["field7"][0] = VariantDataModel(vdm3);

        VariantDataModel& refToField7 = refToField4[2]["field7"];
        QCOMPARE(refToField7.size(), 4);
        QCOMPARE(refToField7[0].size(), 2);

        QCOMPARE(refToField7[0].getValue(), vdm3);

        QCOMPARE(refToField7.removeAt(2), true);
        QCOMPARE(refToField7[2].getValue(), "w");

        VariantDataModel& refToField0 = vdm1["field0"];
        QVariantList l1 = QVariantList({ "A", "B", QVariantList({ "C", "D"})});
        refToField0["field3"] = VariantDataModel(l1);
        QCOMPARE(vdm1["field0"]["field3"].size(), 3);
        QCOMPARE(vdm1["field0"]["field3"][2][1].getValue(), QVariant("D"));

        QCOMPARE(vdm1.toJsonString(), R"json({"field0":{"field1":true,"field2":["value2-0",4.567],"field3":["A","B",["C","D"]]},"field3":0,"field4":[{"field5":"value4","field6":"value5"},{"field5":"value6","field6":42},{"field6":12.5,"field7":[{"key1":1,"key2":"vm3"},"y","w"],"field8":-50,"field9":"value"}]})json");
    }

    void testUnsupportedVariantToJson() {
        VariantDataModel vdm = QVariantMap();
        QUuid myId = QUuid::createUuid();
        vdm["myId"] = myId;
        vdm["nullId"] = QUuid();
        vdm["testPoint"] = QPoint(42, -42);
        QDate today = QDate::currentDate();
        vdm["testDate"] = today;
        vdm["testColor"] = QColor();

        std::cout << vdm.toJsonString().toStdString() << std::endl;

        QCOMPARE(vdm["myId"].toString(), myId.toString());

        QCOMPARE(vdm["nullId"].getValue().isValid(), true);
        QCOMPARE(vdm["nullId"].getValue(), QVariant(QUuid()));
        QCOMPARE(vdm["nullId"].getValue().toUuid().isNull(), true);

        QCOMPARE(vdm["testPoint"].getValue().toPoint().x(), 42);
        QCOMPARE(vdm["testPoint"].toString().isNull(), true); // QVariant cannot convert QPoint directly to QString
        QCOMPARE(vdm["testDate"].getValue(), today);
        QCOMPARE(vdm["testColor"].getValue(), QColor());
        QCOMPARE(vdm["testColor"].getValue().isValid(), true);
    }

    void invalidVariant() {
        QString rawInput = R"json({"field0":{"field1":true,"field2":["value2-0",4.567]},"field3":0,"field4":[{"field5":"value4","field6":"value5"},{"field5":"value6","field6":"value7"}]})json";
        VariantDataModel vdm = VariantDataModel::fromJsonString(rawInput);
        QCOMPARE(vdm.isValid(), true);
        QCOMPARE(vdm.invalid().isValid(), false);
    }

    void docExample() {
        QString rawInput = R"json({"name":"foo","parameters":[{"name":"bar","value":14},{"name":"baz","value":"faz"}]})json";
        VariantDataModel dataModel = VariantDataModel::fromJsonString(rawInput);
        QCOMPARE(dataModel["name"].getValue(), QString("foo"));
        std::cout << "Name: " << dataModel["name"].toString().toStdString() << std::endl; // "foo"
        VariantDataModel& parameters = dataModel["parameters"];

        parameters[0]["value"] = parameters[0]["value"].getValue().toInt() / 4.0;

        parameters[1]["value"] = "fuz";

        VariantDataModel anotherParam = QVariantMap();
        anotherParam["name"] = "camitk";
        anotherParam["value"] = true;
        parameters.append(anotherParam);
        QCOMPARE(dataModel.toJsonString(), R"json({"name":"foo","parameters":[{"name":"bar","value":3.5},{"name":"baz","value":"fuz"},{"name":"camitk","value":true}]})json");

        for (unsigned i = 0; i < parameters.size(); i++) {
            std::cout << "- parameter " << i << ": name \"" << parameters[i]["name"].toString().toStdString() << "\", value: " << parameters[i]["value"].toString().toStdString() << std::endl;
        }
        // Prints
        // Name: foo
        // - parameter 0: name "bar", value: 3.5
        // - parameter 1: name "baz", value: fuz
        // - parameter 2: name "camitk", value: true
        //

        // using STL
        for (auto p : parameters) {
            std::cout << "- parameter name \"" << p["name"].toString().toStdString() << "\", value: " << p["value"].toString().toStdString() << std::endl;
        }

        QString parameterToFind = "camitk";
        auto itList = std::find_if(parameters.begin(), parameters.end(), [ &parameterToFind ](VariantDataModel const & p) {
            return p["name"].toString() == parameterToFind;
        });
        QCOMPARE((itList != parameters.end()), true);

        QString valueToFind = "foo";
        auto itMap = std::find_if(dataModel.beginMap(), dataModel.endMap(), [&valueToFind](const QString & value) {
            return value == valueToFind;
        });
        QCOMPARE((itMap != dataModel.endMap()), true);
        QCOMPARE(itMap.key(), QString("name"));

    }

};

// Manual coverage:
// cd build
// ./bin/test-variantdatamodel
// ctest -R test-variantdatamodel-1
// lcov --capture --directory . --output-file coverage.info
// genhtml coverage.info --output-directory coverage_report
//

QTEST_APPLESS_MAIN(TestVariantDataModel)

#include "TestVariantDataModel.moc"