#include "LuaAnimationTreeApi.hpp"
#include "../systems/AnimationTreeRegistry.hpp"
#include "../components/AnimationTree.hpp"
#include <lua.hpp>

namespace editScene
{

static void pushAnimationTreeNode(lua_State *L,
				  const AnimationTreeNode &node);
static bool readAnimationTreeNode(lua_State *L, int idx,
				  AnimationTreeNode &node);

static void pushAnimationTreeNode(lua_State *L,
				  const AnimationTreeNode &node)
{
	lua_newtable(L);
	lua_pushstring(L, node.type.c_str());
	lua_setfield(L, -2, "type");
	if (!node.name.empty()) {
		lua_pushstring(L, node.name.c_str());
		lua_setfield(L, -2, "name");
	}
	if (!node.animationName.empty()) {
		lua_pushstring(L, node.animationName.c_str());
		lua_setfield(L, -2, "animationName");
	}
	if (node.speed != 1.0f) {
		lua_pushnumber(L, node.speed);
		lua_setfield(L, -2, "speed");
	}
	if (node.fadeSpeed != 7.5f) {
		lua_pushnumber(L, node.fadeSpeed);
		lua_setfield(L, -2, "fadeSpeed");
	}
	if (!node.endTransitions.empty()) {
		lua_newtable(L);
		for (const auto &pair : node.endTransitions) {
			lua_pushstring(L, pair.second.c_str());
			lua_setfield(L, -2, pair.first.c_str());
		}
		lua_setfield(L, -2, "endTransitions");
	}
	if (!node.children.empty()) {
		lua_newtable(L);
		for (size_t i = 0; i < node.children.size(); ++i) {
			pushAnimationTreeNode(L, node.children[i]);
			lua_rawseti(L, -2, (int)i + 1);
		}
		lua_setfield(L, -2, "children");
	}
}

static bool readAnimationTreeNode(lua_State *L, int idx,
				  AnimationTreeNode &node)
{
	if (!lua_istable(L, idx))
		return false;

	node.type = "animation";
	if (lua_getfield(L, idx, "type"), lua_isstring(L, -1))
		node.type = lua_tostring(L, -1);
	lua_pop(L, 1);

	node.name = "";
	if (lua_getfield(L, idx, "name"), lua_isstring(L, -1))
		node.name = lua_tostring(L, -1);
	lua_pop(L, 1);

	node.animationName = "";
	if (lua_getfield(L, idx, "animationName"), lua_isstring(L, -1))
		node.animationName = lua_tostring(L, -1);
	lua_pop(L, 1);

	node.speed = 1.0f;
	if (lua_getfield(L, idx, "speed"), lua_isnumber(L, -1))
		node.speed = (float)lua_tonumber(L, -1);
	lua_pop(L, 1);

	node.fadeSpeed = 7.5f;
	if (lua_getfield(L, idx, "fadeSpeed"), lua_isnumber(L, -1))
		node.fadeSpeed = (float)lua_tonumber(L, -1);
	lua_pop(L, 1);

	node.endTransitions.clear();
	if (lua_getfield(L, idx, "endTransitions"), lua_istable(L, -1)) {
		lua_pushnil(L);
		while (lua_next(L, -2) != 0) {
			if (lua_isstring(L, -2) && lua_isstring(L, -1)) {
				node.endTransitions[lua_tostring(L, -2)] =
					lua_tostring(L, -1);
			}
			lua_pop(L, 1);
		}
	}
	lua_pop(L, 1);

	node.children.clear();
	if (lua_getfield(L, idx, "children"), lua_istable(L, -1)) {
		int childIdx = 1;
		while (true) {
			lua_rawgeti(L, -1, childIdx++);
			if (lua_isnil(L, -1)) {
				lua_pop(L, 1);
				break;
			}
			AnimationTreeNode child;
			if (readAnimationTreeNode(L, lua_gettop(L), child))
				node.children.push_back(child);
			lua_pop(L, 1);
		}
	}
	lua_pop(L, 1);

	return true;
}

static int luaAnimationTreeRegister(lua_State *L)
{
	if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_istable(L, 2))
		return 0;

	Ogre::String name = lua_tostring(L, 1);
	AnimationTreeRegistry::AnimationTreeDefinition def;
	def.name = name;

	if (lua_getfield(L, 2, "skeletonSource"), lua_isstring(L, -1))
		def.skeletonSource = lua_tostring(L, -1);
	lua_pop(L, 1);

	if (lua_getfield(L, 2, "root"), lua_istable(L, -1))
		readAnimationTreeNode(L, lua_gettop(L), def.root);
	lua_pop(L, 1);

	AnimationTreeRegistry::getSingleton().registerTree(def);
	return 0;
}

static int luaAnimationTreeFind(lua_State *L)
{
	if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
		return 0;

	Ogre::String name = lua_tostring(L, 1);
	const AnimationTreeRegistry::AnimationTreeDefinition *def =
		AnimationTreeRegistry::getSingleton().findTree(name);
	if (!def)
		return 0;

	lua_newtable(L);
	lua_pushstring(L, def->name.c_str());
	lua_setfield(L, -2, "name");
	lua_pushstring(L, def->skeletonSource.c_str());
	lua_setfield(L, -2, "skeletonSource");
	pushAnimationTreeNode(L, def->root);
	lua_setfield(L, -2, "root");
	return 1;
}

static int luaAnimationTreeList(lua_State *L)
{
	lua_newtable(L);
	std::vector<Ogre::String> names =
		AnimationTreeRegistry::getSingleton().getTreeNames();
	std::sort(names.begin(), names.end());
	for (size_t i = 0; i < names.size(); ++i) {
		lua_pushstring(L, names[i].c_str());
		lua_rawseti(L, -2, (int)i + 1);
	}
	return 1;
}

static int luaAnimationTreeRemove(lua_State *L)
{
	if (lua_gettop(L) < 1 || !lua_isstring(L, 1))
		return 0;
	AnimationTreeRegistry::getSingleton().removeTree(lua_tostring(L, 1));
	return 0;
}

static int luaAnimationTreeSave(lua_State *L)
{
	(void)L;
	bool ok = AnimationTreeRegistry::getSingleton().saveToFile(
		AnimationTreeRegistry::defaultPath());
	lua_pushboolean(L, ok ? 1 : 0);
	return 1;
}

static int luaAnimationTreeLoad(lua_State *L)
{
	(void)L;
	bool ok = AnimationTreeRegistry::getSingleton().loadFromFile(
		AnimationTreeRegistry::defaultPath());
	lua_pushboolean(L, ok ? 1 : 0);
	return 1;
}

void registerLuaAnimationTreeApi(lua_State *L)
{
	lua_newtable(L);
	lua_pushcfunction(L, luaAnimationTreeRegister);
	lua_setfield(L, -2, "register");
	lua_pushcfunction(L, luaAnimationTreeFind);
	lua_setfield(L, -2, "find");
	lua_pushcfunction(L, luaAnimationTreeList);
	lua_setfield(L, -2, "list");
	lua_pushcfunction(L, luaAnimationTreeRemove);
	lua_setfield(L, -2, "remove");
	lua_pushcfunction(L, luaAnimationTreeSave);
	lua_setfield(L, -2, "save");
	lua_pushcfunction(L, luaAnimationTreeLoad);
	lua_setfield(L, -2, "load");
	lua_setfield(L, -2, "animation_trees");
}

} // namespace editScene
