//*****************************************************************************
//  luax.cpp
//
//  Implementation of Luax::Script class.
//    
//	File maintained by:
//		Young Tae Son (yson@ea.com)
//
//	(c) 2005 Electronic Arts Inc.
//*****************************************************************************
#include "luax/config.h"
#include "luax/luax.h"
#include "luax/impl/luaerror.h"
#include "lualib.h"
#include "lgc.h"

#include <EASTL/fixed_string.h>

static const luaL_Reg gLuaLibs[] = {
	{"_G", luaopen_base},
	//{LUA_LOADLIBNAME, luaopen_package},
	{LUA_TABLIBNAME, luaopen_table},
	{LUA_IOLIBNAME, luaopen_io},
	//{LUA_OSLIBNAME, luaopen_os},
	{LUA_STRLIBNAME, luaopen_string},
	{LUA_MATHLIBNAME, luaopen_math},
	{LUA_COLIBNAME, luaopen_coroutine},
	{LUA_DBLIBNAME, luaopen_debug},
	{NULL, NULL}
}; 

namespace Luax
{
	LuaInitFn*		gLuaInitFnList = nullptr;
    LuaExportedFn*	gLuaExportFnList = nullptr;

	LuaExportedFn::LuaExportedFn(lua_CFunction cb, const char* name) 
		: mCallback(cb)
		, mName(name)
	{
		mNext = gLuaExportFnList;
		gLuaExportFnList = this;
	}
    
    Script::Script(lua_Alloc luaAllocatorCallback, LuaAlloactorCreateFunc luaAllocatorCreateFunc)
		: mLuaAllocatorCallback(luaAllocatorCallback)
		, mLuaAllocatorCreateFunc(luaAllocatorCreateFunc)
    {		
		for (eastl_size_t i = 0; i < mLuaState.max_size(); ++i)
		{
			PrepareNextState();
		}
	}

    Script::~Script()
    {
		for (lua_State* state : mLuaState)
		{
			lua_close(state);
		}
    }

	lua_State* Script::InitState(lua_Alloc alloc, void* user_data) const
	{
		lua_State* state = lua_newstate(alloc, user_data);

        // Open lua standard libs
		//luaL_openlibs(mLuaState);
		// Open our own subset of libs to load instead of all in luaL_openLibs()
  		suspendnurserygc(G(state));

		const luaL_Reg *lib = gLuaLibs;
		for (; lib->func; lib++)
		{
			luaL_requiref(state, lib->name, lib->func, 1);
			lua_pop(state, 1);  /* remove lib */
		}

		resumenurserygc(G(state));

        lua_settop(state, 0);

        RegisterExports(state);

		CallInitFuncs(state);

		return state;
	}

	void Script::PrepareNextState()
	{
		eastl::unique_ptr<LuaAllocator> allocator = mLuaAllocatorCreateFunc();
		lua_State* state = InitState(mLuaAllocatorCallback, allocator.get());
		mLuaState.emplace_back(state, eastl::move(allocator));
	}

	void Script::ClearCurrentState()
	{
		mEnableGarbageCollection = false;

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

		lua_close(mLuaState.front());
		mLuaState.pop_front();
	}

    bool Script::LoadFile(lua_State* l, const char* filename)
    {
		lua_settop(l, 0);
		if (luaL_loadfile(l, filename))
		{
			Error::instance().handle(l);
			return false;
		}
        return RunLuaCode(l, 0, 0);
    }

    bool Script::LoadBuffer(lua_State* l, const char* buffer, size_t bufflen, const char* chunkname)
    {
		lua_settop(l, 0);

		if (luaL_loadbuffer(l, buffer, bufflen, chunkname ))
		{
			Error::instance().handle(l);
			return false;
		}
        return RunLuaCode(l, 0, 0);
    }

    void Script::RegisterExports(lua_State* state) const
    {
        LuaExportedFn* curr = gLuaExportFnList;
        while( curr )
        {
			lua_register(state, curr->mName, curr->mCallback);
            curr = curr->mNext;
        }
    }

	void Script::CallInitFuncs(lua_State* state) const
	{
		LuaInitFn* curr = gLuaInitFnList;
		while (curr)
		{
			(*curr)(state);
			curr = curr->mNext;
		}
	}

	// from lua.c
	static int traceback(lua_State *L) 
	{
	  if (!lua_isstring(L, 1))  /* 'message' not a string? */
		return 1;  /* keep it intact */
	  lua_pushglobaltable(L);
	  lua_getfield(L, -1, "debug");
	  lua_remove(L, -2);
	  if (!lua_istable(L, -1)) {
		lua_pop(L, 1);
		return 1;
	  }
	  lua_getfield(L, -1, "traceback");
	  if (!lua_isfunction(L, -1)) {
		lua_pop(L, 2);
		return 1;
	  }
	  lua_pushvalue(L, 1);  /* pass error message */
	  lua_pushinteger(L, 2);  /* skip this function and traceback */
	  lua_call(L, 2, 1);  /* call debug.traceback */
	  return 1;
	}

    // based on docall in lua.c
    bool Script::RunLuaCode(lua_State* l, int nargs, int nresults)
    {
        int base = lua_gettop(l) - nargs;  /* function index */
  		lua_pushcfunction(l, traceback);  /* push traceback function */
        lua_insert(l, base);  /* put it under chunk and args */
		bool error = lua_pcall(l, nargs, nresults, base) != 0;
		if (error)
		{
			Error::instance().handle(l);
		}
        lua_remove(l, base);  /* remove traceback function */
		return !error;
    }

	// force garbage collection
	void Script::GarbageCollect()
	{
		if (mLuaState.empty())
		{
			return;
		}

		if (mEnableGarbageCollection)
		{
			lua_gc(GetLuaState(), LUA_GCCOLLECT, 0);
			mLuaState.front().mAllocator->garbageCollect();
		}
	}


	// creates a table if it doesn't exist
	void SetLuaTableSetup(lua_State* lstate, const char* name)
	{		
		lua_getglobal(lstate, name);
		if (lua_isnil(lstate, -1))
		{
			// no such table? let's make one
			lua_newtable(lstate);
			lua_setglobal(lstate, name);
			lua_getglobal(lstate, name);
		}
		else if(!lua_istable(lstate, -1))
		{
			// calling luaL_error outside pcall will exit the program. Very dangerous.
			lua_assert(false);
			lua_settop(lstate, 0);
			return;
		}
	}
}
