#include "filecomplete.h"
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include <vector>

#ifdef USE_BOOST_FILESYSTEM
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#else
#include <filesystem>
namespace fs = std::filesystem;
#endif

#ifdef _MSC_VER
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#endif

#include <cstdio>
#include <cstdlib>
#include <cstring>

#define MAX_OF(x, y) (((x) > (y)) ? (x) : (y))

#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN64)
#ifndef OS_WINDOWS
#define OS_WINDOWS

#include <conio.h>
#include <stdint.h>
#include <windows.h>
#endif
#elif defined(__APPLE__) || defined(__unix__) || defined(__unix) || defined(unix) || defined(__linux__)
#ifndef OS_UNIX
#define OS_UNIX

#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#endif
#else
#error Unknown environment!
#endif

#if defined(OS_WINDOWS)
#define ENTER 13
#define BACKSPACE 8
#define LEFT 75
#define RIGHT 77
#define UP 72
#define DOWN 80
#define DEL 83
#define CTRL_C 3
#define SPECIAL_SEQ_1 0
#define SPECIAL_SEQ_2 224
#define COLOR_TYPE uint16_t
#define DEFAULT_TITLE_COLOR 160
#define DEFAULT_PREDICT_COLOR 8
#define DEFAULT_MAIN_COLOR 7
#elif defined(OS_UNIX)
#ifdef IOS_ISH
#define ENTER 13
#else
#define ENTER 10
#endif
#define BACKSPACE 127
#define LEFT 68
#define RIGHT 67
#define UP 65
#define DOWN 66
#define DEL 51
#define DEL_AFTER 126
#define SPECIAL_SEQ_1 27
#define SPECIAL_SEQ_2 91
#define COLOR_TYPE char*
#define DEFAULT_TITLE_COLOR "0;30;102"
#define DEFAULT_PREDICT_COLOR "90"
#define DEFAULT_MAIN_COLOR "0"
#endif
#define SPACE 32
#define TAB 9

constexpr int buf_size = 1024 * 100;
static std::vector<std::vector<char>> buf{};
static std::vector<char> word{};
static size_t wo{};
static size_t len{};
static char* main_buf{};

void trans2buf(char* buffer);
void clear_line();
void set_cursor_x(short x);
short get_cursor_y();
char* fc_readline();
void color_print(const char* text, const COLOR_TYPE color);

static std::vector<std::string> cur_work_content;
static std::vector<char> deadline_vch;
static std::string str_predict;
static std::map<std::string, std::vector<std::string>> path_cache;
static std::vector<std::string> history;
static size_t his_pos{};

void append_his(const std::string& his)
{
    history.push_back(his);
    his_pos = history.size();
}

std::string up_his()
{
    if (history.size() < 1) {
        return "";
    }

    if (his_pos >= 1) {
        --his_pos;
        return history[his_pos];
    } else {
        return history[0];
    }
}

std::string next_his()
{
    if (history.size() < 1) {
        return "";
    }

    if (his_pos < (history.size() - 1)) {
        ++his_pos;
        return history[his_pos];
    } else {
        his_pos = history.size();
        return "";
    }
}

void flush_content(const std::string& search_dir, std::vector<std::string>& out)
{
    out.clear();
    fs::path work_path(search_dir);
    try {
        if (!fs::exists(work_path)) {
            return;
        }
    } catch (const std::exception& e) {
        return;
    }
    if (path_cache.count(search_dir)) {
        out = path_cache[search_dir];
    } else {
        for (const auto& entry : fs::directory_iterator(work_path)) {
            out.push_back(entry.path().lexically_normal().string());
        }
        path_cache[search_dir] = out;
    }
}

std::string file_predict(const char* data)
{
    std::string result;
    std::string cur(data);

    std::string not_key(" ");
    std::string search_key;
    for (const auto& item : deadline_vch) {
        not_key.push_back(item);
    }
    auto fk = cur.find_last_of(not_key);
    if (fk != std::string::npos) {
        search_key = cur.substr(fk + 1, cur.size() - fk);
    } else {
        search_key = cur;
    }
    if (search_key.find("/") == std::string::npos && search_key.find("\\") == std::string::npos) {
        for (const auto& item : cur_work_content) {
            if (item != search_key && item.find(search_key) == 0) {
                return item.substr(search_key.size(), item.size() - search_key.size());
            }
        }
    }
    std::string bk_search_path = search_key.substr(0, search_key.find_last_of("\\/") + 1);
    std::vector<std::string> sr;
    flush_content(bk_search_path, sr);
    for (const auto& item : sr) {
        if (item != search_key && item.find(search_key) == 0) {
            return item.substr(search_key.size(), item.size() - search_key.size());
        }
    }
    return result;
}

#if defined(OS_UNIX)
int _getch()
{
    int character;
    struct termios old_attr, new_attr;

    // Backup terminal attributes
    if (tcgetattr(STDIN_FILENO, &old_attr) == -1) {
        fprintf(stderr, "[ERROR] Couldn't get terminal attributes\n");
        exit(1);
    }

    // Disable echo
    new_attr = old_attr;
    new_attr.c_lflag &= ~(ICANON | ECHO);
    if (tcsetattr(STDIN_FILENO, TCSANOW, &new_attr) == -1) {
        fprintf(stderr, "[ERROR] Couldn't set terminal attributes\n");
        exit(1);
    }

    // Get input character
    character = getchar();

    // Restore terminal attributes
    if (tcsetattr(STDIN_FILENO, TCSANOW, &old_attr) == -1) {
        fprintf(stderr, "[ERROR] Couldn't reset terminal attributes\n");
        exit(1);
    }

    return character;
}
#endif

std::pair<short, short> get_wh()
{
#if defined(OS_WINDOWS)
    HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE);
    if (h_console == NULL) {
        fprintf(stderr, "[ERROR] Couldn't handle terminal\n");
        exit(1);
    }
    CONSOLE_SCREEN_BUFFER_INFO console_info;
    if (GetConsoleScreenBufferInfo(h_console, &console_info) == 0) {
        fprintf(stderr, "[ERROR] Couldn't get terminal info\n");
        exit(1);
    }
    int cw = console_info.srWindow.Right - console_info.srWindow.Left + 1;
    int ch = console_info.srWindow.Bottom - console_info.srWindow.Top + 1;
    auto w = static_cast<short>(cw);
    auto h = static_cast<short>(ch);
    return std::make_pair(w, h);
#elif defined(OS_UNIX)
    return std::make_pair(12, 12);
#endif
}

size_t get_u8_len(unsigned char ch)
{
    if (ch <= 0x7F) {
        return 1;
    } else if ((ch & 0xE0) == 0xC0) {
        return 2;
    } else if ((ch & 0xF0) == 0xE0) {
        return 3;
    } else if ((ch & 0xF8) == 0xF0) {
        return 4;
    } else if ((ch & 0xFC) == 0xF8) {
        return 5;
    } else if ((ch & 0xFE) == 0xFC) {
        return 6;
    } else {
        printf("invalid u8 first ch.");
        exit(1);
    }
    return 0;
}

std::vector<std::vector<char>> str_to_vec(const std::string& source)
{
    std::vector<std::vector<char>> result;
    for (size_t i = 0; i < source.size();) {
        std::vector<char> b;
        char curch = source[i];
        b.push_back(curch);
        if (curch >= 0) {
            result.push_back(b);
            ++i;
        } else {
#if defined(BINARY_GBK)
            size_t length = 2;
#else
            size_t length = get_u8_len(curch);
#endif
            for (size_t z = 1; z < length; ++z) {
                if ((i + z) < source.size()) {
                    b.push_back(source[i + z]);
                }
            }
            result.push_back(b);
            i += length;
        }
    }
    return result;
}

void supply(std::vector<char>& wch, char ch)
{
#if defined(BINARY_GBK)
    wch.push_back(ch);
    if (ch >= 0 && ch < 128) {
        return;
    }
    auto tch = _getch();
    wch.push_back(tch);
#else
    wch.push_back(ch);
    int length = get_u8_len(static_cast<unsigned char>(ch));
    if (length == 0) {
        printf("Invalid Charactor!\n");
        exit(1);
    }
    for (int i = 1; i < length; ++i) {
        auto tch = _getch();
        wch.push_back(tch);
    }
#endif
}

void clear_line()
{
#if defined(OS_WINDOWS)
    // Get current terminal width
    short width = get_wh().first;
    if (width < 1) {
        fprintf(stderr, "[ERROR] Size of terminal is too small\n");
        exit(1);
    }

    // Create long empty string
    char* empty = (char*)malloc(sizeof(char) * width);
    if (empty) {
        memset(empty, ' ', width);
        empty[width - 1] = '\0';
    }

    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    GetConsoleCursorInfo(hConsole, &cursorInfo);
    cursorInfo.bVisible = FALSE;
    SetConsoleCursorInfo(hConsole, &cursorInfo);

    // Clear line
    printf("\r%s\r", empty);

    // Free line
    free(empty);
#elif defined(OS_UNIX)
    printf("\033[2K\r");
#endif
}

void set_cursor_x(short x)
{
#if defined(OS_WINDOWS)
    HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE);
    if (h_console == NULL) {
        fprintf(stderr, "[ERROR] Couldn't handle terminal\n");
        exit(1);
    }

    auto wh = get_wh();
    // Create position
    COORD xy;
    xy.Y = get_cursor_y();
    short px = x % wh.first - 1;
    xy.X = px < 0 ? 0 : px;

    // Set cursor position
    if (SetConsoleCursorPosition(h_console, xy) == 0) {
        auto code = GetLastError();
        fprintf(stderr, "[ERROR] Couldn't set terminal cursor position, err=%lu\n", code);
        exit(1);
    }
#elif defined(OS_UNIX)
    printf("\033[%d;%dH", get_cursor_y(), x);
#endif
}

short get_cursor_y()
{
#if defined(OS_WINDOWS)
    HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE);
    if (h_console == NULL) {
        fprintf(stderr, "[ERROR] Couldn't handle terminal\n");
        exit(1);
    }

    // Get terminal info
    CONSOLE_SCREEN_BUFFER_INFO console_info;
    if (GetConsoleScreenBufferInfo(h_console, &console_info) == 0) {
        fprintf(stderr, "[ERROR] Couldn't get terminal Y position\n");
        exit(1);
    }

    // Return Y position
    return console_info.dwCursorPosition.Y;
#elif defined(OS_UNIX)
    struct termios old_attr, new_attr;
    char ch, buf[30] = {0};
    int i = 0, pow = 1, y = 0;

    // Backup terminal attributes
    if (tcgetattr(STDIN_FILENO, &new_attr) == -1) {
        fprintf(stderr, "[ERROR] Couldn't get terminal attributes\n");
        exit(1);
    }

    // Disable echo
    old_attr = new_attr;
    old_attr.c_lflag &= ~(ICANON | ECHO);
    if (tcsetattr(STDIN_FILENO, TCSANOW, &old_attr) == -1) {
        fprintf(stderr, "[ERROR] Couldn't set terminal attributes\n");
        exit(1);
    }

    // Get info about cursor
    if (write(STDOUT_FILENO, "\033[6n", 4) != 4) {
        fprintf(stderr, "[ERROR] Couldn't get cursor information\n");
        exit(1);
    }

    // Get ^[[{this};1R value

    for (ch = 0; ch != 'R'; i++) {
        if (read(STDIN_FILENO, &ch, 1) != 1) {
            fprintf(stderr, "[ERROR] Couldn't read cursor information");
            exit(1);
        }
        buf[i] = ch;
    }

    i -= 2;
    while (buf[i] != ';') {
        i -= 1;
    }

    i -= 1;
    while (buf[i] != '[') {
        y = y + (buf[i] - '0') * pow;
        pow *= 10;
        i -= 1;
    }

    // Reset attributes
    if (tcsetattr(0, TCSANOW, &new_attr) == -1) {
        fprintf(stderr, "[ERROR] Couldn't reset terminal attributes\n");
        exit(1);
    }

    return (short)y;
#endif
}

void fc_append(char deadline_ch)
{
    deadline_vch.push_back(deadline_ch);
}

char* fc_readline()
{
    main_buf = (char*)malloc(sizeof(char) * buf_size);
    std::memset(main_buf, 0x0, buf_size);
    if (main_buf == NULL) {
        fprintf(stderr, "[ERROR] Couldn't allocate memory for buffer\n");
        exit(1);
    }

    char* tmp_buf = (char*)malloc(sizeof(char) * buf_size);
    std::memset(tmp_buf, 0x0, buf_size);
    std::shared_ptr<int> deleter(new int(), [tmp_buf](int* p) {
        delete p;
        free(tmp_buf);
    });

    flush_content(".", cur_work_content);
    auto add_newer = [&](const std::vector<char>& wch) {
        if (wo < buf.size()) {
            if (len >= buf.size()) {
                buf.resize(buf.size() * 2);
            }
            if (len > 0) {
                for (size_t i = len - 1; i >= wo; --i) {
                    buf[i + 1] = buf[i];
                }
            }
            buf[wo] = wch;
        } else {
            buf.push_back(wch);
        }
        ++wo;
        ++len;
    };

    bool need_predic = true;
    std::chrono::time_point<std::chrono::high_resolution_clock> p1, p2;
    p1 = std::chrono::high_resolution_clock::now();

#if defined(OS_WINDOWS)
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    GetConsoleCursorInfo(hConsole, &cursorInfo);
    cursorInfo.bVisible = TRUE;
    SetConsoleCursorInfo(hConsole, &cursorInfo);
#endif

    while (1) {
        word.clear();
        clear_line();
        // Print current buffer
        color_print(nullptr, DEFAULT_MAIN_COLOR);
        if (!str_predict.empty()) {
            color_print(str_predict.data(), DEFAULT_PREDICT_COLOR);
        }
        // Move cursor
        size_t cur_pos{};
#if defined(BINARY_GBK)
        for (size_t i = 0; i < buf.size() && i < wo; ++i) {
            cur_pos += buf[i].size();
        }
#else
        for (size_t i = 0; i < buf.size() && i < wo; ++i) {
            if (buf[i].size() > 1) {
                cur_pos += 2;
            } else {
                cur_pos += 1;
            }
        }
#endif
        set_cursor_x(cur_pos + 1);
        need_predic = true;

        // Read character from console
        int ch = _getch();

        p2 = p1;
        p1 = std::chrono::high_resolution_clock::now();

        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(p1 - p2).count();
        if (duration <= 10) {
            need_predic = false;
        }

        switch (ch) {
        case ENTER:
#if defined(OS_WINDOWS)
            hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
            CONSOLE_CURSOR_INFO cursorInfo;
            GetConsoleCursorInfo(hConsole, &cursorInfo);
            cursorInfo.bVisible = FALSE;
            SetConsoleCursorInfo(hConsole, &cursorInfo);
#endif
            append_his(main_buf);
            return main_buf;
#if defined(OS_WINDOWS)
        case CTRL_C: {
            free(main_buf);
            return nullptr;
        }
#endif
        case BACKSPACE: {
            if (wo > 0) {
                for (size_t i = wo - 1; i < buf.size() - 1; ++i) {
                    buf[i] = buf[i + 1];
                }
                --wo;
                --len;
            }
            break;
        }
        case TAB: {
            // 在这里补全
            if (!str_predict.empty()) {
                auto temp = str_to_vec(str_predict);
                str_predict.clear();
                for (const auto& item : temp) {
                    add_newer(item);
                }
                continue;
            }
            break;
        }
#if defined(OS_WINDOWS)
        case SPECIAL_SEQ_1:
        case SPECIAL_SEQ_2:
#elif defined(OS_UNIX)
        case SPECIAL_SEQ_1:
#endif
        {
#if defined(OS_UNIX)
            if (_getch() != SPECIAL_SEQ_2) {
                continue;
            }
#endif
            switch (_getch()) {
            case LEFT:
                if (wo > 0) {
                    --wo;
                }
                break;
            case RIGHT:
                if (wo < len) {
                    ++wo;
                }
                break;
            case UP: {
                auto up_str = up_his();
                buf.clear();
                auto t = str_to_vec(up_str);
                if (t.size() > 0) {
                    buf.insert(buf.end(), t.begin(), t.end());
                }
                len = buf.size();
                wo = len;
                break;
            }

            case DOWN: {
                auto next_str = next_his();
                buf.clear();
                auto t = str_to_vec(next_str);
                if (t.size() > 0) {
                    buf.insert(buf.end(), t.begin(), t.end());
                }
                len = buf.size();
                wo = len;
                break;
            }
            case DEL:
                // Edit buffer like DELETE key
#if defined(OS_UNIX)
                if (_getch() == DEL_AFTER)
#endif
                {
                }
                break;
            default:
                break;
            }
            break;
        }
        default: {
            supply(word, ch);
            add_newer(word);
            break;
        }
        }
        // 补正
        trans2buf(tmp_buf);
        if (need_predic) {
            str_predict = file_predict(tmp_buf);
        } else if (!str_predict.empty()) {
            str_predict.clear();
        }
        p1 = std::chrono::high_resolution_clock::now();
    }
    return main_buf;
}

void fc_free(char* str)
{
    buf.clear();
    wo = 0;
    len = 0;
    free(str);
}

void trans2buf(char* buffer)
{
    int j = 0;
    for (size_t i = 0; i < buf.size() && i < len; ++i) {
        for (const auto& c : buf[i]) {
            buffer[j++] = c;
        }
    }
    buffer[j++] = '\0';
}

void color_print(const char* text, const COLOR_TYPE color)
{
#if defined(OS_WINDOWS)
    HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE);
    if (h_console == NULL) {
        fprintf(stderr, "[ERROR] Couldn't handle terminal\n");
        exit(1);
    }

    CONSOLE_SCREEN_BUFFER_INFO console_info;
    COLOR_TYPE backup;

    // Save current attributes
    if (GetConsoleScreenBufferInfo(h_console, &console_info) == 0) {
        fprintf(stderr, "[ERROR] Couldn't get terminal info\n");
        exit(1);
    }

    auto wh = get_wh();
    backup = console_info.wAttributes;

    // Print colored text
    if (SetConsoleTextAttribute(h_console, color) == 0) {
        fprintf(stderr, "[ERROR] Couldn't set terminal color\n");
        exit(1);
    }

    if (!text) {
        trans2buf(main_buf);
        printf("%s", main_buf);
    } else {
        printf("%s", text);
    }

    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    GetConsoleCursorInfo(hConsole, &cursorInfo);
    cursorInfo.bVisible = TRUE;
    SetConsoleCursorInfo(hConsole, &cursorInfo);

    // Restore original color
    if (SetConsoleTextAttribute(h_console, backup) == 0) {
        fprintf(stderr, "[ERROR] Couldn't reset terminal color\n");
        exit(1);
    }
#elif defined(OS_UNIX)
    // Set new terminal color
    printf("\033[");
    printf("%s", color);
    printf("m");

    if (!text) {
        trans2buf(main_buf);
        printf("%s", main_buf);
    } else {
        printf("%s", text);
    }
    // Resets the text to default color
    printf("\033[0m");
#if defined(MFLUSH_STDOUT)
    fflush(stdout);
#endif
#endif
}