
#include "wrap-vector.hpp"
#include "wrap-string.hpp"

#include "luastack.hpp"
#include "util.hpp"
#include "gui.hpp"
#include "invocation.hpp"
#include "world.hpp"
#include "traceback.hpp"
#include "luaconsole.hpp"
#include "pprint.hpp"
#include "printbuffer.hpp"
#include "drivenengine.hpp"

#include <memory>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <csignal>

class TextGame : public DrivenEngine {
private:
    using StringVec = LuaConsole::StringVec;
    UniqueWorld world_;
    LuaConsole console_;
    PrintChanneler print_channeler_;
    Gui gui_;
    int64_t actor_id_;
    
    void do_luainvoke_command(const StringVec &words) {
        world_->invoke(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1]));
    }

    void do_luaprobe_command(const StringVec &words) {
        world_->snapshot();
        stdostream() << world_->probe_lua(actor_id_, words[1]);
        world_->rollback();
    }

    void do_syntax_command(const StringVec &words) {
        stdostream() << "Syntax Error: " << words[1] << std::endl;
    }

    void do_view_command(const StringVec &cmd) {
        stdostream() << world_->tangibles_near_debug_string(actor_id_, 100);
    }

    void do_menu_command(const StringVec &cmd) {
        int64_t place = util::strtoint(cmd[1], actor_id_);
        world_->update_gui(actor_id_, place, &gui_);
        stdostream() << gui_.menu_debug_string();
    }

    void do_choose_command(const StringVec &cmd) {
        eng::string action = gui_.get_action(util::strtoint(cmd[1], -1));
        if (action == "") {
            stdostream() << "Invalid menu item #" << std::endl;
            return;
        }
        Invocation inv(Invocation::KIND_PLAN, actor_id_, gui_.place(), action);
        stdostream() << "Invoking: " << inv.debug_string() << std::endl;
        world_->invoke(inv);
    }

    void do_tick_command(const StringVec &cmd) {
        world_->invoke(Invocation(Invocation::KIND_TICK, actor_id_, actor_id_, ""));
    }

    void do_quit_command(const StringVec &cmd) {
        actor_id_ = 0;
    }

    void do_command(const StringVec &words) {
        if (words.empty()) return;
        else if (words[0] == "luainvoke") do_luainvoke_command(words);
        else if (words[0] == "luaprobe") do_luaprobe_command(words);
        else if (words[0] == "syntax") do_syntax_command(words);
        else if (words[0] == "view") do_view_command(words);
        else if (words[0] == "menu") do_menu_command(words);
        else if (words[0] == "quit") do_quit_command(words);
        else if (words[0] == "tick") do_tick_command(words);
        else if (words[0] == "choose") do_choose_command(words);
        else {
            stdostream() << "Unsupported command: " << words[0] << std::endl;
        }
    }

    void check_redirects() {
        World::Redirects redir = world_->fetch_redirects();
        for (const auto &p : redir) {
            if (p.first == actor_id_) {
                actor_id_ = p.second;
                stdostream() << "Login actor ID: " << actor_id_ << std::endl;
                gui_.clear(0);
            }
        }
    }

    void event_init(int argc, char *argv[])
    {
        world_.reset(new World(util::WORLD_TYPE_STANDALONE));
        world_->update_source(get_lua_source());
        world_->run_unittests();
        actor_id_ = world_->create_login_actor();
        stdostream() << "Login actor ID: " << actor_id_ << std::endl;
        get_stdio_channel()->set_prompt(console_.get_prompt());
    }

    void event_update()
    {
        world_->update_source(get_lua_source());
        while (true) {
            eng::string line = get_stdio_channel()->in()->readline();
            if (line == "") break;
            console_.add(line);
            get_stdio_channel()->set_prompt(console_.get_prompt());
            do_command(console_.get_command());
            if (print_channeler_.channel(world_->get_printbuffer(actor_id_), stdostream())) {
                world_->invoke(print_channeler_.invocation(actor_id_));
            }
            check_redirects();
            if (actor_id_ == 0) {
                stop_driver();
                break;
            }
        }
    }
};

UniqueDrivenEngine make_TextGame() {
    return UniqueDrivenEngine(new TextGame);
}
static DrivenEngineReg reg_TextGame("textgame", make_TextGame);
