#include "LuaState.hpp"
#include <OgreResourceGroupManager.h>
#include <OgreLogManager.h>
#include <OgreDataStream.h>
#include <iostream>

extern "C" {
int luaopen_lpeg(lua_State *L);
}

namespace editScene
{

// ---------------------------------------------------------------------------
// Custom library loader: loads .lua files from OGRE resource groups
// ---------------------------------------------------------------------------

int LuaState::luaLibraryLoader(lua_State *L)
{
	if (!lua_isstring(L, 1)) {
		luaL_error(
			L,
			"luaLibraryLoader: Expected string for first parameter");
	}

	std::string libraryFile = lua_tostring(L, 1);

	// Translate '.' to '/' for OGRE resource path compatibility
	while (libraryFile.find('.') != std::string::npos)
		libraryFile.replace(libraryFile.find('.'), 1, "/");

	libraryFile += ".lua";

	Ogre::DataStreamPtr stream =
		Ogre::ResourceGroupManager::getSingleton().openResource(
			libraryFile, "LuaScripts");
	Ogre::String script = stream->getAsString();
	if (luaL_loadbuffer(L, script.c_str(), script.length(),
			    libraryFile.c_str())) {
		luaL_error(
			L,
			"Error loading library '%s' from resource archive.\n%s",
			libraryFile.c_str(), lua_tostring(L, -1));
	}
	return 1;
}

// ---------------------------------------------------------------------------
// Install the custom loader into package.searchers
// ---------------------------------------------------------------------------

void LuaState::installLibraryLoader()
{
	lua_getglobal(L, "table");
	lua_getfield(L, -1, "insert");
	lua_remove(L, -2); // table
	lua_getglobal(L, "package");
	lua_getfield(L, -1, "searchers");
	lua_remove(L, -2); // package
	lua_pushnumber(L, 1); // insert at position 1 (highest priority)
	lua_pushcfunction(L, luaLibraryLoader);
	if (lua_pcall(L, 3, 0, 0))
		Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1);
}

// ---------------------------------------------------------------------------
// Constructor: create Lua state and open standard libraries
// ---------------------------------------------------------------------------

LuaState::LuaState()
	: L(luaL_newstate())
{
	luaopen_base(L);
	luaopen_table(L);
	luaopen_package(L);
	luaL_requiref(L, "table", luaopen_table, 1);
	lua_pop(L, 1);
	luaL_requiref(L, "math", luaopen_math, 1);
	lua_pop(L, 1);
	luaL_requiref(L, "package", luaopen_package, 1);
	lua_pop(L, 1);
	luaL_requiref(L, "string", luaopen_string, 1);
	lua_pop(L, 1);
	luaL_requiref(L, "io", luaopen_io, 1);
	lua_pop(L, 1);
	luaL_requiref(L, "lpeg", luaopen_lpeg, 1);
	lua_pop(L, 1);

	installLibraryLoader();
	lua_pop(L, 1);
}

// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------

LuaState::~LuaState()
{
	lua_close(L);
}

// ---------------------------------------------------------------------------
// Handler registration
// ---------------------------------------------------------------------------

int LuaState::setupHandler()
{
	luaL_checktype(L, 1, LUA_TFUNCTION);
	lua_pushvalue(L, 1);
	int ref = luaL_ref(L, LUA_REGISTRYINDEX);
	setupHandlers.push_back(ref);
	return 0;
}

// ---------------------------------------------------------------------------
// Call all handlers with an event name (no entities)
// ---------------------------------------------------------------------------

int LuaState::callHandler(const Ogre::String &event)
{
	for (int ref : setupHandlers) {
		lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
		lua_pushstring(L, event.c_str());
		lua_pushinteger(L, -1);
		lua_pushinteger(L, -1);
		if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
			Ogre::LogManager::getSingleton().stream()
				<< lua_tostring(L, -1);
			OgreAssert(false, "Lua error");
		}
	}
	return 0;
}

// ---------------------------------------------------------------------------
// Call all handlers with an event name and two entities
// ---------------------------------------------------------------------------

int LuaState::callHandler(const Ogre::String &event, flecs::entity e1,
			  flecs::entity e2)
{
	// Entity IDs are mapped through the global idmap (see LuaEntityApi.cpp)
	extern int luaEntityToId(flecs::entity e);
	extern flecs::entity luaIdToEntity(int id);

	for (int ref : setupHandlers) {
		lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
		lua_pushstring(L, event.c_str());
		lua_pushinteger(L, luaEntityToId(e1));
		lua_pushinteger(L, luaEntityToId(e2));
		if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
			Ogre::LogManager::getSingleton().stream()
				<< lua_tostring(L, -1);
			OgreAssert(false, "Lua error");
		}
	}
	return 0;
}

// ---------------------------------------------------------------------------
// Late setup: load data.lua and run initialisation
// ---------------------------------------------------------------------------

void LuaState::lateSetup()
{
	Ogre::DataStreamPtr stream =
		Ogre::ResourceGroupManager::getSingleton().openResource(
			"data2.lua", "LuaScripts");
	std::cout << "stream: " << stream->getAsString() << "\n";
	if (luaL_dostring(L, stream->getAsString().c_str()) != LUA_OK) {
		std::cout << "error: " << lua_tostring(L, -1) << "\n";
		OgreAssert(false, "Script failure");
	}

	const char *lua_code = "\n\
		function stuff()\n\
			return 4\n\
		end\n\
		x = stuff()\n\
	";
	luaL_dostring(L, lua_code);
	lua_getglobal(L, "x");
	int x = lua_tonumber(L, 1);
	std::cout << "lua: " << x << "\n";
}

} // namespace editScene
