#ifdef TESTS_BUILD
#include <catch.hpp>
#include "../src/Script.hpp"
using namespace Porygon;

class ModuleHandler{
    class Internal{
    public:
        unordered_map<string, Script*> MODULES;

        Internal(){
            MODULES = {
                {"simple_return", Script::Create(u"return 500")},
                {"simple_variables", Script::Create(u"foo = 50\nbar = \'test\'")},
                {
                    "complex_module", Script::Create(uR"(
local module = {
    function contains(table table, string s)
        for _, v in table do
            if (v == s) then return true end
        end
        return false
    end
}
return module
)")
                },
                {"simple_no_return", Script::Create(u"foo = 500")},
                {"no_return_with_local", Script::Create(uR"(
local foo = 684
function bar()
    return foo
end
)")},
            };
        }

        ~Internal(){
            for (const auto& v: MODULES){
                delete v.second;
            }
            MODULES.clear();
        }
    };
public:

    static Internal* _internal;

    static Internal* GetInternal(){
        if (!_internal)
            _internal = new Internal();
        return _internal;
    }

    inline static bool DoesModuleExist(const char* moduleName, size_t size){
        return GetInternal()->MODULES.find(moduleName) != GetInternal()->MODULES.end();
    }

    inline static Script* ResolveModule(const char* moduleName, size_t size){
        auto module = GetInternal()->MODULES[moduleName];
        REQUIRE(!module->Diagnostics->HasErrors());
        return module;
    }

    static void Initialize(){
        ScriptOptions::GetDefaultScriptOptions()->SetModuleExistsFunc(DoesModuleExist);
        ScriptOptions::GetDefaultScriptOptions()->SetResolveModuleFunc(ResolveModule);
    }
};

ModuleHandler::Internal* ModuleHandler::_internal = nullptr;

TEST_CASE( "Require simple return script", "[integration]" ) {
    ModuleHandler::Initialize();
    auto script = Script::Create(uR"(
return require("simple_return")
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    auto var = script->Evaluate();
    REQUIRE(var->EvaluateInteger() == 500);
    delete script;
}

TEST_CASE( "Require simple variables script", "[integration]" ) {
    ModuleHandler::Initialize();
    auto script = Script::Create(uR"(
require("simple_variables")
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    REQUIRE(script->HasVariable(u"foo"));
    REQUIRE(script->HasVariable(u"bar"));
    auto foo = script->GetVariable(u"foo");
    auto bar = script->GetVariable(u"bar");
    CHECK(foo->EvaluateInteger() == 50);
    CHECK(bar->EvaluateString() == u"test");

    delete foo;
    delete bar;
    delete script;
}

TEST_CASE( "Require string table contains", "[integration]" ) {
    ModuleHandler::Initialize();
    auto script = Script::Create(uR"(
local list = require("complex_module")
return list.contains({"foo", "bar"}, "bar")
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    auto result = script->Evaluate();
    CHECK(result->EvaluateBool());

    delete script;
}

TEST_CASE( "Simple no return module", "[integration]" ) {
    ModuleHandler::Initialize();
    auto script = Script::Create(uR"(
require("simple_no_return")
return foo
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    auto result = script->Evaluate();
    CHECK(result->EvaluateInteger() == 500);

    delete script;
}

TEST_CASE( "Simple no return module with local variable", "[integration]" ) {
    ModuleHandler::Initialize();
    auto script = Script::Create(uR"(
require("no_return_with_local")
return bar()
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    auto result = script->Evaluate();
    CHECK(result->EvaluateInteger() == 684);

    delete script;
}


#endif