#include "readline.hpp"

#define MAXLINE 512


static std::u32string white_(1, ' ');
static std::u32string newline_(1, '\n');

static std::u32string n_backspaces(int n) {
    std::u32string result(3 * n, 0);
    for (int i = 0; i < n; i++) {
        result[i*3 + 0] = '\b';
        result[i*3 + 1] = ' ';
        result[i*3 + 2] = '\b';
    }
    return result;
}

static int common_prefix_length(const std::u32string &a, const std::u32string &b) {
    int minlen = std::min(a.size(), b.size());
    for (int i = 0; i < minlen; i++) {
        if (a[i] != b[i]) return i;
    }
    return minlen;
}

void ReadlineDevice::set_prompt(std::string_view prompt) {
    std::scoped_lock lock(mutex_);
    desired_prompt_ = drvutil::utf8_to_utf32(prompt, nullptr);
    echo_command();
}

void ReadlineDevice::erase_command() {
    int ccsize = current_prompt_.size() + current_command_.size();
    if (ccsize > 0) {
        drvutil::console_write(n_backspaces(ccsize));
        current_prompt_.clear();
        current_command_.clear();
    }
}

void ReadlineDevice::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();
        drvutil::console_write(n_backspaces(ccsize));
        drvutil::console_write(desired_prompt_);
        current_command_.clear();
        current_prompt_ = desired_prompt_;
    }

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

    // Echo backspaces to remove the non-matching part.
    int remove = current_command_.size() - match;
    if (remove > 0) {
        drvutil::console_write(n_backspaces(remove));
        current_command_ = current_command_.substr(0, match);
    }

    // Echo the new part.
    std::u32string newpart = desired_command_.substr(current_command_.size());
    if (!newpart.empty()) {
        drvutil::console_write(newpart);
        current_command_ = desired_command_;
    }
}

std::string ReadlineDevice::putcode(char32_t c) {
    std::scoped_lock lock(mutex_);
    if ((c == '\n') && (readline_lastc_ == '\r')) {
        // Ignore newline immediately after carriage return.
        // Otherwise, crlf produces two newlines.
        return "";
    } else if ((c == '\r') || (c == '\n')) {
        echo_command();
        drvutil::console_write(white_ + newline_);
        std::u32string result = desired_command_ + newline_;
        desired_command_.clear();
        current_prompt_.clear();
        current_command_.clear();
        return drvutil::utf32_to_utf8(result);
    } else if ((c == '\b') || (c == 127)) {
        int len = desired_command_.size();
        if (len > 0) {
            desired_command_ = desired_command_.substr(0, len-1);
        }
        echo_command();
        return "";
    } else if ((c >= 32)&&(c <= 0x10FFFF)) {
        int len = desired_command_.size();
        if (len < MAXLINE) {
            desired_command_ = desired_command_ + c;
        }
        echo_command();
        return "";
    }
    readline_lastc_ = c;
    return "";
}


void ReadlineDevice::printline(std::string_view s) {
    std::scoped_lock lock(mutex_);
    bool missing_newline = ((s.size() == 0) || (s[s.size() - 1] != '\n'));
    std::u32string utf32 = drvutil::utf8_to_utf32(s, nullptr);
    erase_command();
    drvutil::console_write(utf32);
    if (missing_newline) drvutil::console_write(newline_);
    echo_command();
}

