#include <assert.h>
#include <string.h>
#include <vector>
#include "script_system.h"
#include "engine/utils/logger.h"

static int Engine_Msg(lua_State* L)
{
	const char* msg = lua_tostring(L, 1);
	if (msg == nullptr)
	{
		return ScriptSystem::PushError(L, "engine.msg: missing string argument");
	}

	Msg("[LUA]: %s", msg);
	return 1;
}

static int Engine_LoadScript(lua_State* L)
{
	const char* filename = lua_tostring(L, 1);
	g_scriptSystem.DoFile(filename);
	return 1;
}

static int Engine_StartThread(lua_State* L)
{
	const char* threadname = lua_tostring(L, 1);
	const char* functionname = lua_tostring(L, 2);

	g_ScriptThreadManager.CreateThread(threadname, functionname);

	return 1;
}

static const struct luaL_Reg g_engineFunctions[] =
{
	{ "msg", Engine_Msg },
	{ "load_script", Engine_LoadScript },
	{ "start_thread", Engine_StartThread },
	{ 0, 0 }
};

ScriptSystem g_scriptSystem;

ScriptSystem::ScriptSystem() :
	m_State(nullptr)
{
}

ScriptSystem::~ScriptSystem()
{
}

static int EngineLib_require(lua_State* L)
{
	luaL_newlib(L, g_engineFunctions);
	return 1;
}

void ScriptSystem::Init()
{
	Msg("Script system initializing ...");

	m_State = luaL_newstate();
	luaL_openlibs(m_State);

	// register engine lib
	luaL_requiref(m_State, "engine", EngineLib_require, 1);
	lua_pop(m_State, 1);
}

void ScriptSystem::Shutdown()
{
	if (m_State)
	{
		lua_close(m_State);
		m_State = nullptr;
	}
}

lua_State* ScriptSystem::GetState()
{
	return m_State;
}

void ScriptSystem::DoFile(const char* filename)
{
	lua_State* L = m_State;

	int r = luaL_dofile(L, filename);
	if (r != LUA_OK)
	{
		Msg("Lua Error: %s", lua_tostring(L, lua_gettop(L)));
		lua_pop(L, lua_gettop(L));
	}
}

void ScriptSystem::DoString(const char* functionName)
{
	lua_State* L = m_State;

	int r = luaL_dostring(L, functionName);
	if (r != LUA_OK)
	{
		Msg("Lua Error: %s", lua_tostring(L, lua_gettop(L)));
		lua_pop(L, lua_gettop(L));
	}
}

int ScriptSystem::PushError(lua_State* L, const char* errorMsg)
{
	lua_pushstring(L, errorMsg);
	return lua_error(L);
}

void ScriptSystem::RegisterGlobalInt(const char* name, int value)
{
	lua_pushnumber(m_State, value);
	lua_setglobal(m_State, name);
}

ScriptThreadManager g_ScriptThreadManager;

ScriptThreadManager::ScriptThreadManager()
{
}

ScriptThreadManager::~ScriptThreadManager()
{
}

lua_State* ScriptThreadManager::CreateThread(const char* name, const char* functionName)
{
	assert(name);
	assert(functionName);
	assert(g_scriptSystem.GetState() && "Couldn't create script thread while script system is not initialized");

	Msg("Start script thread %s with function %s", name, functionName);

	lua_State* L = g_scriptSystem.GetState();
	
	ScriptThreadInfo info = {};
	info.threadname = strdup(name); // DMan to Kirill : unfreed memory
	info.state = lua_newthread(L);
	m_Threads.push_back(info);

#if 0
	// get function from base state
	int functionId = 0;

	// push globals
	lua_pushglobaltable(L);

	// push nil to stack
	lua_pushnil(L);

	while (lua_next(L, -2) != 0)
	{
		const char* variableName = lua_tostring(L, -2);
		Msg("%s", variableName);
		if (strcmp(variableName, functionName) == 0)
		{
			if (!lua_isfunction(L, -2))
			{
				FATAL("Lua: %s is not a function!", functionName);
			}
			else
			{
				// execute it from thread

			}
		}

		lua_pop(L, 1);
	}

	// pop globals
	lua_pop(L, 1);
#else
	char funcBuf[256];
	sprintf(funcBuf, "%s()", functionName);
	luaL_dostring(info.state, funcBuf);
#endif

	//lua_resume(info.state, 0, 0, 0);
	lua_resetthread(info.state);

	return info.state;
}
