#pragma once

#include "lua_string.h"
#include "lua_env.h"
#include "lua_script.h"
#include "lua_subsystem.h"

namespace luna
{
	template <typename>	struct LuaClass;


	template<typename T> struct LuaStack
	{
		inline static T& get(lua_State* state, int idx)
		{
			luaL_argcheck(state, !LuaClass<T>::klassName.empty(), 1, (std::string("cpp class `") + typeid(T).name() + "` not export").c_str());
			T** t = (T**)luaL_checkudata(state, idx, LuaClass<T>::klassName.c_str());
			luaL_argcheck(state, t != NULL, 1, "invalid user data");
			luaL_argcheck(state, *t != NULL, 1, "invalid user data");
			return (**t);
		}

		inline static void put(lua_State* L, T* t)
		{
			lua_pushlightuserdata(L, t);
		}
	};

	template<>
	struct LuaStack<float>
	{
		inline static float get(lua_State* L, int idx)
		{
			if (lua_isnumber(L, idx) || lua_isstring(L, idx))
			{
				return float(lua_tonumber(L, idx));
			}
			else
			{
				luaL_checktype(L, idx, LUA_TNUMBER);
			}
			return 0;
		}

		inline static void put(lua_State* L, const float& t)
		{
			lua_pushnumber(L, t);
		}
	};

	template<>
	struct LuaStack<double>
	{
		inline static double get(lua_State* L, int idx)
		{
			if (lua_isnumber(L, idx) || lua_isstring(L, idx))
			{
				return double(lua_tonumber(L, idx));
			}
			else
			{
				luaL_checktype(L, idx, LUA_TNUMBER);
			}
			return 0;
		}

		inline static void put(lua_State* L, const double& t)
		{
			lua_pushnumber(L, t);
		}
	};

	template<>
	struct LuaStack<int>
	{
		inline static int get(lua_State* L, int idx)
		{
			if (lua_isnumber(L, idx) || lua_isstring(L, idx))
			{
				return int(lua_tointeger(L, idx));
			}
			else
			{
				luaL_checktype(L, idx, LUA_TNUMBER);
			}
			return 0;
		}

		inline static void put(lua_State* L, const int& t)
		{
			lua_pushinteger(L, t);
		}
	};

	template<>
	struct LuaStack<bool>
	{
		inline static bool get(lua_State* L, int idx)
		{
			luaL_checktype(L, idx, LUA_TBOOLEAN);
			return lua_toboolean(L, idx) != 0;
		}

		inline static void put(lua_State* L, const bool& t)
		{
			lua_pushboolean(L, t);
		}
	};

	template<>
	struct LuaStack<const char*>
	{
		inline static const char* get(lua_State* L, int idx)
		{
			thread_local static char sss[256];

			switch (lua_type(L, idx))
			{
			case LUA_TBOOLEAN:
				return (lua_toboolean(L, idx) ? "true" : "false");
			case LUA_TNUMBER:
				snprintf((sss), sizeof(sss), LUA_NUMBER_FMT, (lua_tonumber(L, idx)));
				return sss;
			case LUA_TSTRING:
				return lua_tostring(L, idx);
			default:
				luaL_checktype(L, idx, LUA_TSTRING);
				break;
			}
			return "";
		}

		inline static void put(lua_State* L, const char* s)
		{
			lua_pushstring(L, s);
		}
	};



	template<>
	struct LuaStack<char*>
	{
		inline static char* get(lua_State* L, int idx)
		{
			return const_cast<char*>(LuaStack<const char*>::get(L, idx));
		}

		inline static void put(lua_State* L, const char* s)
		{
			LuaStack<const char*>::put(L, s);
		}
	};


	template<>
	struct LuaStack<std::string>
	{
		inline static std::string get(lua_State* L, int idx)
		{
			return LuaStack<const char*>::get(L, idx);
		}

		inline static void put(lua_State* L, const std::string& s)
		{
			LuaStack<const char*>::put(L, s.c_str());
		}
	};


	template <typename T>
	inline void LuaStackReturn(lua_State* L, T t)
	{
		lua_settop(L, 0);
		LuaStack<T>::put(L, t);
	}
}