#include "core/rendering/text.hpp"

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

#include "core/resources/resource_helper.hpp"

// Forward declarations de gif.h (inclòs des de jdraw8.cpp, no es pot incloure dos vegades)
struct rgb;
// NOLINTNEXTLINE(readability-identifier-naming) — exportat per external/gif.h, no controlem el nom.
extern auto LoadGif(unsigned char* data, unsigned short* w, unsigned short* h) -> unsigned char*;

Text::Text(const char* fnt_file, const char* gif_file) {
    loadBitmap(gif_file);
    loadFont(fnt_file);
}

// --- UTF-8 ---

auto Text::nextCodepoint(const char*& ptr) -> uint32_t {
    auto byte = static_cast<uint8_t>(*ptr);
    if (byte == 0) {
        return 0;
    }

    uint32_t cp = 0;
    int extra = 0;

    if (byte < 0x80) {
        cp = byte;
    } else if ((byte & 0xE0) == 0xC0) {
        cp = byte & 0x1F;
        extra = 1;
    } else if ((byte & 0xF0) == 0xE0) {
        cp = byte & 0x0F;
        extra = 2;
    } else if ((byte & 0xF8) == 0xF0) {
        cp = byte & 0x07;
        extra = 3;
    } else {
        ptr++;
        return 0xFFFD;
    }

    ptr++;
    for (int i = 0; i < extra; i++) {
        auto cont = static_cast<uint8_t>(*ptr);
        if ((cont & 0xC0) != 0x80) {
            return 0xFFFD;
        }
        cp = (cp << 6) | (cont & 0x3F);
        ptr++;
    }

    return cp;
}

// --- Càrrega de font ---

void Text::loadFont(const char* fnt_file) {
    auto buffer = ResourceHelper::loadFile(fnt_file);
    if (buffer.empty()) {
        std::cerr << "Text: unable to load font file: " << fnt_file << '\n';
        return;
    }

    std::istringstream stream(std::string(reinterpret_cast<const char*>(buffer.data()), buffer.size()));

    std::string line;
    int glyph_index = 0;

    while (std::getline(stream, line)) {
        // Ignora comentaris i línies buides
        if (line.empty() || line[0] == '#') {
            continue;
        }

        // Elimina comentaris inline
        auto comment_pos = line.find('#');
        if (comment_pos != std::string::npos) {
            line.resize(comment_pos);
        }

        // Parseja directives
        if (line.starts_with("box_width")) {
            sscanf(line.c_str(), "box_width %d", &box_width_);
            continue;
        }
        if (line.starts_with("box_height")) {
            sscanf(line.c_str(), "box_height %d", &box_height_);
            continue;
        }
        if (line.starts_with("columns")) {
            sscanf(line.c_str(), "columns %d", &columns_);
            continue;
        }
        if (line.starts_with("cell_spacing")) {
            sscanf(line.c_str(), "cell_spacing %d", &cell_spacing_);
            continue;
        }
        if (line.starts_with("row_spacing")) {
            sscanf(line.c_str(), "row_spacing %d", &row_spacing_);
            continue;
        }

        // Línies de glifos: "codepoint width"
        uint32_t codepoint = 0;
        int visual_width = 0;
        if (sscanf(line.c_str(), "%u %d", &codepoint, &visual_width) == 2) {
            int col = glyph_index % columns_;
            int row = glyph_index / columns_;

            GlyphInfo glyph{};
            glyph.x = col * (box_width_ + cell_spacing_) + cell_spacing_;
            glyph.y = row * (box_height_ + (row_spacing_ > 0 ? row_spacing_ : cell_spacing_)) + cell_spacing_;
            glyph.w = visual_width;

            glyphs_[codepoint] = glyph;
            glyph_index++;
        }
    }

    std::cout << "Text: loaded font with " << glyphs_.size() << " glyphs (" << box_width_ << "x" << box_height_ << ")\n";
}

void Text::loadBitmap(const char* gif_file) {
    auto buffer = ResourceHelper::loadFile(gif_file);
    if (buffer.empty()) {
        std::cerr << "Text: unable to load bitmap: " << gif_file << '\n';
        return;
    }

    // Extrau dimensions del header GIF (bytes 6-7 = width, 8-9 = height, little-endian)
    auto* raw = buffer.data();
    int w = raw[6] | (raw[7] << 8);
    int h = raw[8] | (raw[9] << 8);

    unsigned short gw = 0;
    unsigned short gh = 0;
    Uint8* pixels = LoadGif(raw, &gw, &gh);
    if (pixels == nullptr) {
        std::cerr << "Text: unable to decode GIF: " << gif_file << '\n';
        return;
    }

    bitmap_width_ = w;
    bitmap_height_ = h;
    bitmap_.assign(pixels, pixels + (static_cast<size_t>(w) * h));
    free(pixels);  // LoadGif usa malloc internament

    std::cout << "Text: bitmap loaded " << w << "x" << h << '\n';
}

// --- Renderitzat ---

auto Text::resolveGlyph(uint32_t cp) const -> const GlyphInfo* {
    auto it = glyphs_.find(cp);
    if (it != glyphs_.end()) {
        return &it->second;
    }
    it = glyphs_.find('?');
    return (it != glyphs_.end()) ? &it->second : nullptr;
}

void Text::blitGlyph(Uint32* pixel_data, int dst_x, int dst_y, const GlyphInfo& glyph, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const {
    const int GY_START = std::max(0, clip_y_min - dst_y);
    const int GY_END = std::min(box_height_, clip_y_max - dst_y);
    const int GX_START = std::max(0, clip_x_min - dst_x);
    const int GX_END = std::min(glyph.w, clip_x_max - dst_x);
    for (int gy = GY_START; gy < GY_END; gy++) {
        const int SRC_Y = glyph.y + gy;
        if (SRC_Y >= bitmap_height_) {
            continue;
        }
        const int DST_ROW = dst_y + gy;
        for (int gx = GX_START; gx < GX_END; gx++) {
            const int SRC_X = glyph.x + gx;
            if (SRC_X >= bitmap_width_) {
                continue;
            }
            const Uint8 PIXEL = bitmap_[SRC_X + (SRC_Y * bitmap_width_)];
            if (PIXEL != 0) {
                pixel_data[(dst_x + gx) + (DST_ROW * SCREEN_WIDTH)] = color;
            }
        }
    }
}

void Text::draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color) const {
    if (bitmap_.empty() || (pixel_data == nullptr)) {
        return;
    }
    const char* ptr = text;
    int cursor_x = x;
    while (*ptr != 0) {
        uint32_t cp = nextCodepoint(ptr);
        if (cp == 0) {
            break;
        }
        const GlyphInfo* glyph = resolveGlyph(cp);
        if (glyph == nullptr) {
            cursor_x += box_width_;
            continue;
        }
        blitGlyph(pixel_data, cursor_x, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT);
        cursor_x += glyph->w + 1;
    }
}

void Text::drawCentered(Uint32* pixel_data, int y, const char* text, Uint32 color) const {
    int w = width(text);
    int x = (SCREEN_WIDTH - w) / 2;
    draw(pixel_data, x, y, text, color);
}

void Text::drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const {
    if (bitmap_.empty() || (pixel_data == nullptr)) {
        return;
    }
    if (y + box_height_ <= clip_y_min || y >= clip_y_max) {
        return;
    }
    const int X_MIN = std::max(0, clip_x_min);
    const int X_MAX = std::min(SCREEN_WIDTH, clip_x_max);
    const int Y_MIN = std::max(0, clip_y_min);
    const int Y_MAX = std::min(SCREEN_HEIGHT, clip_y_max);
    const char* ptr = text;
    int cursor_x = x;
    while (*ptr != 0) {
        uint32_t cp = nextCodepoint(ptr);
        if (cp == 0) {
            break;
        }
        const GlyphInfo* glyph = resolveGlyph(cp);
        if (glyph == nullptr) {
            cursor_x += box_width_;
            continue;
        }
        if (cursor_x + glyph->w > X_MIN && cursor_x < X_MAX) {
            blitGlyph(pixel_data, cursor_x, y, *glyph, color, X_MIN, X_MAX, Y_MIN, Y_MAX);
        }
        cursor_x += glyph->w + 1;
    }
}

void Text::drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int cell_w) const {
    if (bitmap_.empty() || (pixel_data == nullptr)) {
        return;
    }
    const char* ptr = text;
    int cursor_x = x;
    while (*ptr != 0) {
        uint32_t cp = nextCodepoint(ptr);
        if (cp == 0) {
            break;
        }
        const GlyphInfo* glyph = resolveGlyph(cp);
        if (glyph == nullptr) {
            cursor_x += cell_w;
            continue;
        }
        const int GLYPH_X = cursor_x + ((cell_w - glyph->w) / 2);
        blitGlyph(pixel_data, GLYPH_X, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT);
        cursor_x += cell_w;
    }
}

void Text::drawMonoDigits(Uint32* pixel_data, int x, int y, const char* text, Uint32 color, int digit_cell_w) const {
    if (bitmap_.empty() || (pixel_data == nullptr)) {
        return;
    }
    const char* ptr = text;
    int cursor_x = x;
    bool first = true;
    while (*ptr != 0) {
        uint32_t cp = nextCodepoint(ptr);
        if (cp == 0) {
            break;
        }
        if (!first) {
            cursor_x += 1;  // kerning
        }
        const GlyphInfo* glyph = resolveGlyph(cp);
        if (glyph == nullptr) {
            cursor_x += box_width_;
            first = false;
            continue;
        }
        const bool IS_DIGIT = (cp >= '0' && cp <= '9');
        const int GLYPH_X = IS_DIGIT ? cursor_x + ((digit_cell_w - glyph->w) / 2) : cursor_x;
        blitGlyph(pixel_data, GLYPH_X, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT);
        cursor_x += IS_DIGIT ? digit_cell_w : glyph->w;
        first = false;
    }
}

auto Text::widthMonoDigits(const char* text, int digit_cell_w) const -> int {
    const char* ptr = text;
    int w = 0;
    bool first = true;
    while (*ptr != 0) {
        uint32_t cp = nextCodepoint(ptr);
        if (cp == 0) {
            break;
        }
        if (!first) {
            w += 1;  // kerning
        }
        first = false;
        bool is_digit = (cp >= '0' && cp <= '9');
        if (is_digit) {
            w += digit_cell_w;
        } else {
            auto it = glyphs_.find(cp);
            if (it == glyphs_.end()) {
                it = glyphs_.find('?');
            }
            if (it != glyphs_.end()) {
                w += it->second.w;
            } else {
                w += box_width_;
            }
        }
    }
    return w;
}

auto Text::widthMono(const char* text, int cell_w) -> int {
    const char* ptr = text;
    int count = 0;
    while (*ptr != 0) {
        uint32_t cp = nextCodepoint(ptr);
        if (cp == 0) {
            break;
        }
        count++;
    }
    return count * cell_w;
}

auto Text::width(const char* text) const -> int {
    const char* ptr = text;
    int w = 0;
    bool first = true;

    while (*ptr != 0) {
        uint32_t cp = nextCodepoint(ptr);
        if (cp == 0) {
            break;
        }

        auto it = glyphs_.find(cp);
        if (it == glyphs_.end()) {
            it = glyphs_.find('?');
        }

        if (!first) {
            w += 1;  // kerning
        }
        first = false;

        if (it != glyphs_.end()) {
            w += it->second.w;
        } else {
            w += box_width_;
        }
    }

    return w;
}
