// https://github.com/vinniefalco/LuaBridge
// Copyright 2019, Dmitry Tarakanov
// SPDX-License-Identifier: MIT

#include "TestBase.h"

struct IssueTests : TestBase
{
};

struct AbstractClass
{
    virtual int sum(int a, int b) = 0;
};

struct ConcreteClass : AbstractClass
{
    int sum(int a, int b) override { return a + b; }

    static AbstractClass& get()
    {
        static ConcreteClass instance;
        return instance;
    }
};

TEST_F(IssueTests, Issue87)
{
    luabridge::getGlobalNamespace(L)
        .beginClass<AbstractClass>("Class")
        .addFunction("sum", &AbstractClass::sum)
        .endClass()
        .addFunction("getAbstractClass", &ConcreteClass::get);

    runLua("result = getAbstractClass ():sum (1, 2)");
    ASSERT_TRUE(result().isNumber());
    ASSERT_EQ(3, result<int>());
}

TEST_F(IssueTests, Issue121)
{
    runLua(R"(
    first = {
      second = {
        actual = "data"
      }
    }
  )");
    auto first = luabridge::getGlobal(L, "first");
    ASSERT_TRUE(first.isTable());
    ASSERT_EQ(0, first.length());
    ASSERT_TRUE(first["second"].isTable());
    ASSERT_EQ(0, first["second"].length());
}

void pushArgs(lua_State*)
{
}

template<class Arg, class... Args>
void pushArgs(lua_State* L, Arg arg, Args... args)
{
    luabridge::Stack<Arg>::push(L, arg);
    pushArgs(L, args...);
}

template<class... Args>
std::vector<luabridge::LuaRef> callFunction(const luabridge::LuaRef& function, Args... args)
{
    assert(function.isFunction());

    lua_State* L = function.state();
    int originalTop = lua_gettop(L);
    function.push(L);
    pushArgs(L, args...);

    luabridge::LuaException::pcall(L, sizeof...(args), LUA_MULTRET);

    std::vector<luabridge::LuaRef> results;
    int top = lua_gettop(L);
    results.reserve(top - originalTop);
    for (int i = originalTop + 1; i <= top; ++i)
    {
        results.push_back(luabridge::LuaRef::fromStack(L, i));
    }
    return results;
}

TEST_F(IssueTests, Issue160)
{
    runLua("function isConnected (arg1, arg2) "
           " return 1, 'srvname', 'ip:10.0.0.1', arg1, arg2 "
           "end");

    luabridge::LuaRef f_isConnected = luabridge::getGlobal(L, "isConnected");

    auto v = callFunction(f_isConnected, 2, "abc");
    ASSERT_EQ(5u, v.size());
    ASSERT_EQ(1, v[0].cast<int>());
    ASSERT_EQ("srvname", v[1].cast<std::string>());
    ASSERT_EQ("ip:10.0.0.1", v[2].cast<std::string>());
    ASSERT_EQ(2, v[3].cast<int>());
    ASSERT_EQ("abc", v[4].cast<std::string>());
}

struct Vector
{
    float getX() const { return x; }

    float x = 0;
};

struct WideVector : Vector
{
    WideVector(float, float, float, float w) { x = w; }
};

TEST_F(IssueTests, Issue178)
{
    luabridge::getGlobalNamespace(L)
        .beginClass<Vector>("Vector")
        .addFunction("getX", &Vector::getX)
        .addProperty("X", &Vector::getX)
        .addData("x", &Vector::x, true)
        .endClass()
        .deriveClass<WideVector, Vector>("WideVector")
        .addConstructor<void (*)(float, float, float, float)>()
        .endClass();

    runLua("result = WideVector (0, 1, 2, 3).x");

    ASSERT_TRUE(result().isNumber());
    ASSERT_EQ(3.f, result<float>());
}

enum class MyEnum
{
    VALUE0,
    VALUE1,
};

template<typename T>
struct EnumWrapper
{
    static typename std::enable_if<std::is_enum<T>::value, void>::type push(lua_State* L, T value)
    {
        lua_pushnumber(L, static_cast<std::size_t>(value));
    }

    static typename std::enable_if<std::is_enum<T>::value, T>::type get(lua_State* L, int index)
    {
        return static_cast<T>(lua_tointeger(L, index));
    }
};

namespace luabridge {

template<>
struct Stack<MyEnum> : EnumWrapper<MyEnum>
{
};

} // namespace luabridge

TEST_F(IssueTests, Issue127)
{
    runLua("result = 1");
    ASSERT_EQ(MyEnum::VALUE1, result<MyEnum>());
}
