#include <map>

#include "iidx_seg.h"
#include "games/io.h"
#include "games/iidx/iidx.h"
#include "util/logging.h"

namespace overlay::windows {

    uint32_t IIDX_SEGMENT_FONT_SIZE = 64;
    std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt;
    std::string IIDX_SEGMENT_LOCATION = "bottom";

    static const size_t TICKER_SIZE = 9;
    static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
    static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
    static const int PADDING_Y = 8;
    static const int PADDING_X = 4;

    static const std::map<char, std::pair<char, char>> CHARMAP = {\
        // period - add a space afterwards (game sends 'm' for period)
        {'m', {'.', ' '}},
        // exclamation mark (use ./ to make it look like one)
        {'!', {'.', '/'}},
        // font doesn't have tilde so using a dash instead
        {'~', {'-', '\0'}},
    };

    IIDXSegmentDisplay::IIDXSegmentDisplay(SpiceOverlay *overlay) : Window(overlay) {
        if (!DSEG_FONT) {
            log_fatal("iidx_seg", "DSEG_FONT is null");
        }

        this->title = "IIDX LED Segment Display";
        this->toggle_button = games::OverlayButtons::ToggleSubScreen;
        this->remove_window_padding = true;
        this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
        this->flags = ImGuiWindowFlags_NoTitleBar
                      | ImGuiWindowFlags_NoScrollbar
                      | ImGuiWindowFlags_NoResize
                      | ImGuiWindowFlags_NoNavFocus
                      | ImGuiWindowFlags_NoNavInputs
                      | ImGuiWindowFlags_NoDocking;

        if (IIDX_SEGMENT_FONT_COLOR.has_value()) {
            const auto rgb = IIDX_SEGMENT_FONT_COLOR.value();
            const auto rgba = IM_COL32_A_MASK | rgb;
            this->color = ImGui::ColorConvertU32ToFloat4(rgba);
        } else {
            this->color = RED;
        }
    }

    void IIDXSegmentDisplay::calculate_initial_window() {
        // ImGui::CalcTextSize doesn't seem to work here, so manually calculate
        ImGui::PushFont(DSEG_FONT);
        this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~.");
        this->init_size.x += PADDING_X * 2;
        this->init_size.y += PADDING_Y * 2;
        ImGui::PopFont();

        // initial horizontal position
        if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) {
            this->init_pos.x = 0;
        } else if (IIDX_SEGMENT_LOCATION.find("right") != std::string::npos) {
            this->init_pos.x = ImGui::GetIO().DisplaySize.x - this->init_size.x;
        } else {
            // center
            this->init_pos.x = ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2;
        }

        // initial vertical position
        if (IIDX_SEGMENT_LOCATION.rfind("top", 0) == 0) {
            this->init_pos.y = 0;
        } else {
            // bottom
            this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y;
        }
    }

    void IIDXSegmentDisplay::build_content() {
        char input_ticker[TICKER_SIZE];

        // get ticker content from game
        games::iidx::IIDX_LED_TICKER_LOCK.lock();
        memcpy(input_ticker, games::iidx::IIDXIO_LED_TICKER, TICKER_SIZE);
        games::iidx::IIDX_LED_TICKER_LOCK.unlock();

        // since every input character can result in up to two characters,
        // need double size for output
        const size_t TICKER_OUTPUT_SIZE = TICKER_SIZE * 2 + 1;
        char output_ticker[TICKER_OUTPUT_SIZE];
        int output_ticker_index = 0;

        // look at each input char and convert into output char(s)
        for (const auto c : input_ticker) {
            // see if there is an applicable rule for this input character
            if (0 == CHARMAP.count(c)) {
                // if not, copy the first character and keep going
                output_ticker[output_ticker_index] = c;
                output_ticker_index += 1;
                continue;
            }

            // there is a replacement rule for this input character
            auto replacements = CHARMAP.at(c);

            // replace the first character...
            output_ticker[output_ticker_index] = replacements.first;
            output_ticker_index += 1;

            // and optionally add the second character
            if (replacements.second != '\0') {
                output_ticker[output_ticker_index] = replacements.second;
                output_ticker_index += 1;
            }
        }
        if ((int)TICKER_OUTPUT_SIZE <= output_ticker_index) {
            log_fatal("iidx_seg", "{} is beyond array bounds", output_ticker_index);
        }

        // terminating null...
        output_ticker[output_ticker_index] = '\0';

        // finally, draw UI elements
        draw_ticker(output_ticker);
    }

    void IIDXSegmentDisplay::draw_ticker(char *ticker_string) {
        ImGui::PushFont(DSEG_FONT);

        const auto pos = ImVec2(PADDING_X, PADDING_Y);

        // to imitate LED "off"
        ImGui::SetCursorPos(pos);
        ImGui::PushStyleColor(ImGuiCol_Text, DARK_GRAY);
        ImGui::TextUnformatted("~.~.~.~.~.~.~.~.~.");
        ImGui::PopStyleColor();

        // ... then draw the LED "on" above it
        ImGui::SetCursorPos(pos);
        ImGui::PushStyleColor(ImGuiCol_Text, this->color);
        ImGui::TextUnformatted(ticker_string);
        ImGui::PopStyleColor();

        ImGui::PopFont();
    }
}
