// https://github.com/vinniefalco/LuaBridge
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2007, Nathan Reed
// SPDX-License-Identifier: MIT

// A set of tests of different types' communication with Lua

#include "TestBase.h"

#include <gtest/gtest.h>

#include <cstring>
#include <iostream>
#include <memory>
#include <string>

void printValue(lua_State* L, int index)
{
    int type = lua_type(L, index);
    switch (type)
    {
    case LUA_TBOOLEAN:
        std::cerr << std::boolalpha << (lua_toboolean(L, index) != 0);
        break;
    case LUA_TSTRING:
        std::cerr << lua_tostring(L, index);
        break;
    case LUA_TNUMBER:
        std::cerr << lua_tonumber(L, index);
        break;
    case LUA_TTABLE:
    case LUA_TTHREAD:
    case LUA_TFUNCTION:
        std::cerr << lua_topointer(L, index);
        break;
    }
    std::cerr << ": " << lua_typename(L, type) << " (" << type << ")" << std::endl;
}

struct LuaBridgeTest : TestBase
{
};

template<class T>
T identityCFunction(T value)
{
    return value;
}

TEST_F(LuaBridgeTest, CFunction)
{
    luabridge::getGlobalNamespace(L)
        .addFunction("boolFn", &identityCFunction<bool>)
        .addFunction("ucharFn", &identityCFunction<unsigned char>)
        .addFunction("shortFn", &identityCFunction<short>)
        .addFunction("ushortFn", &identityCFunction<unsigned short>)
        .addFunction("intFn", &identityCFunction<int>)
        .addFunction("uintFn", &identityCFunction<unsigned int>)
        .addFunction("longFn", &identityCFunction<long>)
        .addFunction("ulongFn", &identityCFunction<unsigned long>)
        .addFunction("longlongFn", &identityCFunction<long long>)
        .addFunction("ulonglongFn", &identityCFunction<unsigned long long>)
        .addFunction("floatFn", &identityCFunction<float>)
        .addFunction("doubleFn", &identityCFunction<double>)
        .addFunction("charFn", &identityCFunction<char>)
        .addFunction("cstringFn", &identityCFunction<const char*>)
        .addFunction("stringFn", &identityCFunction<std::string>);

    {
        runLua("result = ucharFn (255)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(255u, result<unsigned char>());
    }

    {
        runLua("result = boolFn (false)");
        ASSERT_EQ(true, result().isBool());
        ASSERT_EQ(false, result<bool>());
    }
    {
        runLua("result = boolFn (true)");
        ASSERT_EQ(true, result().isBool());
        ASSERT_EQ(true, result<bool>());
    }

    {
        runLua("result = shortFn (-32768)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(-32768, result<int>());
    }

    {
        runLua("result = ushortFn (32767)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(32767u, result<unsigned int>());
    }
    {
        runLua("result = intFn (-500)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(-500, result<int>());
    }

    {
        runLua("result = uintFn (42)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(42u, result<unsigned int>());
    }

    {
        runLua("result = longFn (-8000)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(-8000, result<long>());
    }

    {
        runLua("result = ulongFn (9000)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(9000u, result<unsigned long>());
    }

    {
        runLua("result = longlongFn (-8000)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(-8000, result<long long>());
    }

    {
        runLua("result = ulonglongFn (9000)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_EQ(9000u, result<unsigned long long>());
    }

    {
        runLua("result = floatFn (3.14)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_FLOAT_EQ(3.14f, result<float>());
    }

    {
        runLua("result = doubleFn (-12.3)");
        ASSERT_EQ(true, result().isNumber());
        ASSERT_DOUBLE_EQ(-12.3, result<double>());
    }

    {
        runLua("result = charFn ('a')");
        ASSERT_EQ(true, result().isString());
        ASSERT_EQ('a', result<char>());
    }

    {
        runLua("result = cstringFn ('abc')");
        ASSERT_EQ(true, result().isString());
        ASSERT_STREQ("abc", result<const char*>());
    }

    {
        runLua("result = stringFn ('lua')");
        ASSERT_EQ(true, result().isString());
        ASSERT_EQ("lua", result<std::string>());
    }
}

template<class T>
struct TestClass
{
    TestClass(T data) : data(data), constData(data) {}

    T getValue() { return data; }
    T* getPtr() { return &data; }
    T const* getConstPtr() { return &data; }
    T& getRef() { return data; }
    T const& getConstRef() { return data; }
    T getValueConst() const { return data; }
    T* getPtrConst() const { return &data; }
    T const* getConstPtrConst() const { return &data; }
    T& getRefConst() const { return data; }
    T const& getConstRefConst() const { return data; }

    mutable T data;
    mutable T constData;
};

TEST_F(LuaBridgeTest, ClassFunction)
{
    typedef TestClass<int> Inner;
    typedef TestClass<Inner> Outer;

    luabridge::getGlobalNamespace(L)
        .beginClass<Inner>("Inner")
        .addConstructor<void (*)(int)>()
        .addData("data", &Inner::data)
        .endClass()
        .beginClass<Outer>("Outer")
        .addConstructor<void (*)(Inner)>()
        .addFunction("getValue", &Outer::getValue)
        .addFunction("getPtr", &Outer::getPtr)
        .addFunction("getConstPtr", &Outer::getConstPtr)
        .addFunction("getRef", &Outer::getRef)
        .addFunction("getConstRef", &Outer::getConstRef)
        .addFunction("getValueConst", &Outer::getValueConst)
        .addFunction("getPtrConst", &Outer::getPtrConst)
        .addFunction("getConstPtrConst", &Outer::getConstPtrConst)
        .addFunction("getRefConst", &Outer::getRefConst)
        .addFunction("getConstRefConst", &Outer::getConstRefConst)
        .endClass();

    Outer outer(Inner(0));
    luabridge::setGlobal(L, &outer, "outer");

    outer.data.data = 0;
    runLua("outer:getValue ().data = 1");
    ASSERT_EQ(0, outer.data.data);

    outer.data.data = 1;
    runLua("outer:getPtr ().data = 10");
    ASSERT_EQ(10, outer.data.data);

    outer.data.data = 2;
    ASSERT_THROW(runLua("outer:getConstPtr ().data = 20"), std::runtime_error);

    outer.data.data = 3;
    runLua("outer:getRef().data = 30");
    ASSERT_EQ(30, outer.data.data);

    outer.data.data = 4;
    ASSERT_THROW(runLua("outer:getConstPtr ().data = 40"), std::runtime_error);

    outer.data.data = 5;
    runLua("outer:getValueConst ().data = 50");
    ASSERT_EQ(5, outer.data.data);

    outer.data.data = 6;
    runLua("outer:getPtrConst ().data = 60");
    ASSERT_EQ(60, outer.data.data);

    outer.data.data = 7;
    ASSERT_THROW(runLua("outer:getConstPtr ().data = 70"), std::runtime_error);

    outer.data.data = 8;
    runLua("outer:getRef().data = 80");
    ASSERT_EQ(80, outer.data.data);

    outer.data.data = 9;
    ASSERT_THROW(runLua("outer:getConstPtr ().data = 90"), std::runtime_error);
}
