//*****************************************************************************
//  luax.h
//
//  A wrapper class for Lua.  Provides easy way of calling C functions from
//  Lua and calling Lua functions from C.
//
//	File maintained by:
//		Young Tae Son (yson@ea.com)
//
//	(c) 2005 Electronic Arts Inc.
//*****************************************************************************

#pragma once

#include "luax/impl/luareturn.h"
#include "luax/impl/luatype.h"
#include "luax/impl/luaglobal.h"
#include "luax/impl/luainit.h"
#include "luax/impl/luaexport.h"
#include "luax/impl/luaerror.h"
#include "luax/luatable.h"

#include <EASTL/fixed_list.h>
#include <EASTL/unique_ptr.h>
#include <eathread/eathread_futex.h>

namespace Luax
{

class LuaAllocator
{
public:
	virtual ~LuaAllocator() {}
	virtual void garbageCollect() = 0;
	virtual void setFastMode() = 0;
};

struct LuaState
{
	LuaState(lua_State* L, eastl::unique_ptr<LuaAllocator> alloc)
		: mLuaState(L)
		, mAllocator(eastl::move(alloc))
	{
	}

	operator lua_State* () const { return mLuaState; }

	lua_State*						mLuaState = nullptr;
	eastl::unique_ptr<LuaAllocator> mAllocator;
	eastl::fixed_string<char, 256>	mErrorMessage;
};

void SetLuaTableSetup(lua_State* lstate, const char* name);
template<typename T1, typename T2>
inline void SetLuaTable(lua_State* lstate, const char* name, T1 key, T2 val)
{
	SetLuaTableSetup(lstate, name);
	PushReturnToLua(lstate, key);

	PushReturnToLua(lstate, val);
	if (lua_istable(lstate, -3))
	{
		lua_settable(lstate, -3);
	}
	lua_settop(lstate, 0);
}

template <typename T, typename... Args>
inline void Push(lua_State* state, T type, Args... args)
{
	PushReturnToLua(state, type);
	EA_DISABLE_CLANG_WARNING(-Wc++17-extensions)
	if constexpr (sizeof...(args) > 0)
	EA_RESTORE_CLANG_WARNING()
	{
		Push(state, args...);
	}
}

class Script
{
public:
	using LuaAlloactorCreateFunc = eastl::function<eastl::unique_ptr<LuaAllocator>()>;

    explicit Script(lua_Alloc luaAllocatorCallback, LuaAlloactorCreateFunc luaAllocatorCreateFunc);
    ~Script();

	void PrepareNextState();
	void ClearCurrentState();
	
	bool IsReady() const { return !mLuaState.empty(); }

	LUAX_API static bool LoadBuffer(lua_State* l, const char* buffer, size_t bufflen, const char* chunkname);
	LUAX_API static bool LoadFile(lua_State* l, const char* filename);

	template <typename... Args>
    static LuaReturn CallFunction(lua_State* l, const char* fn, Args... args);

	lua_State* GetLuaState() const { return mLuaState.front(); }

	void GarbageCollect();

	void EnableFastAllocator()
	{
		if (mLuaState.empty())
		{
			return;
		}

		mEnableGarbageCollection = true;
		mLuaState.front().mAllocator->setFastMode();
	}

	static const char* GlobalName() { return "Luax_Script"; }

private:

	lua_State* InitState(lua_Alloc alloc, void* user_data) const;
    void RegisterExports(lua_State* state) const;
	void CallInitFuncs(lua_State* state) const;
	LUAX_API static bool RunLuaCode(lua_State* l, int nargs, int nresults);

	static constexpr eastl_size_t	kMaxBufferedStates = 2;
	using LuaStateBuffer			= eastl::fixed_list<LuaState, kMaxBufferedStates>;

	LuaStateBuffer					mLuaState;

	lua_Alloc						mLuaAllocatorCallback = nullptr;
	LuaAlloactorCreateFunc			mLuaAllocatorCreateFunc;
	bool							mEnableGarbageCollection = false;
};

template <typename... Args>
inline LuaReturn Script::CallFunction(lua_State* l, const char* fn, Args... args)
{
	lua_settop(l, 0);
	lua_getglobal(l, fn);
	EA_DISABLE_CLANG_WARNING(-Wc++17-extensions)
	constexpr int numArgs = sizeof...(args);
	if constexpr (numArgs > 0)
	EA_RESTORE_CLANG_WARNING()
	{
		Push(l, args...);
	}
	if (RunLuaCode(l, numArgs, -1))
	{
		return LuaReturn{ l };
	}
	return LuaReturn{ nullptr }; // INVALID
}

}
