#define CATCH_CONFIG_RUNNER

// clazy:excludeall=non-pod-global-static

#include "configuration.h"
#include <QApplication>
#include <catch2/catch.hpp>

SCENARIO("Configuration")
{
    GIVEN("a Configuration object with some initial values")
    {
        Configuration conf{
            { "name", std::string() },
            { "over", std::string() },
            // this entry is not in the conf file
            { "other", std::string("not in cfg") },
            // commented out entry in the conf file
            { "comment", std::string("123.456") },
            { "number", int(0) },
            { "toggle", bool(false) },

            { "main/name", std::string() },
            { "main/number", int(0) },
            { "main/toggle", bool(true) },

            { "extra/name", std::string() },
            { "extra/number", int(0) },
            { "extra/toggle", bool(false) },
        };

        WHEN("reading default values")
        {
            REQUIRE(conf.value<std::string>("name"));
            REQUIRE(conf.value<std::string>("name").value() == std::string());
            REQUIRE(conf.value<int>("number"));
            REQUIRE(conf.value<int>("number").value() == 0);
            REQUIRE(conf.value<bool>("toggle"));
            REQUIRE(conf.value<bool>("toggle").value() == false);

            REQUIRE(!conf.value<int>("nullopt"));
            REQUIRE(!conf.value<bool>("nullopt"));
            REQUIRE(!conf.value<std::string>("nullopt"));

            REQUIRE(conf.value<std::string>("main/name"));
            REQUIRE(conf.value<std::string>("main/name").value() == std::string());
            REQUIRE(conf.value<int>("main/number"));
            REQUIRE(conf.value<int>("main/number").value() == 0);
            REQUIRE(conf.value<bool>("main/toggle"));
            REQUIRE(conf.value<bool>("main/toggle").value() == true);

            REQUIRE(!conf.value<int>("main/nullopt"));
            REQUIRE(!conf.value<bool>("main/nullopt"));
            REQUIRE(!conf.value<std::string>("main/nullopt"));

            THEN("value casting")
            {
                REQUIRE(conf.value<bool>("number").value() == false);
                REQUIRE(conf.value<std::string>("number").value() == "0");

                REQUIRE(conf.value<std::string>("toggle").value() == "false");
                REQUIRE(conf.value<std::string>("main/toggle").value() == "true");

                REQUIRE(conf.value<int>("comment").value() == 123);
                REQUIRE(std::abs(conf.value<double>("comment").value() - 123.456) < 0.001);
                REQUIRE(!conf.value<bool>("name"));
            }
        }

        WHEN("reading configuration file")
        {
            conf.read_file(std::getenv("CONFIGFILE"));

            THEN("reading no section")
            {
                REQUIRE(conf.value<std::string>("name").value() == "Top level");
                REQUIRE(conf.value<std::string>("other").value() == "not in cfg");
                REQUIRE(conf.value<std::string>("comment").value() == "123.456");
                REQUIRE(conf.value<int>("number").value() == 12);
                REQUIRE(conf.value<bool>("toggle").value() == true);
            }

            THEN("reading main section")
            {
                REQUIRE(conf.value<std::string>("main/name").value() == "Section Testing");
                REQUIRE(conf.value<int>("main/number").value() == 10);
                REQUIRE(conf.value<bool>("main/toggle").value() == false);
            }

            THEN("reading included section")
            {
                REQUIRE(conf.value<std::string>("over").value() == "extra");
                REQUIRE(conf.value<std::string>("extra/name").value() == "extra section");
                REQUIRE(conf.value<int>("extra/number").value() == 12);
                REQUIRE(conf.value<bool>("extra/toggle").value() == true);
            }

            THEN("value casting")
            {
                REQUIRE(conf.value<std::string>("number").value() == "12");
                REQUIRE(conf.value<std::string>("toggle").value() == "true");
                REQUIRE(conf.value<std::string>("main/toggle").value() == "false");
            }

            THEN("Qt cast specialization")
            {
                REQUIRE(conf.value<QString>("name").value() == "Top level");
                REQUIRE(conf.value<QString>("number").value() == "12");
                REQUIRE(conf.value<QString>("toggle").value() == "true");
                REQUIRE(conf.value<QString>("main/toggle").value() == "false");
                REQUIRE(!conf.value<QString>("nullopt"));

                REQUIRE(conf.value<QStringList>("list").value() == QStringList({ "one", "two", "three", "for four" }));
                REQUIRE(!conf.value<QStringList>("nullopt"));
            }

            THEN("Qt shortcut")
            {
                REQUIRE(conf.value<std::string>("qt/shortcut") == "Ctrl+Q");
                QAction action;
                REQUIRE(conf.shortcut<QAction>(action, "qt/shortcut").shortcut().toString() == "Ctrl+Q");
                REQUIRE(conf.shortcut<QAction>(action, "qt/nil").shortcut().toString() == "Ctrl+Q");

                QKeySequence sequence;
                REQUIRE(conf.shortcut<QKeySequence>(sequence, "qt/shortcut").toString() == "Ctrl+Q");
                REQUIRE(conf.shortcut<QKeySequence>(sequence, "qt/nil").toString() == "Ctrl+Q");
            }
        }
    }

    GIVEN("global configuration")
    {
        std::unique_ptr<Configuration> global_conf = std::make_unique<Configuration, std::initializer_list<std::pair<std::string, conf_value_t>>>({
            { "name", std::string("global") },
            { "number", int(123) },
            { "toggle", bool(true) },

        });

        WHEN("no configuration is set")
        {
            REQUIRE_THROWS(Configuration());

            std::stringstream output;
            REQUIRE_THROWS(output << Configuration());
        }
        WHEN("global instance is set")
        {
            std::stringstream output;

            output << *global_conf;
            REQUIRE(output.str() == "name=global\nnumber=123\ntoggle=true\n");

            Configuration::move_global(std::move(global_conf));
            Configuration g;
            REQUIRE(g.value<std::string>("name"));
            REQUIRE(g.value<std::string>("name").value() == "global");
            REQUIRE(g.value<int>("number"));
            REQUIRE(g.value<int>("number").value() == 123);
            REQUIRE(g.value<bool>("toggle"));
            REQUIRE(g.value<bool>("toggle").value() == true);
            REQUIRE(!g.value<std::string>("nullopt"));

            output.str(std::string());
            output << g;
            REQUIRE(output.str() == "name=global\nnumber=123\ntoggle=true\n");
        }
    }
}

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    int result = Catch::Session().run(argc, argv);
    return result;
}