#include <iostream>
#include "Components.h"
#include "GameData.h"
#include "GUIModuleCommon.h"
#include "QuestModule.h"
#include <tracy/Tracy.hpp>

namespace ECS
{

QuestModule::QuestModule(flecs::world &ecs)
{
    ZoneScoped;
    ecs.module<QuestModule>();
    ecs.observer<GameState>("EnableQuests")
        .event(flecs::OnAdd)
        .each([this](GameState &game) {
            ZoneScoped;
            if (quest_update.is_valid())
                return;
            quest_update =
                ECS::get()
                    .system<const EngineData>(
                        "UpdateQuests")
                    .each([this](const EngineData &eng) {
                        for (auto &quest : quests) {
                            if (!quest->_is_complete())
                                quest->_update(
                                    eng.delta);
                        }
                    });
        });
    ecs.observer<GameState>("DisableQuests")
        .event(flecs::OnRemove)
        .each([this](GameState &game) {
            ZoneScoped;
            if (quest_update.is_valid())
                quest_update.destruct();
        });
}

void QuestModule::addQuest(Quest *quest)
{
    ZoneScoped;
    quests.insert(quest);
}

void QuestModule::removeQuest(Quest *quest)
{
    ZoneScoped;
    quests.erase(quests.find(quest));
}
struct LuaNarrationHandler : GUI::NarrationHandler {
    int ref;
    lua_State *L;
    LuaNarrationHandler(lua_State *L, int ref)
        : ref(ref)
        , L(L)
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        lua_pushlightuserdata(L, this);
        lua_pushcclosure(
            L,
            [](lua_State *L) {
                luaL_checktype(L, 1, LUA_TSTRING);
                luaL_checktype(L, 2, LUA_TTABLE);
                LuaNarrationHandler *handler =
                    static_cast<LuaNarrationHandler *>(
                        lua_touserdata(
                            L,
                            lua_upvalueindex(1)));
                Ogre::String event = lua_tostring(L, 1);
                std::vector<Ogre::String> choices;
                int choicesLen = (int)lua_rawlen(L, 2);
                choices.reserve(choicesLen);
                for (int i = 1; i <= choicesLen; ++i) {
                    lua_rawgeti(L, 2, i);
                    if (lua_isstring(L, -1))
                        choices.push_back(
                            lua_tostring(L, -1));
                    lua_pop(L, 1);
                }
                handler->_narration(event, choices);
                return 0;
            },
            1);
        lua_setfield(L, -2, "_narration");
        lua_pushlightuserdata(L, this);
        lua_pushcclosure(
            L,
            [](lua_State *L) {
                LuaNarrationHandler *handler =
                    static_cast<LuaNarrationHandler *>(
                        lua_touserdata(
                            L,
                            lua_upvalueindex(1)));
                handler->_finish();
                return 0;
            },
            1);
        lua_setfield(L, -2, "_finish");
        lua_pushlightuserdata(L, this);
        lua_pushcclosure(
            L,
            [](lua_State *L) {
                LuaNarrationHandler *handler =
                    static_cast<LuaNarrationHandler *>(
                        lua_touserdata(
                            L,
                            lua_upvalueindex(1)));
                int answer = handler->getNarrationAnswer();
                lua_pushinteger(L, answer);
                return 1;
            },
            1);
        lua_setfield(L, -2, "_get_narration_answer");
        lua_pushlightuserdata(L, this);
        lua_pushcclosure(
            L,
            [](lua_State *L) {
                LuaNarrationHandler *handler =
                    static_cast<LuaNarrationHandler *>(
                        lua_touserdata(
                            L,
                            lua_upvalueindex(1)));
                lua_pushstring(
                    L, handler->getProperties().c_str());
                return 1;
            },
            1);
        lua_setfield(L, -2, "_get_properties");
        lua_pop(L, 1);
    }
    void finish() override
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        int type = lua_getfield(L, -1, "finish");
        OgreAssert(type == LUA_TFUNCTION, "bad finish()");
        lua_insert(L, -2);
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cerr << lua_tostring(L, -1) << std::endl;
            OgreAssert(false, "lua error");
            lua_pop(L, 1);
        }
        _clear_narration();
    }
    void activate() override
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        int type = lua_getfield(L, -1, "activate");
        OgreAssert(type == LUA_TFUNCTION, "bad activate()");
        lua_insert(L, -2);
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cerr << lua_tostring(L, -1) << std::endl;
            OgreAssert(false, "lua error");
            lua_pop(L, 1);
        }
    }
    void event(const Ogre::String &evt) override
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        int type = lua_getfield(L, -1, "event");
        OgreAssert(type == LUA_TFUNCTION, "bad event()");
        lua_insert(L, -2);
        lua_pushstring(L, evt.c_str());
        if (lua_pcall(L, 2, 0, 0) != 0) {
            std::cerr << lua_tostring(L, -1) << std::endl;
            OgreAssert(false, "lua error");
            lua_pop(L, 1);
        }
    }
};

struct LuaQuest : QuestModule::Quest {
    lua_State *L;
    int ref;
    LuaQuest(const std::string &name, lua_State *L, int ref)
        : Quest(name)
        , L(L)
        , ref(ref)
    {
        ZoneScoped;
        OgreAssert(L, "bad Lua state");
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        lua_pushlightuserdata(L, this);
        lua_pushcclosure(
            L,
            [](lua_State *L) {
                LuaQuest *_this = static_cast<LuaQuest *>(
                    lua_touserdata(L, lua_upvalueindex(1)));
                _this->_finish(LuaQuest::OK);
                return 0;
            },
            1);
        lua_setfield(L, -2, "_finish");
        lua_pushlightuserdata(L, this);
        lua_pushcclosure(
            L,
            [](lua_State *L) {
                LuaQuest *_this = static_cast<LuaQuest *>(
                    lua_touserdata(L, lua_upvalueindex(1)));
                luaL_checktype(L, 1, LUA_TTABLE);
                lua_pushvalue(L, 1);
                int nref = luaL_ref(L, LUA_REGISTRYINDEX);
                LuaNarrationHandler *handle =
                    OGRE_NEW LuaNarrationHandler(L, nref);
                ECS::get_mut<GUI>().addNarrationHandler(handle);
                ECS::modified<GUI>();

                return 0;
            },
            1);
        lua_setfield(L, -2, "_add_narration");
        lua_pop(L, 1);
    }
    void finish(int rc) override
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        int type = lua_getfield(L, -1, "finish");
        OgreAssert(type == LUA_TFUNCTION, "bad finish()");
        lua_insert(L, -2);
        lua_pushinteger(L, rc);
        if (lua_pcall(L, 2, 0, 0) != 0) {
            std::cerr << lua_tostring(L, -1) << std::endl;
            OgreAssert(false, "lua error");
            lua_pop(L, 1);
        }
    }
    void activate() override
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        int type = lua_getfield(L, -1, "activate");
        OgreAssert(type == LUA_TFUNCTION, "bad activate()");
        lua_insert(L, -2);
        if (lua_pcall(L, 1, 0, 0) != 0) {
            std::cerr << lua_tostring(L, -1) << std::endl;
            OgreAssert(false, "lua error");
            lua_pop(L, 1);
        }
    }
    void event(const Ogre::String &evt) override
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        int type = lua_getfield(L, -1, "event");
        OgreAssert(type == LUA_TFUNCTION, "bad event()");
        lua_insert(L, -2);
        lua_pushstring(L, evt.c_str());
        if (lua_pcall(L, 2, 0, 0) != 0) {
            std::cerr << lua_tostring(L, -1) << std::endl;
            OgreAssert(false, "lua error");
            lua_pop(L, 1);
        }
    }
    int update(float delta) override
    {
        ZoneScoped;
        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
        int type = lua_getfield(L, -1, "finish");
        OgreAssert(type == LUA_TFUNCTION, "bad finish()");
        lua_insert(L, -2);
        if (lua_pcall(L, 1, 1, 0) != LUA_OK) {
            std::cerr << lua_tostring(L, -1) << std::endl;
            OgreAssert(false, "lua error");
            lua_pop(L, 1);
        } else {
            int ret = lua_tointeger(L, -1);
            lua_pop(L, 1);
            return ret;
        }
        return ERROR;
    }
};

void QuestModule::addLuaQuest(lua_State *L)
{
    ZoneScoped;
    luaL_checktype(L, 1, LUA_TSTRING);
    luaL_checktype(L, 2, LUA_TTABLE);
    Ogre::String name = lua_tostring(L, 1);
    lua_pushvalue(L, 2);
    int ref = luaL_ref(L, LUA_REGISTRYINDEX);
    struct LuaQuest *quest = OGRE_NEW LuaQuest(name, L, ref);
    addQuest(quest);
}

void QuestModule::Quest::_activate()
{
    ZoneScoped;
    activate();
    active = true;
}

void QuestModule::Quest::_finish(int rc)
{
    ZoneScoped;
    finish(rc);
    active = false;
    if (rc == OK)
        complete = true;
}

QuestModule::Quest::Quest(const std::string &name)
    : name(name)
    , active(false)
    , complete(false)
    , updatePeriod(1)
    , timeAcc(0)
{
}

QuestModule::Quest::~Quest()
{
}

int QuestModule::Quest::_update(float delta)
{
    ZoneScoped;
    if (!active && !_can_activate())
        return ERROR;
    if (!active)
        _activate();
    timeAcc += delta;

    if (timeAcc > updatePeriod)
        timeAcc = 0;
    else
        return BUSY;
    int ret = update(delta);
    if (ret == BUSY)
        return ret;
    _finish(ret);
    return ret;
}

void QuestModule::Quest::_event(const std::string &evt)
{
    ZoneScoped;
    event(evt);
}

} // namespace ECS
