#include "lua.hpp"
#include <OgreFileSystemLayer.h>
#include "GameData.h"
#include "Components.h"
#include "GUIModuleCommon.h"
#include "PhysicsModule.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
#include "CharacterManagerModule.h"
#include "VehicleManagerModule.h"
#include "BoatModule.h"
#include "EventTriggerModule.h"
#include "SlotsModule.h"
#include "PlayerActionModule.h"
#include "QuestModule.h"
#include "LuaData.h"
#include <tracy/Tracy.hpp>
extern "C" {
int luaopen_lpeg(lua_State *L);
}
struct FooPosition {
	Ogre::Vector3 value;
	Ogre::Vector3 &get()
	{
		return value;
	}
};
namespace ECS
{
struct LuaEcsEntity {
	int id;
	flecs::entity e;
};
struct idmap {
	std::unordered_map<int, flecs::entity> id2entity;
	std::unordered_map<flecs::entity_t, int> entity2id;
	int next_id;
	idmap()
		: id2entity({})
		, entity2id({})
		, next_id(0)
	{
	}
	int get_next_id()
	{
		next_id++;
		return next_id;
	}
	int add_entity(flecs::entity e)
	{
		if (entity2id.find(e.id()) != entity2id.end())
			return entity2id[e.id()];
		int id = get_next_id();
		id2entity[id] = e;
		entity2id[e.id()] = id;
		return id;
	}
	flecs::entity get_entity(int id)
	{
		OgreAssert(id2entity[id].is_valid(), "Invalid entity");
		return id2entity[id];
	}
};
struct idmap idmap;

int LuaData::setup_handler()
{
	luaL_checktype(L, 1, LUA_TFUNCTION);
	lua_pushvalue(L, 1);
	int ref = luaL_ref(L, LUA_REGISTRYINDEX);
	setup_handlers.push_back(ref);
	return 0;
}
int LuaData::call_handler(const Ogre::String &event)
{
	int i;
	for (i = 0; i < setup_handlers.size(); i++) {
		lua_rawgeti(L, LUA_REGISTRYINDEX, setup_handlers[i]);
		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;
}

int LuaData::call_handler(const Ogre::String &event, flecs::entity e,
			  flecs::entity o)
{
	int i;
	for (i = 0; i < setup_handlers.size(); i++) {
		lua_rawgeti(L, LUA_REGISTRYINDEX, setup_handlers[i]);
		lua_pushstring(L, event.c_str());
		lua_pushinteger(L, idmap.add_entity(e));
		lua_pushinteger(L, idmap.add_entity(o));
        if (lua_pcall(L, 3, 0, 0) != LUA_OK) {
			Ogre::LogManager::getSingleton().stream()
				<< lua_tostring(L, -1);
			OgreAssert(false, "Lua error");
		}
	}
	return 0;
}

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

	std::string libraryFile = lua_tostring(L, 1);
	std::cout << libraryFile << std::endl;
	// In order to be compatible with the normal Lua file loader,
	// translate '.' to the file system seperator character.
	// In this case (An ogre resource) '/'
	while (libraryFile.find('.') != std::string::npos)
		libraryFile.replace(libraryFile.find('.'), 1, "/");

	libraryFile += ".lua";
	std::cout << libraryFile << std::endl;
	Ogre::StringVectorPtr scripts =
		Ogre::ResourceGroupManager::getSingleton().listResourceNames(
			"LuaScripts", false);
	std::vector<Ogre::String> &strings = *scripts;
	for (i = 0; i < strings.size(); i++)
		std::cout << strings[i] << std::endl;

	if (0 && !Ogre::ResourceGroupManager::getSingleton()
			  .resourceExistsInAnyGroup(libraryFile)) {
		// Could not find the file.
		std::string errMessage = "\n  no file '" + libraryFile +
					 "' found in Ogre resource archives.";
		lua_pushstring(L, errMessage.c_str());
	} else {
		Ogre::DataStreamList streams =
			Ogre::ResourceGroupManager::getSingleton().openResources(
				"*.lua", "LuaScripts");
		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;
}

struct LuaChildEventTrigger {
	flecs::entity parent_e;
	Ogre::Vector3 position;
	float halfheight;
	float radius;
	Ogre::String event;
};

static void installLibraryLoader(lua_State *L)
{
	// Insert the c++ func 'luaLibraryLoader' into package.loaders.
	// Inserted at the start of the table in order to take precedence.
	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); // index where to insert into loaders table
	lua_pushcfunction(L, luaLibraryLoader);
	if (lua_pcall(L, 3, 0, 0))
		Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1);
}

LuaData::LuaData()
	: 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);
#if 0
	luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
	lua_pushcfunction(L, luaopen_lpeg);
	lua_setfield(L, -2, "lpeg");
	lua_pop(L, 1); // remove PRELOAD table
#endif
	installLibraryLoader(L);
	lua_pop(L, 1);

#if 0
	luaaa::LuaClass<Ogre::Vector3> luaVector3(L, "Vector3");
	luaVector3.ctor<float, float, float>();
	luaVector3.get("x", [](Ogre::Vector3 &v) -> float { return v.x; });
	luaVector3.set("x", [](Ogre::Vector3 &v, float value) -> void {
		v.x = value;
	});
	luaVector3.get("y", [](Ogre::Vector3 &v) -> float { return v.y; });
	luaVector3.set("y", [](Ogre::Vector3 &v, float value) -> void {
		v.y = value;
	});
	luaVector3.get("z", [](Ogre::Vector3 &v) -> float { return v.z; });
	luaVector3.set("z", [](Ogre::Vector3 &v, float value) -> void {
		v.z = value;
	});
	luaVector3.fun("squaredLength", &Ogre::Vector3::squaredLength);
	luaVector3.fun("length", &Ogre::Vector3::length);
	luaVector3.fun("squaredDistance", &Ogre::Vector3::squaredDistance);
	luaVector3.fun("distance", &Ogre::Vector3::distance);
#endif
#if 0
	luaaa::LuaModule luaECS(L, "_ecs");
	luaECS.fun("position", [](int id) -> FooPosition {
#if 0
		flecs::entity e = idmap.get_entity(id);
		if (e.has<CharacterBase>()) {
			const CharacterBase &cb = e.get<CharacterBase>();
			return cb.mBodyNode->_getDerivedPosition();
		} else if (e.has<BoatBase>()) {
			const BoatBase &boat = e.get<BoatBase>();
			return boat.mNode->_getDerivedPosition();
		} else if (e.has<EventTrigger>()) {
			const EventTrigger &trigger = e.get<EventTrigger>();
			return trigger.position;
		} else
			return Ogre::Vector3(0, 0, 0);
#endif
		return FooPosition();
	});
#endif

	lua_pushcfunction(L, [](lua_State *L) -> int {
		OgreAssert(false, "Crash function called");
		return 0;
	});
	lua_setglobal(L, "crash");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		luaL_checktype(L, 1, LUA_TFUNCTION);
		ECS::get<LuaBase>().mLua->setup_handler();
		return 0;
	});
	lua_setglobal(L, "setup_handler");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        luaL_checktype(L, 1, LUA_TSTRING);
        if (lua_type(L, 2) == LUA_TFUNCTION)
            luaL_checktype(L, 2, LUA_TFUNCTION);
        else
            luaL_checktype(L, 2, LUA_TTABLE);
        ECS::get_mut<PlayerActionModule>().setupLuaActionHandler(L);
        ECS::modified<PlayerActionModule>();
        return 0;
    });
    lua_setglobal(L, "setup_action_handler");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        luaL_checktype(L, 1, LUA_TSTRING);
        luaL_checktype(L, 2, LUA_TTABLE);
        ECS::get_mut<QuestModule>().addLuaQuest(L);
        ECS::modified<QuestModule>();
        return 0;
    });
    lua_setglobal(L, "add_quest");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        // FIXME
        return 0;
    });
    lua_setglobal(L, "setup_narration_handler");
    lua_pushcfunction(L, [](lua_State *L) -> int {
		int args = lua_gettop(L);
		if (args < 1)
			return 0;
		ECS::get_mut<GUI>().choices.clear();
		luaL_checktype(L, 1, LUA_TSTRING);
		if (args > 1) {
			luaL_checktype(L, 2, LUA_TTABLE);
			lua_pushnil(L); /* first key */
			while (lua_next(L, 2) != 0) {
				Ogre::String s = lua_tostring(L, -1);
				ECS::get_mut<GUI>().choices.push_back(s);
				lua_pop(L, 1); /* remove value but keep key */
			}
		}
		size_t len;
		Ogre::String message(luaL_tolstring(L, 1, &len));
		std::cout << "narrator message: " << message
			  << " length: " << message.length() << std::endl;
		if (message.length() == 0 && ECS::get_mut<GUI>().narrationBox) {
			ECS::get_mut<GUI>().enabled = false;
			ECS::get_mut<GUI>().grab = true;
			ECS::get_mut<GUI>().grabChanged = true;
			ECS::get_mut<GUI>().narrationText = message;
			ECS::get_mut<GUI>().narrationBox = false;
			ECS::modified<GUI>();
			std::cout << "narration ended\n";
		} else if (message.length() > 0) {
			ECS::get_mut<GUI>().enabled = true;
			ECS::get_mut<GUI>().grab = false;
			ECS::get_mut<GUI>().grabChanged = true;
			ECS::get_mut<GUI>().narrationText = message;
			ECS::get_mut<GUI>().narrationBox = true;
			ECS::modified<GUI>();
		}

		return 0;
	});
	lua_setglobal(L, "narrate");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		//		ECS::get_mut<GUI>().mainMenu = true;
		lua_pushinteger(L, ECS::get<GUI>().narration_answer);
		return 1;
	});
	lua_setglobal(L, "narration_get_answer");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		//		ECS::get_mut<GUI>().mainMenu = true;
		ECS::get_mut<GUI>().enabled = true;
		ECS::get_mut<GUI>().mainMenu = true;
		ECS::get_mut<GUI>().grab = false;
		ECS::get_mut<GUI>().grabChanged = true;
		ECS::modified<GUI>();
		return 0;
	});
	lua_setglobal(L, "main_menu");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		luaL_checktype(L, 1, LUA_TSTRING);
		luaL_checktype(L, 2, LUA_TNUMBER);
		luaL_checktype(L, 3, LUA_TNUMBER);
		luaL_checktype(L, 4, LUA_TNUMBER);
		luaL_checktype(L, 5, LUA_TNUMBER);
		Ogre::String what = lua_tostring(L, 1);
		if (what == "boat") {
			float yaw = lua_tonumber(L, 5);
			float x = lua_tonumber(L, 2);
			float y = lua_tonumber(L, 3);
			float z = lua_tonumber(L, 4);
			Ogre::Quaternion orientation(Ogre::Radian(yaw),
						     Ogre::Vector3(0, 1, 0));
			Ogre::Vector3 position(x, y, z);
			flecs::entity e =
				ECS::get_mut<VehicleManagerModule>()
					.createVehicleAtPosition(
						"boat", position, orientation);
			ECS::modified<VehicleManagerModule>();
			int ret = idmap.add_entity(e);
			lua_pushinteger(L, ret);
			std::cout << "boat created: " << ret << std::endl;
			return 1;
		} else if (what == "raft") {
			float yaw = lua_tonumber(L, 5);
			float x = lua_tonumber(L, 2);
			float y = lua_tonumber(L, 3);
			float z = lua_tonumber(L, 4);
			Ogre::Quaternion orientation(Ogre::Radian(yaw),
						     Ogre::Vector3(0, 1, 0));
			Ogre::Vector3 position(x, y, z);
			flecs::entity e =
				ECS::get_mut<VehicleManagerModule>()
					.createVehicleAtPosition(
						"raft", position, orientation);
			ECS::modified<VehicleManagerModule>();
			int ret = idmap.add_entity(e);
			lua_pushinteger(L, ret);
			std::cout << "raft created: " << ret << std::endl;
			return 1;
		}
		lua_pushinteger(L, -1);
		return 1;
	});
	lua_setglobal(L, "ecs_vehicle_set");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		OgreAssert(lua_gettop(L) == 6, "bad parameters");
		luaL_checktype(L, 1, LUA_TSTRING); // event
		luaL_checktype(L, 2, LUA_TNUMBER); // x
		luaL_checktype(L, 3, LUA_TNUMBER); // y
		luaL_checktype(L, 4, LUA_TNUMBER); // z
		luaL_checktype(L, 5, LUA_TNUMBER); // halfh
		luaL_checktype(L, 6, LUA_TNUMBER); // radius
		flecs::entity e = ECS::get().entity();
		Ogre::String event = lua_tostring(L, 1);
		float x = lua_tonumber(L, 2);
		float y = lua_tonumber(L, 3);
		float z = lua_tonumber(L, 4);
		float h = lua_tonumber(L, 5);
		float r = lua_tonumber(L, 6);
		Ogre::Vector3 position(x, y, z);
		e.set<EventTrigger>({ nullptr, position, h, r, event });
		lua_pushinteger(L, idmap.add_entity(e));
		return 1;
	});
	lua_setglobal(L, "ecs_character_trigger");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		luaL_checktype(L, 1, LUA_TNUMBER); // object
		int object = lua_tointeger(L, 1);
		flecs::entity object_e = idmap.get_entity(object);
		flecs::entity parent_e = object_e.parent();
		lua_pushinteger(L, idmap.add_entity(parent_e));
		return 1;
	});
	lua_setglobal(L, "ecs_parent");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		OgreAssert(lua_gettop(L) == 7, "bad parameters");
		luaL_checktype(L, 1, LUA_TNUMBER); // parent
		luaL_checktype(L, 2, LUA_TSTRING); // event
		luaL_checktype(L, 3, LUA_TNUMBER); // x
		luaL_checktype(L, 4, LUA_TNUMBER); // y
		luaL_checktype(L, 5, LUA_TNUMBER); // z
		luaL_checktype(L, 6, LUA_TNUMBER); // halfh
		luaL_checktype(L, 7, LUA_TNUMBER); // radius
		int parent = lua_tointeger(L, 1);
		std::cout << "parent: " << parent << std::endl;
		flecs::entity parent_e = idmap.get_entity(parent);
		Ogre::String event = lua_tostring(L, 2);
		float x = lua_tonumber(L, 3);
		float y = lua_tonumber(L, 4);
		float z = lua_tonumber(L, 5);
		float h = lua_tonumber(L, 6);
		float r = lua_tonumber(L, 7);
		Ogre::Vector3 position(x, y, z);
		flecs::entity e = ECS::get().entity().child_of(parent_e);
		e.set<LuaChildEventTrigger>(
			{ parent_e, position, h, r, event });
		lua_pushinteger(L, idmap.add_entity(e));
		return 1;
	});
	lua_setglobal(L, "ecs_child_character_trigger");
	lua_pushcfunction(L, [](lua_State *L) -> int {
		luaL_checktype(L, 1, LUA_TNUMBER); // trigger
		luaL_checktype(L, 2, LUA_TNUMBER); // object
		int trigger = lua_tointeger(L, 1);
		int object = lua_tointeger(L, 2);
		flecs::entity trigger_e = idmap.get_entity(trigger);
		flecs::entity object_e = idmap.get_entity(object);
		Ogre::SceneNode *target_node = nullptr;
		Ogre::Any targetAny = trigger_e.get<EventTrigger>()
					      .node->getUserObjectBindings()
					      .getUserAny("target");
		OgreAssert(targetAny.has_value(), "need target");
		target_node = Ogre::any_cast<Ogre::SceneNode *>(targetAny);
		Ogre::Vector3 position = target_node->_getDerivedPosition();
		Ogre::Quaternion orientation =
			target_node->_getDerivedOrientation();
        if (object_e.has<CharacterBase>()) {
            Ogre::SceneNode *bodyNode =
                ECS::get<CharacterModule>().characterNodes.at(
                    object_e);
            bodyNode->_setDerivedPosition(position);
            bodyNode->_setDerivedOrientation(orientation);
        }
        return 0;
    });
    lua_setglobal(L, "ecs_trigger_set_position");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        luaL_checktype(L, 1, LUA_TNUMBER); // trigger
        int trigger = lua_tointeger(L, 1);
        flecs::entity trigger_e = idmap.get_entity(trigger);
        Ogre::SceneNode *node = trigger_e.get<EventTrigger>().node;
        Ogre::Any animationAny =
            node->getUserObjectBindings().getUserAny(
                "trigger_animation");
        if (animationAny.has_value()) {
            Ogre::String animation =
                Ogre::any_cast<Ogre::String>(animationAny);
            lua_pushstring(L, animation.c_str());
			return 1;
        }
        lua_pushnil(L);
        return 1;
    });
    lua_setglobal(L, "ecs_trigger_get_animation");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        OgreAssert(lua_gettop(L) == 8, "Invalid parameters");
        luaL_checktype(L, 1, LUA_TSTRING); // face
        luaL_checktype(L, 2, LUA_TSTRING); // hair
        luaL_checktype(L, 3, LUA_TSTRING); // top
        luaL_checktype(L, 4, LUA_TSTRING); // bottom
        luaL_checktype(L, 5, LUA_TSTRING); // feet
        luaL_checktype(L, 6, LUA_TNUMBER);
        luaL_checktype(L, 7, LUA_TNUMBER);
        luaL_checktype(L, 8, LUA_TNUMBER);
        luaL_checktype(L, 9, LUA_TNUMBER);
        Ogre::String face = lua_tostring(L, 1);
        Ogre::String hair = lua_tostring(L, 2);
        Ogre::String top = lua_tostring(L, 3);
        Ogre::String bottom = lua_tostring(L, 4);
        Ogre::String feet = lua_tostring(L, 5);
        float yaw = lua_tonumber(L, 8);
        float x = lua_tonumber(L, 5);
        float y = lua_tonumber(L, 6);
        float z = lua_tonumber(L, 7);
        Ogre::Quaternion orientation(Ogre::Radian(yaw),
                         Ogre::Vector3::UNIT_Y);
        Ogre::Vector3 npcPos(x, y, z);
        flecs::entity e =
            ECS::get_mut<CharacterManagerModule>()
                .createCharacterData(face, hair, top, bottom,
                             feet, npcPos, orientation);
        ECS::modified<CharacterManagerModule>();
        lua_pushinteger(L, idmap.add_entity(e));
        return 1;
    });
    lua_setglobal(L, "ecs_npc_set");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        OgreAssert(lua_gettop(L) == 1, "Bad parameters");
        luaL_checktype(L, 1, LUA_TSTRING); // type
        const char *fileName = lua_tostring(L, 1);
        ECS::get<EngineData>().mScnMgr->getRootSceneNode()->saveChildren(
            fileName);
        return 0;
    });
    lua_setglobal(L, "ecs_save_scene_debug");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        OgreAssert(lua_gettop(L) == 2, "Bad parameters");
        luaL_checktype(L, 1, LUA_TNUMBER); // object
        luaL_checktype(L, 2, LUA_TSTRING); // name
        int object = lua_tointeger(L, 1);
        flecs::entity object_e = idmap.get_entity(object);
        const char *fileName = lua_tostring(L, 2);
        Ogre::SceneNode *node = nullptr;
        if (object_e.has<CharacterBase>()) {
            node = ECS::get<CharacterModule>().characterNodes.at(
                object_e);
        } else if (object_e.has<BoatBase>())
            node = object_e.get<BoatBase>().mNode;
        if (node)
            node->saveChildren(fileName);
        return 0;
    });
    lua_setglobal(L, "ecs_save_object_debug");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        luaL_checktype(L, 1, LUA_TBOOLEAN); // object
        ECS::get_mut<EngineData>().enableDbgDraw = lua_toboolean(L, 1);
        ECS::modified<EngineData>();
        return 0;
    });
    lua_setglobal(L, "ecs_set_debug_drawing");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        OgreAssert(lua_gettop(L) >= 1, "Bad parameters");
        luaL_checktype(L, 1, LUA_TSTRING);
        Ogre::String command = lua_tostring(L, 1);
        if (command == "physics-control") {
            OgreAssert(lua_gettop(L) == 3, "Bad parameters");
            luaL_checktype(L, 2, LUA_TNUMBER); // object
            luaL_checktype(L, 3, LUA_TBOOLEAN);
            int object = lua_tointeger(L, 2);
            flecs::entity object_e = idmap.get_entity(object);
            bool enable = lua_toboolean(L, 3);
            OgreAssert(object_e.has<CharacterBase>(),
                   "Not a character");
            PhysicsModule::controlPhysics(object_e, enable);
            object_e.add<CharacterUpdatePhysicsState>();
			return 0;
        } else if (command == "is-player") {
			OgreAssert(lua_gettop(L) == 2, "Bad parameters");
            luaL_checktype(L, 2, LUA_TNUMBER); // object
            int object = lua_tointeger(L, 2);
			flecs::entity object_e = idmap.get_entity(object);
            lua_pushboolean(L, object_e.has<Player>());
            return 1;
        } else if (command == "set-actuator") {
            OgreAssert(lua_gettop(L) == 3, "Bad parameters");
            luaL_checktype(L, 2, LUA_TNUMBER); // object
            luaL_checktype(L, 3, LUA_TSTRING); // animation
            Ogre::String animation = lua_tostring(L, 3);

            int object = lua_tointeger(L, 2);
            flecs::entity object_e = idmap.get_entity(object);
            object_e.set<CharacterVelocity>(
                { { 0, 0, 0 }, { 0, 0, 0 } });
            if (animation.length() > 0)
                object_e.set<CharacterInActuator>(
                    { animation, { 0, 0, 0 } });
            else
                object_e.remove<CharacterInActuator>();
			return 0;
        } else if (command == "animation-state") {
            OgreAssert(lua_gettop(L) >= 4, "Bad parameters");
            luaL_checktype(L, 2, LUA_TNUMBER); // object
            luaL_checktype(L, 3, LUA_TSTRING); // node
            luaL_checktype(L, 4, LUA_TSTRING); // state
            if (lua_gettop(L) == 5)
                luaL_checktype(L, 5,
                           LUA_TBOOLEAN); // reset
            int object = lua_tointeger(L, 2);
            flecs::entity object_e = idmap.get_entity(object);
            Ogre::String nodeName = lua_tostring(L, 3);
            Ogre::String stateName = lua_tostring(L, 4);
#if 0
			bool reset = false;
			if (lua_gettop(L) == 5)
				reset = lua_toboolean(L, 5);
			GameWorld::ValueParameter<flecs::entity> *obj =
				object_e.world()
					.get_mut<GameWorld>()
					.allocate_entity(object_e);
			GameWorld::ValueParameter<std::string> *obj_node =
				object_e.world()
					.get_mut<GameWorld>()
					.allocate_string(nodeName);
			GameWorld::ValueParameter<std::string> *obj_state =
				object_e.world()
					.get_mut<GameWorld>()
					.allocate_string(stateName);
			ECS::get_mut<GameWorld>().command("set_animation_state",
							  { obj, obj_node,
							    obj_state });
			ECS::modified<GameWorld>();
#endif
            OgreAssert(false, "Not implemented");

            return 0;
        } else if (command == "params-set") {
            OgreAssert(lua_gettop(L) == 4, "Invalid parameters");
            luaL_checktype(L, 2, LUA_TNUMBER);
            luaL_checktype(L, 3, LUA_TSTRING);
            luaL_checktype(L, 4, LUA_TBOOLEAN);
            bool enable = lua_toboolean(L, 4);
            flecs::entity e = idmap.get_entity(lua_tointeger(L, 2));
            Ogre::String what = lua_tostring(L, 3);
            OgreAssert(e.is_valid(), "Invalid character");
            OgreAssert(e.has<Character>(), "Not a character");
            if (what == "gravity") {
                /* clear momentum */
                if (e.has<CharacterVelocity>()) {
                    e.get_mut<CharacterVelocity>()
                        .gvelocity.y = 0.0f;
                    e.get_mut<CharacterVelocity>()
                        .velocity.y = 0.0f;
                    e.modified<CharacterVelocity>();
                }
                if (enable)
                    e.add<CharacterGravity>();
                else
                    e.remove<CharacterGravity>();
            } else if (what == "buoyancy") {
                if (enable)
                    e.add<CharacterBuoyancy>();
                else
                    e.remove<CharacterBuoyancy>();
            } else
                OgreAssert(false, "Bad parameter " + what);
            return 0;
        } else {
            OgreAssert(false, "bad argument " + command);
            return 0;
        }
    });
    lua_setglobal(L, "ecs_character");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        OgreAssert(lua_gettop(L) == 3, "Bad parameters");
        luaL_checktype(L, 1, LUA_TNUMBER); // parent
        luaL_checktype(L, 2, LUA_TNUMBER); // object
        luaL_checktype(L, 3, LUA_TSTRING); // slot
        int parent = lua_tointeger(L, 1);
        int object = lua_tointeger(L, 2);
        Ogre::String slot = lua_tostring(L, 3);
        flecs::entity parent_e = idmap.get_entity(parent);
        flecs::entity object_e = idmap.get_entity(object);
        PhysicsModule::controlPhysics(object_e, false);
        object_e.set<ParentSlot>({ parent_e, slot });
        return 0;
    });
    lua_setglobal(L, "ecs_set_slot");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        flecs::entity e = ECS::get().lookup("player");
        int result = idmap.add_entity(e);
        lua_pushinteger(L, result);
        return result;
    });
    lua_setglobal(L, "ecs_get_player_entity");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        luaL_checktype(L, 1, LUA_TNUMBER); // entity id
        int id = lua_tointeger(L, 1);
        flecs::entity e = idmap.get_entity(id);
        LuaEcsEntity *edata = static_cast<LuaEcsEntity *>(
            lua_newuserdata(L, sizeof(LuaEcsEntity)));
        new (edata) LuaEcsEntity();
        edata->e = e;
        edata->id = e;
        luaL_getmetatable(L, "FlecsEntityMetaTable");
        lua_setmetatable(L, -2);
        return 1;
    });
    lua_setglobal(L, "ecs_get_entity");
    luaL_newmetatable(L, "FlecsEntityMetaTable");
    lua_pushstring(L, "__index");
    lua_pushcfunction(L, [](lua_State *L) -> int {
        luaL_checktype(L, 1, LUA_TUSERDATA); //metatable
        luaL_checktype(L, 2, LUA_TSTRING); //function
        Ogre::String component = lua_tostring(L, 2);
        if (component == "components") {
            lua_pushvalue(L, 1);
            lua_pushcclosure(
                L,
                [](lua_State *L) -> int {
                    luaL_checktype(L, lua_upvalueindex(1),
                               LUA_TUSERDATA);
                    LuaEcsEntity *ent = static_cast<
                        LuaEcsEntity *>(lua_touserdata(
                        L, lua_upvalueindex(1)));
                    std::cout << ent->id << " called!!!\n";
                    ent->e.each([&](flecs::id id) {
                        flecs::entity cmp =
                            ECS::get().entity(id);
                        if (cmp.is_alive()) {
                            const char *name =
                                cmp.name();
                            if (name)
                                std::cout
                                    << "component: "
                                    << name
                                    << std::endl;
                        }
                    });
                    return 0;
                },
                1);
        } else if (component == "is_trigger") {
            lua_pushvalue(L, 1);
            lua_pushcclosure(
                L,
                [](lua_State *L) -> int {
                    luaL_checktype(L, lua_upvalueindex(1),
                               LUA_TUSERDATA);
                    LuaEcsEntity *ent = static_cast<
                        LuaEcsEntity *>(lua_touserdata(
                        L, lua_upvalueindex(1)));
                    lua_pushboolean(
                        L, ent->e.has<EventTrigger>());
                    return 1;
                },
                1);
        } else if (component == "is_character") {
            lua_pushvalue(L, 1);
            lua_pushcclosure(
                L,
                [](lua_State *L) -> int {
                    luaL_checktype(L, lua_upvalueindex(1),
                               LUA_TUSERDATA);
                    LuaEcsEntity *ent = static_cast<
                        LuaEcsEntity *>(lua_touserdata(
                        L, lua_upvalueindex(1)));
                    lua_pushboolean(
                        L, ent->e.has<Character>());
                    return 1;
                },
                1);
        } else if (component == "is_boat") {
            lua_pushvalue(L, 1);
            lua_pushcclosure(
                L,
                [](lua_State *L) -> int {
                    luaL_checktype(L, lua_upvalueindex(1),
                               LUA_TUSERDATA);
                    LuaEcsEntity *ent = static_cast<
                        LuaEcsEntity *>(lua_touserdata(
                        L, lua_upvalueindex(1)));
                    lua_pushboolean(L,
							ent->e.has<BoatBase>());
                    return 1;
                },
                1);
        } else if (component == "is_player") {
            lua_pushvalue(L, 1);
            lua_pushcclosure(
                L,
                [](lua_State *L) -> int {
                    luaL_checktype(L, lua_upvalueindex(1),
                               LUA_TUSERDATA);
                    LuaEcsEntity *ent = static_cast<
                        LuaEcsEntity *>(lua_touserdata(
                        L, lua_upvalueindex(1)));
                    lua_pushboolean(L,
							ent->e.has<Player>());
                    return 1;
                },
                1);
        } else
            lua_pushnil(L);
        return 1;
    });
    lua_settable(L, -3);
}

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

void LuaData::lateSetup()
{
#if 0
	Ogre::DataStreamList streams =
		Ogre::ResourceGroupManager::getSingleton().openResources(
			"*.lua", "LuaScripts");
	while (!streams.empty()) {
		Ogre::DataStreamPtr s = streams.front();
		std::cout << "stream: " << s->getAsString() << "\n";
		streams.pop_front();
		if (luaL_dostring(L, s->getAsString().c_str()) != LUA_OK) {
			std::cout << "error: " << lua_tostring(L, -1) << "\n";
			OgreAssert(false, "Script failure");
		}
	}
#endif
    Ogre::DataStreamPtr stream =
        Ogre::ResourceGroupManager::getSingleton().openResource(
            "data.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";
}

LuaModule::LuaModule(flecs::world &ecs)
{
    ecs.module<LuaModule>();
    ecs.import <SlotsModule>();
    ecs.import <VehicleManagerModule>();
    ecs.import <PlayerActionModule>();
    ecs.component<LuaChildEventTrigger>();
    ecs.component<LuaBase>()
        .on_add([](LuaBase &lua) {
            lua.mLua = new LuaData;
            lua.setup_called = false;
            lua.startup_called = false;
        })
        .add(flecs::Singleton);
    ecs.component<LuaEvent>().add(flecs::Singleton);
    ecs.system<const EngineData, LuaBase>("LuaUpdate")
        .kind(flecs::OnUpdate)
        .each([](const EngineData &eng, LuaBase &lua) {
            if (!lua.setup_called) {
                lua.mLua->lateSetup();
                lua.mLua->call_handler("setup");
                lua.setup_called = true;
            }
            if (!lua.startup_called) {
                if (eng.startupDelay <= 0.0f) {
                    lua.mLua->call_handler("startup");
                    lua.startup_called = true;
				}
            }
        });
    ecs.system<const EngineData, const LuaChildEventTrigger>(
           "CreateChildTrigger")
        .kind(flecs::OnUpdate)
        .without<EventTrigger>()
        .write<EventTrigger>()
        .each([](flecs::entity e, const EngineData &env,
             const LuaChildEventTrigger &lct) {
            Ogre::SceneNode *parentNode = nullptr;
            flecs::entity parent_e = lct.parent_e;
            if (parent_e.has<CharacterBase>()) {
                parentNode =
                    ECS::get<CharacterModule>()
                        .characterNodes.at(parent_e);
                OgreAssert(parentNode, "bad node");

            } else if (parent_e.has<BoatBase>()) {
                parentNode = parent_e.get<BoatBase>().mNode;
                OgreAssert(parent_e.get<BoatBase>().mNode,
                       "bad node");
            } else
                return;
            EventTrigger &trigger = e.ensure<EventTrigger>();
            OgreAssert(parentNode, "bad parent");
            trigger.position = lct.position;
            trigger.halfheight = lct.halfheight;
            trigger.radius = lct.radius;
            trigger.event = lct.event;
            trigger.parent = parentNode;
            e.modified<EventTrigger>();
        });
    ecs.system<LuaBase, LuaEvent>("HandleLuaEvents")
        .kind(flecs::OnUpdate)
        .each([](LuaBase &base, LuaEvent &evt) {
            while (!evt.events.empty()) {
                LuaEvent::Event &ev = evt.events.front();
                base.mLua->call_handler(ev.event, ev.e1, ev.e2);
                evt.events.pop_front();
            }
        });
}
}
