#include <nfd.hpp>
#include <fstream>
#include "mcalc.h"

// stolen from https://doc.rust-lang.org/src/core/time.rs.html#1023
std::string fmt_decimal(uint64_t integer_part, uint32_t fractional_part, uint32_t divisor, const char *postfix) {
    char buf[10] = "000000000";

    int pos = 0;

    while (fractional_part > 0 && pos < 9) {
        buf[pos] = (char)('0' + (fractional_part / divisor));
        fractional_part %= divisor;
        divisor /= 10;
        pos += 1;
    }

    if (fractional_part > 0 && fractional_part >= divisor * 5)  {
        int rev_pos = pos;
        bool carry = true;
        while (carry && rev_pos > 0) {
            rev_pos -= 1;

            if (buf[rev_pos] < '9') {
                buf[rev_pos] += 1;
                carry = false;
            } else {
                buf[rev_pos] = '0';
            }
        }

        if (carry)
            integer_part += 1;
    }

    return "Last eval took: " + std::to_string(integer_part) + "." + buf + postfix;
}

// stolen from https://doc.rust-lang.org/src/core/time.rs.html#1023
std::string format_duration(const State::clock::duration &dur) {
    constexpr uint32_t NANOS_PER_SEC = 1'000'000'000, NANOS_PER_MILLI = 1'000'000, NANOS_PER_MICRO = 1'000;

    auto secs = std::chrono::duration_cast<std::chrono::seconds>(dur).count();
    auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(dur).count();

    if (secs > 0)
        return fmt_decimal(secs, nanos, NANOS_PER_SEC / 10, "s");
    else if (nanos >= NANOS_PER_MILLI)
        return fmt_decimal(nanos / NANOS_PER_MILLI, nanos % NANOS_PER_MILLI, NANOS_PER_MILLI / 10, "ms");
    else if (nanos >= NANOS_PER_MICRO)
        return fmt_decimal(nanos / NANOS_PER_MICRO, nanos % NANOS_PER_MICRO, NANOS_PER_MICRO / 10, "µs");
    else
        return fmt_decimal(nanos, 0, 1, "ns");
}

void draw_menu_bar() {
    static const nfdfilteritem_t file_filer[1] = {{"JSON File", "json"}};
    if (ImGui::BeginMainMenuBar()) {
        if (ImGui::MenuItem("Clear")) {
            state.clear();
        }
        if (ImGui::MenuItem("Open")) {
            NFD::UniquePathN path;
            if (NFD::OpenDialog(path, file_filer, 1) == NFD_OKAY) {
                std::ifstream f{path.get()};
                nlohmann::json j = nlohmann::json::parse(f);
                state.from_json(j);
            }
        }
        if (ImGui::MenuItem("Save")) {
            NFD::UniquePathN path;
            if (NFD::SaveDialog(path, file_filer, 1) == NFD_OKAY) {
                nlohmann::json j;
                state.to_json(j);
                std::ofstream f{path.get()};
                f << std::setw(4) << j << std::endl;
            }
        }
        ImGui::EndMainMenuBar();
    }
}

std::stringstream space_ss() {
    std::stringstream ss;
    ss.imbue(std::locale(std::locale(), new SpaceNumpunct()));
    return ss;
}

void draw_table(bool& focus_input) {
    bool do_eval = false;
    static std::stringstream ss = space_ss();
    if (ImGui::BeginTable("VariableTable", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) {
        std::vector<int> to_remove;
        ImGui::TableSetupColumn("Name");
        ImGui::TableSetupColumn("Value");
        ImGui::TableSetupColumn("Expression");
        ImGui::TableSetupColumn("Action");
        ImGui::TableHeadersRow();

        for (auto it = state.vars.cbegin(); it != state.vars.cend(); ) {
            const auto &v = *it;
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            ImGui::TextUnformatted(v.second.name);
            ImGui::TableNextColumn();
            if (v.second.value) {
                 ss << *v.second.value;
                 ImGui::TextUnformatted(ss.str());
                 ss.str("");
            } else ImGui::TextUnformatted("-");
            ImGui::TableNextColumn();
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0, 0, 0, 0});
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4{0, 0, 0, 0});
            ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{0, 0, 0, 0});
            if (ImGui::Button(v.second.expr.c_str())) {
                state.input_buf = v.second.expr;
                focus_input = true;
            }
            ImGui::PopStyleColor(3);
            ImGui::TableNextColumn();
            if (ImGui::Button(("Remove##" + v.second.name).c_str())) {
                it = state.vars.erase(it);
                do_eval = true;
            } else
                ++it;
        }

        ImGui::EndTable();
    }
    if (do_eval)
        state.eval();
}

int input_history_cbk(ImGuiInputTextCallbackData *data) {
    bool up = data->EventKey == ImGuiKey_UpArrow;

    if (up && state.history_entry > 0 || !up && state.history_entry < state.history.size()) {
        state.history_entry += up ? -1 : 1;
        if (data->BufTextLen > 0)
            data->DeleteChars(0, data->BufTextLen);
        if (state.history_entry < state.history.size()) {
            const auto &entry = state.history[state.history_entry];
            data->InsertChars(0, entry.c_str(), entry.c_str() + entry.size());
        }
    }

    return 0;
}

void draw_window() {
    constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse
        | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking;

    static bool focus_input = false;

    const ImGuiViewport* viewport = ImGui::GetMainViewport();
    ImGui::SetNextWindowPos(viewport->WorkPos);
    ImGui::SetNextWindowSize(viewport->WorkSize);
    if (ImGui::Begin("Main", nullptr, flags)) {
        if (ImGui::BeginChild("History", {0, 300}, true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
            for (const auto &line : state.history)
                ImGui::TextUnformatted(line);
            ImGui::EndChild();
        }

        if (state.error.has_value()) {
            ImGui::PushStyleColor(ImGuiCol_Text, {1,0,0,1});
            ImGui::TextUnformatted("Error: ");
            ImGui::PopStyleColor();
            ImGui::SameLine();
            ImGui::TextUnformatted(state.error.value());
        }

        ImGui::TextUnformatted("Input: ");
        ImGui::SameLine();
        if (ImGui::InputText("##Input", &state.input_buf, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory, input_history_cbk)) {
            state.handle_input();
            state.eval();
            ImGui::SetKeyboardFocusHere(-1); // Focuses input again
        }
        if (focus_input) {
            ImGui::SetKeyboardFocusHere(-1);
            focus_input = false;
        }

        if (state.last_eval_duration.has_value()) {
            ImGui::SameLine();
            ImGui::TextUnformatted(format_duration(state.last_eval_duration.value()));
        }

        ImGui::Spacing();
        ImGui::Separator();
        ImGui::Spacing();

        if (ImGui::BeginChild("Variables")) {
            draw_table(focus_input);
            ImGui::EndChild();
        }

        ImGui::End();
    }
}
