#include "eng-malloc.hpp"
#include "luastack.hpp"
#include "wrap-string.hpp"
#include "wrap-vector.hpp"

#include "luaconsole.hpp"
#include "util.hpp"

#include <cstring>
#include <iostream>

LuaConsole::LuaConsole() {
    lua_state_ = LuaStack::newstate(eng::l_alloc);
    clear_raw_input();
}

LuaConsole::~LuaConsole() {
    lua_close(lua_state_);
}

static LuaConsole::StringVec split_words(const eng::string &raw) {
    LuaConsole::StringVec result;
    eng::string acc;
    for (char c : raw) {
        if ((c == ' ')||(c == '\n')||(c == '\r')) {
            if (!acc.empty()) {
                result.push_back(acc);
                acc = "";
            }
        } else {
            acc += c;
        }
    }
    if (!acc.empty()) {
        result.push_back(acc);
    }
    return result;
}

LuaConsole::StringVec LuaConsole::get_command() {
    StringVec result = words_;
    words_.clear();
    return result;
}

void LuaConsole::synerr(const eng::string &msg) {
    words_.clear();
    words_.push_back("syntax");
    words_.push_back(msg);
}

void LuaConsole::simplify(const StringVec &words) {
    words_ = words;
    if (words.size() == 0) {
        return;
    } else if (util::validinteger(words[0])) {
        if (words.size() == 1) {
            words_.clear();
            words_.push_back("choose");
            words_.push_back(words[0]);
        } else {
            synerr("/choose command takes no arguments");
        }
    } else if (words[0] == "choose") {
        if ((words.size() == 2)&&(util::validinteger(words[1]))) {
            // OK
        } else {
            synerr("/choose [menu-line-number]");
        }
    } else if (words[0] == "view") {
        if (words.size() != 1) {
            synerr("/view takes no arguments");
        }
    } else if (words[0] == "menu") {
        if (words.size() == 1) {
            words_.push_back("-");
        } else if ((words.size() == 2)&&(util::validinteger(words[1]))) {
            // OK
        } else {
            synerr("/menu [optional-tangible-id]");
        }
    } else if (words[0] == "quit") {
        if (words.size() != 1) {
            synerr("/quit takes no arguments");
        }
    } else if (words[0] == "tick") {
        if (words.size() != 1) {
            synerr("/tick takes no arguments");
        }
    } else if (words[0] == "cpl") {
        if (words.size() != 1) {
            synerr("/cpl takes no arguments");
        }
    } else if (words[0] == "work") {
        if (words.size() != 1) {
            synerr("/work takes no arguments");
        }
    } else {
        synerr("unrecognized command");
    }
}

void LuaConsole::clear_raw_input() {
    raw_input_ = "";
    lines_ = 0;
    prompt_ = "> ";
}

void LuaConsole::add(eng::string line) {
    for (int i = 0; i < int(line.size()); i++) {
        if (line[i] == '\n') line[i] = ' ';
    }
    raw_input_ += line;
    raw_input_ += '\n';
    lines_ += 1;
    prompt_ = ">> ";
    words_.clear();

    // Try to interpret it as a slash-command.
    if ((lines_ == 1)&&(raw_input_[0] == '/')) {
        simplify(split_words(raw_input_.substr(1)));
        clear_raw_input();
        return;
    }

    // Translate lua expression, deal with initial prefix.
    eng::string partial;
    eng::string lua_mode;
    if (util::has_prefix(raw_input_, "?=")) {
        lua_mode = "luaprobe";
        partial = eng::string("return ") + raw_input_.substr(2);
    } else if (util::has_prefix(raw_input_, "?")) {
        lua_mode = "luaprobe";
        partial = raw_input_.substr(1);
    } else if (util::has_prefix(raw_input_, "=")) {
        lua_mode = "luainvoke";
        partial = eng::string("return ") + raw_input_.substr(1);
    } else {
        lua_mode = "luainvoke";
        partial = raw_input_;
    }

    // Try to parse the lua expression
    int top = lua_gettop(lua_state_);
    int status = luaL_loadbuffer(lua_state_, partial.c_str(), partial.size(), "=stdin");
    if (status == LUA_ERRSYNTAX)
    {
        const char *eof = "<eof>";
        int leof = strlen(eof);
        size_t lmsg;
        const char *msg = lua_tolstring(lua_state_, -1, &lmsg);
        const char *tp = msg + lmsg - leof;
        if (strstr(msg, eof) != tp) {
            words_.push_back("syntax");
            words_.push_back(msg);
            clear_raw_input();
        }
    } else {
        words_.push_back(lua_mode);
        words_.push_back(util::rtrim(partial));
        clear_raw_input();
    }
    lua_settop(lua_state_, top);
}

