//*****************************************************************************
//  luaexport.h
//
//  Registering/Exporting C functions to Lua happens here.
//    
//	File maintained by:
//		Young Tae Son (yson@ea.com)
//
//	(c) 2005 Electronic Arts Inc.
//*****************************************************************************


#pragma once

#include "luax/config.h"
#include "luax/impl/luatype.h"
#include <EASTL/type_traits.h>

namespace Luax
{

template<typename T>
inline T lua_type_getter(lua_State* lstate, int& index)
{
	if constexpr (eastl::is_same_v<T, lua_State*>)
	{
		return lstate;
	}
	else
	{
		return GetFromLuaType<T>(lstate, ++index);
	}
}


//> **********************************************************************************************
//> For Static Function callbacks
//> **********************************************************************************************

struct call_func_helper
{
	template <typename R, typename... Args>
	inline call_func_helper(lua_State* lstate, R(*fn)(Args...), Args&&... args)
	{
		if constexpr (eastl::is_same_v<R, void>)
		{
			fn(eastl::forward<Args>(args)...);
		}
		else
		{
			PushReturnToLua(lstate, fn(eastl::forward<Args>(args)...));
		}
	}
};

template <typename R, typename... Args>
inline int CallLuaExport(lua_State* lstate, R(*fn)(Args...))
{
	int argIndex = 0;
	call_func_helper{ lstate, fn, lua_type_getter<Args>(lstate, argIndex)... };
	return eastl::is_same_v<R, void> ? 0 : 1;
}


//> **********************************************************************************************
//> For Member Function callbacks
//> **********************************************************************************************

struct call_member_func_helper
{
	template <typename CL, typename R, typename... Args>
	inline call_member_func_helper(lua_State* lstate, CL& obj, R(CL::*fn)(Args...), Args&&... args)
	{
		if constexpr (eastl::is_same_v<R, void>)
		{
			(obj.*(fn))(eastl::forward<Args>(args)...);
		}
		else
		{
			PushReturnToLua(lstate, (obj.*(fn))(eastl::forward<Args>(args)...));
		}
	}
};

template <typename CL, typename R, typename... Args>
inline int CallLuaExportMember(lua_State* lstate, const char* classname, R(CL::*fn)(Args...))
{
	lua_getglobal(lstate, classname);
	CL* obj = luax_argcheck<CL*>(lstate, -1) ? GetFromLuaType<CL*>(lstate, -1) : nullptr;
	if (!obj)
	{
		return 0;
	}

	int argIndex = 0;
	call_member_func_helper{ lstate, *obj, fn, lua_type_getter<Args>(lstate, argIndex)... };
	return eastl::is_same_v<R, void> ? 0 : 1;
}

// ****************************************************************************************************

struct LuaExportedFn
{
	LUAX_API LuaExportedFn(lua_CFunction cb, const char* name);

    lua_CFunction mCallback;
    const char* mName;
    LuaExportedFn* mNext;
};

}

#define LUA_EXPORT(fn) \
int LuaCallback##fn(lua_State* lstate) \
{ \
    return Luax::CallLuaExport(lstate, fn); \
} \
Luax::LuaExportedFn fn##_functor(LuaCallback##fn, #fn)

#define LUA_EXPORT_NAME(fn, name) \
int LuaCallback##name(lua_State* lstate) \
{ \
    return Luax::CallLuaExport(lstate, fn); \
} \
Luax::LuaExportedFn name##_functor(LuaCallback##name, #name)

#define LUA_EXPORT_MEMBER(cl, fn, var) \
int LuaCallback##cl##fn(lua_State* lstate) \
{ \
    return Luax::CallLuaExportMember(lstate, var, &cl::fn); \
} \
Luax::LuaExportedFn cl##fn##_functor(LuaCallback##cl##fn, #fn)

