#include <hex/api/content_registry/hex_editor.hpp>

#include <imgui.h>
#include <imgui_internal.h>
#include <hex/api/imhex_api/hex_editor.hpp>
#include <hex/ui/imgui_imhex_extensions.h>

#include <hex/helpers/utils.hpp>
#include <pl/patterns/pattern.hpp>
#include <wolv/utils/lock.hpp>

namespace hex::plugin::builtin {

    namespace {

        void entropyMiniMapVisualizer(u64, std::span<const u8> data, std::vector<ImColor> &output) {
            std::array<u8, 256> frequencies = { 0 };
            for (u8 byte : data)
                frequencies[byte] += 1;

            double entropy = 0.0;
            for (u32 frequency : frequencies) {
                if (frequency == 0)
                    continue;

                double probability = static_cast<double>(frequency) / data.size();
                entropy -= probability * std::log2(probability);
            }

            // Calculate color
            ImColor color = ImColor::HSV(0.0F, 0.0F, 1.0F);

            if (entropy > 0.0) {
                double hue = std::clamp(entropy / 8.0, 0.0, 1.0);
                color = ImColor::HSV(static_cast<float>(hue) / 0.75F, 0.8F, 1.0F);
            }

            output.push_back(color);
        }

        void zerosCountMiniMapVisualizer(u64, std::span<const u8> data, std::vector<ImColor> &output) {
            u32 zerosCount = 0;
            for (u8 byte : data) {
                if (byte == 0x00)
                    zerosCount += 1;
            }

            output.push_back(ImColor::HSV(0.0F, 0.0F, 1.0F - (double(zerosCount) / data.size())));
        }

        void zerosMiniMapVisualizer(u64, std::span<const u8> data, std::vector<ImColor> &output) {
            for (u8 byte : data) {
                if (byte == 0x00)
                    output.emplace_back(1.0F, 1.0F, 1.0F, 1.0F);
                else
                    output.emplace_back(0.0F, 0.0F, 0.0F, 1.0F);
            }
        }

        void byteTypeMiniMapVisualizer(u64, std::span<const u8> data, std::vector<ImColor> &output) {
            for (u8 byte : data) {
                if (std::isalpha(byte))
                    output.emplace_back(1.0F, 0.0F, 0.0F, 1.0F);
                else if (std::isdigit(byte))
                    output.emplace_back(0.0F, 1.0F, 0.0F, 1.0F);
                else if (std::isspace(byte))
                    output.emplace_back(0.0F, 0.0F, 1.0F, 1.0F);
                else if (std::iscntrl(byte))
                    output.emplace_back(0.5F, 0.5F, 0.5F, 1.0F);
                else
                    output.emplace_back(0.0F, 0.0F, 0.0F, 1.0F);
            }
        }

        void asciiCountMiniMapVisualizer(u64, std::span<const u8> data, std::vector<ImColor> &output) {
            u8 asciiCount = 0;
            for (u8 byte : data) {
                if (std::isprint(byte))
                    asciiCount += 1;
            }

            output.push_back(ImColor::HSV(0.5F, 0.5F, (double(asciiCount) / data.size())));
        }

        void byteMagnitudeMiniMapVisualizer(u64, std::span<const u8> data, std::vector<ImColor> &output) {
            for (u8 byte : data) {
                output.push_back(ImColor::HSV(0.0F, 0.0F, static_cast<float>(byte) / 255.0F));
            }
        }

        using ColorFunc = ImColor(*)(std::span<const u8> data);
        void colorMinimapVisualizer(u64, std::span<const u8> data, std::vector<ImColor> &output, size_t colorSize, ColorFunc func) {
            size_t count = data.size() / colorSize;
            for (size_t i = 0; i < count; i += 1) {
                ImColor color = func(data.subspan(i * colorSize));
                output.push_back(color);
            }
        }

        void rgba8MiniMapVisualizer(u64 address, std::span<const u8> data, std::vector<ImColor> &output) {
            colorMinimapVisualizer(address, data, output, 4, [](std::span<const u8> subData) -> ImColor {
                return {subData[0], subData[1], subData[2], 0xFF};
            });
        }

        void rgb565MiniMapVisualizer(u64 address, std::span<const u8> data, std::vector<ImColor> &output) {
            colorMinimapVisualizer(address, data, output, 2, [](std::span<const u8> subData) -> ImColor {
                u8 r = (subData[0] & 0xF8);
                u8 g = ((subData[0] & 0x07) << 5) | ((subData[1] & 0xE0) >> 3);
                u8 b = (subData[1] & 0x1F) << 3;
                return {r, g, b, 0xFF};
            });
        }

        void highlightsMiniMapVisualizer(u64 address, std::span<const u8> data, std::vector<ImColor> &output) {
            for (size_t i = 0; i < data.size(); i += 1) {
                std::optional<ImColor> result;
                for (const auto &[id, callback] : ImHexApi::HexEditor::impl::getBackgroundHighlightingFunctions()) {
                    if (auto color = callback(address + i, data.data() + i, 1, result.has_value()); color.has_value())
                        result = color;
                }

                if (!result.has_value()) {
                    for (const auto &[id, highlighting] : ImHexApi::HexEditor::impl::getBackgroundHighlights()) {
                        if (highlighting.getRegion().overlaps({ .address=address, .size=1 })) {
                            result = highlighting.getColor();
                            break;
                        }
                    }
                }

                if (result.has_value()) {
                    result->Value.w = 1.0F;
                } else {
                    if (auto region = ImHexApi::HexEditor::getSelection(); region.has_value()) {
                        if (region->overlaps({ .address=address + i, .size=1 }))
                            result = 0x60C08080;
                    }
                }

                output.push_back(result.value_or(ImColor()));
            }
        }

    }

    void registerMiniMapVisualizers() {
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.highlights",       highlightsMiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.entropy",          entropyMiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.zero_count",       zerosCountMiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.zeros",            zerosMiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.ascii_count",      asciiCountMiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.byte_type",        byteTypeMiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.byte_magnitude",   byteMagnitudeMiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.rgba8",            rgba8MiniMapVisualizer);
        ContentRegistry::HexEditor::addMiniMapVisualizer("hex.builtin.minimap_visualizer.rgb565",           rgb565MiniMapVisualizer);
    }

}