/*
 * LuaCallWrapper.h, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */

#pragma once

#include "api/Registry.h"
#include "LuaStack.h"
#include <type_traits>

VCMI_LIB_NAMESPACE_BEGIN

namespace scripting
{

namespace detail
{

template<int ...>
struct Seq {};

template<int N, int ...S>
struct Gens : Gens<N-1, N-1, S...> {};

template<int ...S>
struct Gens<0, S...>
{
	using type = Seq<S...>;
};

template <typename R, typename ... Args>
class LuaArgumentsTuple
{
public:
	using TupleData = std::tuple<Args ...>;
	using Functor = R(*)(Args ...);

	TupleData args;
	Functor f;

	LuaArgumentsTuple(Functor _f)
		:f(_f),
		args()
	{
	}


	STRONG_INLINE int invoke(lua_State * L)
	{
		return callFunc(L, typename Gens<sizeof...(Args)>::type());
	}
private:
	template<int ...N>
	int callFunc(lua_State * L, Seq<N...>)
	{
		LuaStack S(L);

		bool ok[sizeof...(Args)] = {(S.tryGet(N+1, std::get<N>(args)))...};

		if(std::count(std::begin(ok), std::end(ok), false) > 0)
			return S.retVoid();


		R ret = f(std::get<N>(args) ...);
		S.clear();
		S.push(ret);
		return 1;
	}
};


class LuaFunctionInvoker
{
public:
	template<typename R, typename ... Args>
	static STRONG_INLINE int invoke(lua_State * L, R(*f)(Args ...))
	{
		LuaArgumentsTuple<R, Args ...> args(f);
		return args.invoke(L);
	}
};

}

template <typename F, F f>
class LuaFunctionWrapper
{
public:
	static int invoke(lua_State * L)
	{
		return detail::LuaFunctionInvoker::invoke(L, f);
	}
};


//TODO: this should be the only one wrapper type
//
template <typename U, typename M, M m>
class LuaMethodWrapper
{

};

template <typename U, typename T, typename R, R(T:: * method)()const>
class LuaMethodWrapper <U, R(T:: *)()const, method>
{
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		const U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		static auto functor = std::mem_fn(method);

		S.clear();
		S.push(functor(obj));
		return S.retPushed();
	}
};

template <typename U, typename T, typename R, R(T:: * method)()>
class LuaMethodWrapper <U, R(T:: *)(), method>
{
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		static auto functor = std::mem_fn(method);

		S.clear();
		S.push(functor(obj));
		return S.retPushed();
	}
};

template <typename U, typename T, void(T:: * method)()const>
class LuaMethodWrapper <U, void(T:: *)()const, method>
{
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		const U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		functor(obj);
		return 0;
	}
};

template <typename U, typename T, void(T:: * method)()>
class LuaMethodWrapper <U, void(T:: *)(), method>
{
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		functor(obj);
		return 0;
	}
};

template <typename U, typename T, typename R, typename P1, R(T:: * method)(P1)const>
class LuaMethodWrapper <U, R(T:: *)(P1)const, method>
{
	using PM1 = std::remove_cv_t<std::remove_reference_t<P1>>;
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		const U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		PM1 p1;
		if(!S.tryGet(2, p1))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		S.push(functor(obj, p1));
		return S.retPushed();
	}
};

template <typename U, typename T, typename R, typename P1, R(T:: * method)(P1)>
class LuaMethodWrapper <U, R(T:: *)(P1), method>
{
	using PM1 = std::remove_cv_t<std::remove_reference_t<P1>>;
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		PM1 p1;
		if(!S.tryGet(2, p1))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		S.push(functor(obj, p1));
		return S.retPushed();
	}
};

template <typename U, typename T, typename P1, void(T:: * method)(P1)const>
class LuaMethodWrapper <U, void(T:: *)(P1)const, method>
{
	using PM1 = std::remove_cv_t<std::remove_reference_t<P1>>;
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		const U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		PM1 p1;
		if(!S.tryGet(2, p1))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		functor(obj, p1);
		return 0;
	}
};

template <typename U, typename T, typename P1, void(T:: * method)(P1)>
class LuaMethodWrapper <U, void(T:: *)(P1), method>
{
	using PM1 = std::remove_cv_t<std::remove_reference_t<P1>>;
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		U * obj = nullptr;

		if(!S.tryGet(1,obj))
			return S.retVoid();

		PM1 p1;
		if(!S.tryGet(2, p1))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		functor(obj, p1);
		return 0;
	}
};

template <typename U, typename T, typename R, typename P1, typename P2, R(T:: * method)(P1, P2)const>
class LuaMethodWrapper <U, R(T:: *)(P1, P2)const, method>
{
	using PM1 = std::remove_cv_t<std::remove_reference_t<P1>>;
	using PM2 = std::remove_cv_t<std::remove_reference_t<P2>>;
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		const U * obj = nullptr;

		if(!S.tryGet(1, obj))
			return S.retVoid();

		PM1 p1;
		if(!S.tryGet(2, p1))
			return S.retVoid();

		PM2 p2;
		if(!S.tryGet(3, p2))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		S.push(functor(obj, p1, p2));
		return S.retPushed();
	}
};

template <typename U, typename T, typename P1, typename P2, void(T:: * method)(P1, P2)const>
class LuaMethodWrapper <U, void(T:: *)(P1, P2)const, method>
{
	using PM1 = std::remove_cv_t<std::remove_reference_t<P1>>;
	using PM2 = std::remove_cv_t<std::remove_reference_t<P2>>;
public:
	static int invoke(lua_State * L)
	{
		LuaStack S(L);
		const U * obj = nullptr;

		if(!S.tryGet(1, obj))
			return S.retVoid();

		PM1 p1;
		if(!S.tryGet(2, p1))
			return S.retVoid();

		PM2 p2;
		if(!S.tryGet(3, p2))
			return S.retVoid();

		static auto functor = std::mem_fn(method);
		S.clear();
		functor(obj, p1, p2);
		return 0;
	}
};

}

VCMI_LIB_NAMESPACE_END