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

#include <utility>
#include <iostream>
#include <cstring>

#include "drivenengine.hpp"

DrivenEngineReg *DrivenEngineReg::All;

DrivenEngineReg::DrivenEngineReg(const char *n, DrivenEngineMaker fn) {
    name = n;
    maker = fn;
    next = All;
    All = this;
}

void DrivenEngine::print_usage(std::ostream &strm, std::string_view progname) {
    strm << "Usage: " << progname << " <mode>" << std::endl;
    for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) {
        strm << "  Mode can be: " << reg->name << std::endl;
    }
}

UniqueDrivenEngine DrivenEngine::make(std::string_view kind) {
    for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) {
        if (kind == std::string_view(reg->name)) {
            UniqueDrivenEngine result = reg->maker();
            return result;
        }
    }
    return nullptr;
}

Channel::Channel(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop) {
    chid_ = chid;
    port_ = port;
    closed_ = false;
    target_ = target;
    readline_lastc_ = 0;
    desired_prompt_ = "";
    stop_driver_ = stop;
    sb_in_ = eng::make_shared<StreamBuffer>();
    sb_out_ = eng::make_shared<StreamBuffer>();
    sb_drvout_ = sb_out_;
}

void Channel::erase_command() {
    int ccsize = current_prompt_.size() + current_command_.size();
    if (ccsize > 0) {
        sb_drvout_->write_bytes(util::repeat_string("\b \b", ccsize));
        current_prompt_ = "";
        current_command_ = "";
    }
}

void Channel::echo_command() {
    // If the prompt has changed, erase everything and start over.
    if (desired_prompt_ != current_prompt_) {
        int ccsize = current_prompt_.size() + current_command_.size();
        sb_drvout_->write_bytes(util::repeat_string("\b \b", ccsize));
        sb_drvout_->write_bytes(desired_prompt_);
        current_command_ = "";
        current_prompt_ = desired_prompt_;
    }

    // Find out how much of the command matches.
    int match = util::common_prefix_length(current_command_, desired_command_);

    // Echo backspaces to remove the non-matching part.
    int remove = current_command_.size() - match;
    if (remove > 0) {
        sb_drvout_->write_bytes(util::repeat_string("\b \b", remove));
        current_command_ = current_command_.substr(0, match);
    }

    // Echo the new part.
    eng::string newpart = desired_command_.substr(current_command_.size());
    if (newpart != "") {
        sb_drvout_->write_bytes(newpart);
        current_command_ = desired_command_;
    }
}

void Channel::set_prompt(const eng::string &p) {
    desired_prompt_ = p;
}

void Channel::feed_readline(std::string_view data) {
    int nbytes = data.size();
    const char *bytes = data.data();
    for (int i = 0; i < nbytes; i++) {
        char c = bytes[i];
        if ((c == '\n') && (readline_lastc_ == '\r')) {
            // Ignore newline immediately after carriage return.
            // Otherwise, crlf produces two newlines.
        } else if ((c == '\r') || (c == '\n')) {
            echo_command();
            sb_drvout_->write_bytes(eng::string(" \n"));
            sb_in_->write_bytes(desired_command_);
            sb_in_->write_uint8('\n');
            desired_command_ = "";
            current_prompt_ = "";
            current_command_ = "";
        } else if ((c == '\b') || (c == 127)) {
            int len = desired_command_.size();
            if (len > 0) {
                desired_command_ = desired_command_.substr(0, len-1);
            }
        } else if (c >= 32) {
            int len = desired_command_.size();
            if (len < READLINE_MAX) {
                desired_command_ = desired_command_ + c;
            }
        }
        readline_lastc_ = c;
    }
}

std::string_view Channel::peek_outgoing() const {
    return sb_drvout_->view();
}

void Channel::pump_readline() {
    if (sb_drvout_ != sb_out_) {
        if (!sb_out_->empty()) {
            erase_command();
            sb_out_->transfer_into(sb_drvout_.get());
        }
        echo_command();
    }
}

void Channel::sent_outgoing(int nbytes) {
    sb_drvout_->read_bytes(nbytes);
}

int DrivenEngine::find_unused_chid() {
    // Note: channel ID zero is special, it is never reused.
    for (int i = 0; i < MAX_CHAN; i++) {
        int id = next_unused_chid_++;
        if (next_unused_chid_ == MAX_CHAN) next_unused_chid_ = 1;
        if (channels_[id] == nullptr) return id;
    }
    assert(false);
    return 0;
}

Channel *DrivenEngine::get_chid(int chid) const {
    assert(unsigned(chid) < MAX_CHAN);
    assert(channels_[chid].get() != nullptr);
    return channels_[chid].get();
}

void DrivenEngine::listen_port(int port) {
    listen_ports_.push_back(port);
}

double DrivenEngine::get_clock() {
    return clock_;
}

SharedChannel DrivenEngine::new_outgoing_channel(const eng::string &target) {
    int chid = find_unused_chid();
    new_outgoing_.push_back(chid);
    SharedChannel result = eng::make_shared<Channel>(this, chid, 0, target, stop_driver_);
    channels_[chid] = result;
    return result;
}

SharedChannel DrivenEngine::new_incoming_channel() {
    if (accepted_channels_.empty()) {
        return nullptr;
    } else {
        SharedChannel result = std::move(accepted_channels_.back());
        accepted_channels_.pop_back();
        return result;
    }
}

SharedChannel DrivenEngine::get_stdio_channel() {
    return stdio_channel_;
}

util::LuaSourcePtr DrivenEngine::get_lua_source() {
    return std::move(lua_source_);
}

void DrivenEngine::rescan_lua_source() {
    rescan_lua_source_ = true;
}

void DrivenEngine::stop_driver() {
    stop_driver_ = true;
    for (int i = 0; i < MAX_CHAN; i++) {
        if (channels_[i] != nullptr) {
            channels_[i]->stop_driver_ = true;
        }
    }
}

const eng::vector<int> &DrivenEngine::drv_get_listen_ports() const {
    return listen_ports_;
}

const eng::vector<int> &DrivenEngine::drv_get_new_outgoing() const {
    return new_outgoing_;
}

void DrivenEngine::drv_clear_new_outgoing() {
    new_outgoing_.clear();
}

const eng::string &DrivenEngine::drv_get_target(int chid) const {
    return get_chid(chid)->target_;
}

bool DrivenEngine::drv_outgoing_empty(int chid) const {
    std::string_view view = drv_peek_outgoing(chid);
    return (view.size() == 0);
}

bool DrivenEngine::drv_get_channel_released(int chid) const {
    return channels_[chid].use_count() == 1;
}

std::string_view DrivenEngine::drv_peek_outgoing(int chid) const {
    return get_chid(chid)->peek_outgoing();
}

void DrivenEngine::drv_sent_outgoing(int chid, int nbytes) {
    return get_chid(chid)->sent_outgoing(nbytes);
}

void DrivenEngine::drv_recv_incoming(int chid, std::string_view data) {
    if (data.size() > 0) {
        Channel *ch = get_chid(chid);
        if (ch->sb_drvout_ != ch->sb_out_) {
            ch->feed_readline(data);
        } else {
            ch->sb_in_->write_bytes(data);
        }
    }
}

void DrivenEngine::drv_notify_close(int chid, std::string_view err) {
    Channel *ch = get_chid(chid);
    ch->closed_ = true;
    ch->error_ = err;
    channels_[chid].reset();
}

int DrivenEngine::drv_notify_accept(int port) {
    int chid = find_unused_chid();
    channels_[chid] = eng::make_shared<Channel>(this, chid, port, "", stop_driver_);
    accepted_channels_.push_back(channels_[chid]);
    return chid;
}

void DrivenEngine::drv_clear_lua_source() {
    lua_source_.reset();
    rescan_lua_source_ = false;
}

void DrivenEngine::drv_add_lua_source(std::string_view fn, std::string_view data) {
    if (lua_source_ == nullptr) {
        lua_source_.reset(new util::LuaSourceVec);
    }
    lua_source_->emplace_back(eng::string(fn), eng::string(data));
}

void DrivenEngine::drv_invoke_event_init(int argc, char *argv[]) {
    event_init(argc, argv);
    stdio_channel_->pump_readline();
}

void DrivenEngine::drv_invoke_event_update(double clock) {
    clock_ = clock;
    event_update();
    stdio_channel_->pump_readline();
}

bool DrivenEngine::drv_get_rescan_lua_source() const {
    return rescan_lua_source_;
}

bool DrivenEngine::drv_get_stop_driver() const {
    return stop_driver_;
}

DrivenEngine::DrivenEngine() {
    next_unused_chid_ = 1;
    stdio_channel_ = eng::make_shared<Channel>(this, 0, 0, "", false);
    stdio_channel_->sb_drvout_ = eng::make_shared<StreamBuffer>();
    channels_[0] = stdio_channel_;
    rescan_lua_source_ = true;
    clock_ = 0.0;
    stop_driver_ = false;
}

DrivenEngine::~DrivenEngine() {}

static DrivenEngine *engine_;

void DrivenEngine::set(DrivenEngine *de) {
    engine_ = de;
}

DrivenEngine *DrivenEngine::get() {
    return engine_;
}

