#ifdef TESTS_BUILD
#include <catch.hpp>
#include "../src/Script.hpp"

using namespace Porygon;

TEST_CASE( "Define script function", "[integration]" ) {
    Script* script = Script::Create("function add(number a, number b) a + b end");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto variable = script->GetVariable(u"add");
    REQUIRE(variable != nullptr);
    REQUIRE(variable->GetTypeClass() == TypeClass::Function);
    delete script;
    delete variable;
}

TEST_CASE( "Define script function and call", "[integration]" ) {
    Script* script = Script::Create("function add(number a, number b) result = a + b end add(1, 2)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto variable = script->GetVariable(u"add");
    REQUIRE(variable != nullptr);
    REQUIRE(variable->GetTypeClass() == TypeClass::Function);
    auto result = script->GetVariable(u"result");
    REQUIRE(result->GetTypeClass() == TypeClass::Number);
    REQUIRE(result->EvaluateInteger() == 3);
    delete script;
    delete variable;
    delete result;
}

TEST_CASE( "Define script function and call multiple times", "[integration]" ) {
    Script* script = Script::Create("result = 0 function add(number a) result = result + a end add(1) add(4)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto variable = script->GetVariable(u"add");
    REQUIRE(variable != nullptr);
    REQUIRE(variable->GetTypeClass() == TypeClass::Function);
    auto result = script->GetVariable(u"result");
    REQUIRE(result->GetTypeClass() == TypeClass::Number);
    REQUIRE(result->EvaluateInteger() == 5);
    delete script;
    delete variable;
    delete result;
}

TEST_CASE( "Define script function and call from extern", "[integration]" ) {
    Script* script = Script::Create("result = 0 function add(number a) result = result + a end");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();

    REQUIRE(script->HasFunction(u"add"));
    auto toAddVal = new IntegerEvalValue(5);
    script->CallFunction(u"add", {toAddVal});
    delete toAddVal;
    toAddVal = new IntegerEvalValue(6);
    script->CallFunction(u"add", {toAddVal});
    delete toAddVal;

    auto result = script->GetVariable(u"result");
    REQUIRE(result->GetTypeClass() == TypeClass::Number);
    REQUIRE(result->EvaluateInteger() == 11);
    delete script;
    delete result;
}

TEST_CASE( "Define script function and return", "[integration]" ) {
    Script* script = Script::Create(
            "val = 0\n"
            "function add(number a, number b) \n"
            "return a + b \n"
            "val = val + 1\n"
            "end");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();

    REQUIRE(script->HasFunction(u"add"));
    auto toAddVal = new IntegerEvalValue(5);
    auto toAddVal2 = new IntegerEvalValue(6);
    auto result = script->CallFunction(u"add", {toAddVal, toAddVal2});
    delete toAddVal;
    delete toAddVal2;

    REQUIRE(result->GetTypeClass() == TypeClass::Number);
    REQUIRE(result->EvaluateInteger() == 11);

    auto variable = script->GetVariable(u"val");
    REQUIRE(variable->GetTypeClass() == TypeClass::Number);
    REQUIRE(variable->EvaluateInteger() == 0);

    delete script;
    delete variable;
    delete result;
}

TEST_CASE( "Functions can call themselves", "[integration]" ) {
    Script* script = Script::Create(
            R"(
val = 0
function add()
    if val < 5 then
        val = val + 1
        add()
    end
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    script->CallFunction(u"add", {});

    auto variable = script->GetVariable(u"val");
    REQUIRE(variable->GetTypeClass() == TypeClass::Number);
    REQUIRE(variable->EvaluateInteger() == 5);

    delete script;
    delete variable;
}

TEST_CASE( "Functions respect scope", "[integration]" ) {
    Script* script = Script::Create(
R"(
function test()
    function foo()
       local a = 10
    end
    if true then
       if true then
           local a = 50
           foo()
           result = a
       end
    end
end
test()
)"
    );
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();

    auto variable = script->GetVariable(u"result");
    REQUIRE(variable->GetTypeClass() == TypeClass::Number);
    REQUIRE(variable->EvaluateInteger() == 50);

    delete script;
    delete variable;
}

TEST_CASE( "Return doesn't parse next line expression", "[integration]" ) {
    Script* script = Script::Create(
            R"(
return
1 + 1
)"
    );
    REQUIRE(!script->Diagnostics -> HasErrors());
    auto result = script->Evaluate();
    REQUIRE(result == nullptr);

    delete script;
}

TEST_CASE( "Allow declaration of multiple functions with different signatures", "[integration]" ) {
    Script* script = Script::Create(
            R"(
function add(number a, number b)
    return a + b
end

function add(string a, string b)
    return a + b
end

intResult = add(5, 500)
stringResult = add('foo', 'bar')
)"
    );
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto intVar = script -> GetVariable(u"intResult");
    REQUIRE(intVar != nullptr);
    CHECK(intVar->EvaluateInteger() == 505);
    auto stringVar = script -> GetVariable(u"stringResult");
    REQUIRE(stringVar != nullptr);
    CHECK(stringVar->EvaluateString() == u"foobar");

    delete script;
    delete intVar;
    delete stringVar;
}


#endif
