
#ifdef TESTS_BUILD
#include <catch.hpp>
#include "../src/Script.hpp"
#include "../../src/UserData/UserData.hpp"
#include "../../src/UserData/UserDataStorage.hpp"
#include "../../src/UserData/UserDataValue.hpp"
#include "../../src/UserData/UserDataFunction.hpp"
#include "../../src/UserData/UserDataFunctionType.hpp"
#include "../../src/UserData/UserDataTemplates.hpp"
#include "../../src/UserData/UserDataCollections/UserDataCollectionType.hpp"
#include "../../src/UserData/UserDataCollections/UserDataCollectionValue.hpp"
#include "../../src/UserData/UserDataCollections/UserDataCollectionRangeIterator.hpp"
#include "../../src/Evaluator/EvalValues/EvalValueHelper.hpp"

using namespace Porygon;
using namespace Porygon::UserData;
using namespace Porygon::Utilities;

class UserDataTestObject{
public:
    int foo = 10;
    vector<int> fooVector = {5,10,15,25};
    int getFoo(){
        return foo;
    }

    int Addition(int a, int b){
        return a + b;
    }

    // Declare script properties
private:
    PORYGON_PREPARE_FUNCTION(UserDataTestObject, getFoo, IntegerEvalValue)
    PORYGON_PREPARE_FUNCTION(UserDataTestObject, Addition, IntegerEvalValue, (par[0] -> EvaluateInteger()), (par[1] -> EvaluateInteger()))
    void __setFooVector(const EvalValue* key, const EvalValue* value){
        auto index = key->EvaluateInteger();
        fooVector[index] = value->EvaluateInteger();
    }

public:
    PORYGON_USERDATA(UserDataTestObject,
        PORYGON_INTEGER_FIELD(foo)
        PORYGON_INTEGER_FUNCTION(getFoo)
        PORYGON_INTEGER_FUNCTION(Addition, PORYGON_INTEGER_TYPE, PORYGON_INTEGER_TYPE)
        PORYGON_READONLY_VECTOR_FIELD(fooVector, PORYGON_INTEGER_TYPE)
    )
};

TEST_CASE( "Gets UserData value", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    return obj["foo"]
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto par = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), par);
    auto variable = script->CallFunction(u"testFunc", {parameter});
    REQUIRE(variable != nullptr);
    REQUIRE(variable->EvaluateInteger() == 10);
    delete par;
    delete parameter;
    delete script;
    delete variable;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

TEST_CASE( "Sets UserData value", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    obj["foo"] = 5000
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto obj = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), obj);
    script->CallFunction(u"testFunc", {parameter});
    delete script;
    REQUIRE(obj->foo == 5000);
    delete obj;
    delete parameter;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

TEST_CASE( "Calls UserData function", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    return obj.getFoo()
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto obj = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), obj);
    auto result = script->CallFunction(u"testFunc", {parameter});
    REQUIRE(result -> EvaluateInteger() == 10);
    delete script;
    delete obj;
    delete parameter;
    delete result;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

TEST_CASE( "Calls UserData function with parameters", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    return obj.Addition(5046, 8432)
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto obj = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), obj);
    auto result = script->CallFunction(u"testFunc", {parameter});
    REQUIRE(result -> EvaluateInteger() == 13478);
    delete script;
    delete obj;
    delete parameter;
    delete result;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

TEST_CASE( "Gets userdata vector value", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    return obj.fooVector[1]
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto func = (GenericFunctionEvalValue*)script -> GetVariable(u"testFunc");
    auto funcType = func -> GetType();
    REQUIRE(funcType->GetFirstOption()->GetReturnType()->GetClass() == TypeClass::Number);
    auto obj = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), obj);
    auto result = script->CallFunction(u"testFunc", {parameter});
    REQUIRE(result -> EvaluateInteger() == 5);
    delete obj;
    delete parameter;
    delete result;
    delete script;
    delete func;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

TEST_CASE( "Sets userdata vector value", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    obj.fooVector[3] = 684
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto func = (GenericFunctionEvalValue*)script -> GetVariable(u"testFunc");
    auto funcType = func -> GetType();
    auto obj = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), obj);
    script->CallFunction(u"testFunc", {parameter});
    REQUIRE(obj->fooVector[2] == 684);
    delete obj;
    delete parameter;
    delete script;
    delete func;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

TEST_CASE( "Iterate over userdata vector keys", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    local val = 0
    for i in obj.fooVector do
        val = val + i
    end
    return val
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto func = (GenericFunctionEvalValue*)script -> GetVariable(u"testFunc");
    auto funcType = func -> GetType();
    auto obj = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), obj);
    auto result = script->CallFunction(u"testFunc", {parameter});
    REQUIRE(result->EvaluateInteger() == 10);
    delete obj;
    delete parameter;
    delete script;
    delete func;
    delete result;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

TEST_CASE( "Iterate over userdata vector values", "[integration]" ) {
    UserDataStorage::RegisterType(HashedString::ConstHash("testObject"), UserDataTestObject::__createUserData());
    Script* script = Script::Create(R"(
function testFunc(testObject obj)
    local val = 0
    for i, v in obj.fooVector do
        val = val + v
    end
    return val
end
)");
    REQUIRE(!script->Diagnostics -> HasErrors());
    script->Evaluate();
    auto func = (GenericFunctionEvalValue*)script -> GetVariable(u"testFunc");
    auto funcType = func -> GetType();
    auto obj = new UserDataTestObject();
    auto parameter = new UserDataValue(HashedString::ConstHash("testObject"), obj);
    auto result = script->CallFunction(u"testFunc", {parameter});
    REQUIRE(result->EvaluateInteger() == 55);
    delete obj;
    delete parameter;
    delete script;
    delete func;
    delete result;
    UserDataStorage::RemoveType(HashedString::ConstHash("testObject"));
}

#endif
