#include "lgfx_text.h"
#include "zlib.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>

typedef struct
{
    uint8_t  mask;
    uint8_t  lead;
    uint32_t beg;
    uint32_t end;
    int32_t  bits_stored;
} utf_t;

static inline int32_t min_i32(int32_t x, int32_t y) { return x < y ? x : y; }
static inline int32_t max_i32(int32_t x, int32_t y) { return x > y ? x : y; }

static int32_t utf8_len(const uint8_t ch);
static uint32_t next_cp(uint8_t **string);
static FontProperties font_properties_default(void);

static void draw_hline_local(int32_t x, int32_t y, int32_t length, uint8_t color,
                             uint8_t *framebuffer, int32_t fb_width, int32_t fb_height);

static void draw_char_local(const LGFXfont *font,
                            uint8_t *buffer,
                            int32_t *cursor_x,
                            int32_t cursor_y,
                            uint16_t buf_width_bytes,
                            uint16_t buf_height,
                            uint32_t cp,
                            const FontProperties *props);

static void get_char_bounds_local(const LGFXfont *font,
                                  uint32_t cp,
                                  int32_t *x,
                                  int32_t *y,
                                  int32_t *minx,
                                  int32_t *miny,
                                  int32_t *maxx,
                                  int32_t *maxy,
                                  const FontProperties *props);

static utf_t *utf[] = {
    [0] = &(utf_t){0b00111111, 0b10000000, 0,       0,        6},
    [1] = &(utf_t){0b01111111, 0b00000000, 0x0000,  0x007F,   7},
    [2] = &(utf_t){0b00011111, 0b11000000, 0x0080,  0x07FF,   5},
    [3] = &(utf_t){0b00001111, 0b11100000, 0x0800,  0xFFFF,   4},
    [4] = &(utf_t){0b00000111, 0b11110000, 0x10000, 0x10FFFF, 3},
    &(utf_t){0},
};

void lgfx_get_glyph(const LGFXfont *font, uint32_t code_point, LGFXglyph **glyph)
{
    UnicodeInterval *intervals = font->intervals;
    *glyph = NULL;

    for (int32_t i = 0; i < (int32_t)font->interval_count; i++)
    {
        UnicodeInterval *interval = &intervals[i];
        if (code_point >= interval->first && code_point <= interval->last)
        {
            *glyph = &font->glyph[interval->offset + (code_point - interval->first)];
            return;
        }
        if (code_point < interval->first) return;
    }
}

void lgfx_get_text_bounds(const LGFXfont *font, const char *string, int32_t *x, int32_t *y, int32_t *x1, int32_t *y1, int32_t *w, int32_t *h, const FontProperties *properties)
{
    FontProperties props = (properties == NULL) ? font_properties_default() : *properties;
    if (*string == '\0') { *w = 0; *h = 0; *y1 = *y; *x1 = *x; return; }

    int32_t minx = 100000, miny = 100000, maxx = -1, maxy = -1;
    int32_t original_x = *x;
    uint32_t c;
    while ((c = next_cp((uint8_t **)&string)))
    {
        get_char_bounds_local(font, c, x, y, &minx, &miny, &maxx, &maxy, &props);
    }
    *x1 = min_i32(original_x, minx);
    *w = maxx - *x1;
    *y1 = miny;
    *h = maxy - miny;
}

void lgfx_write_mode(const LGFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer, int32_t fb_width, int32_t fb_height, DrawMode_t mode, const FontProperties *properties)
{
    (void)mode;
    if (*string == '\0') return;
    FontProperties props = (properties == NULL) ? font_properties_default() : *properties;

    int32_t x1 = 0, y1 = 0, w = 0, h = 0;
    int32_t tmp_cur_x = *cursor_x;
    int32_t tmp_cur_y = *cursor_y;
    lgfx_get_text_bounds(font, string, &tmp_cur_x, &tmp_cur_y, &x1, &y1, &w, &h, &props);

    uint8_t *buffer;
    int32_t buf_width_bytes;
    int32_t buf_height;
    int32_t baseline_height = *cursor_y - y1;
    int32_t local_cursor_x = 0;
    int32_t local_cursor_y = 0;

    if (framebuffer == NULL)
    {
        buf_width_bytes = (w / 2 + w % 2);
        buf_height = h;
        buffer = (uint8_t *)malloc(buf_width_bytes * buf_height);
        if (!buffer) return;
        memset(buffer, 0xFF, buf_width_bytes * buf_height);
        local_cursor_y = buf_height - baseline_height;
    }
    else
    {
        buf_width_bytes = fb_width / 2;
        buf_height = fb_height;
        buffer = framebuffer;
        local_cursor_x = *cursor_x;
        local_cursor_y = *cursor_y;
    }

    uint32_t c;
    int32_t cursor_x_init = local_cursor_x;
    int32_t cursor_y_init = local_cursor_y;

    if (props.flags & DRAW_BACKGROUND)
    {
        uint8_t bg = props.bg_color;
        for (int32_t l = 0; l < font->advance_y; l++)
        {
            draw_hline_local(local_cursor_x, local_cursor_y - (font->advance_y - baseline_height) + l, w, bg << 4 | bg, buffer, (framebuffer == NULL) ? w : fb_width, buf_height);
        }
    }

    while ((c = next_cp((uint8_t **)&string)))
    {
        draw_char_local(font, buffer, &local_cursor_x, local_cursor_y, buf_width_bytes, buf_height, c, &props);
    }
    *cursor_x += local_cursor_x - cursor_x_init;
    *cursor_y += local_cursor_y - cursor_y_init;

    if (framebuffer == NULL) free(buffer);
}

// 수정됨: color를 받아 FontProperties에 세팅 후 write_mode 호출
void lgfx_writeln(const LGFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer, int32_t fb_width, int32_t fb_height, uint8_t color)
{
    FontProperties props = font_properties_default();
    props.fg_color = color; // 외부에서 넘겨받은 색상(0 또는 15/16) 적용
    
    lgfx_write_mode(font, string, cursor_x, cursor_y, framebuffer, fb_width, fb_height, BLACK_ON_WHITE, &props);
}

void lgfx_write_string(const LGFXfont *font, const char *string, int32_t *cursor_x, int32_t *cursor_y, uint8_t *framebuffer, int32_t fb_width, int32_t fb_height)
{
    char *token, *newstring, *tofree;
    if (string == NULL) return;
    tofree = newstring = strdup(string);
    if (newstring == NULL) return;

    int32_t line_start = *cursor_x;
    while ((token = strsep(&newstring, "\n")) != NULL)
    {
        *cursor_x = line_start;
        // 기본 출력용으로 0(Black) 전달
        lgfx_writeln(font, token, cursor_x, cursor_y, framebuffer, fb_width, fb_height, 0);
        *cursor_y += font->advance_y;
    }
    free(tofree);
}

static int32_t utf8_len(const uint8_t ch)
{
    int32_t len = 0;
    for (utf_t **u = utf; *u; ++u)
    {
        if ((ch & ~(*u)->mask) == (*u)->lead) break;
        ++len;
    }
    return len;
}

static uint32_t next_cp(uint8_t **string)
{
    if (**string == 0) return 0;
    int32_t bytes = utf8_len(**string);
    uint8_t *chr = *string;
    *string += bytes;
    int32_t shift = utf[0]->bits_stored * (bytes - 1);
    uint32_t codep = (*chr++ & utf[bytes]->mask) << shift;
    for (int32_t i = 1; i < bytes; ++i, ++chr)
    {
        shift -= utf[0]->bits_stored;
        codep |= ((uint8_t)*chr & utf[0]->mask) << shift;
    }
    return codep;
}

static FontProperties font_properties_default(void)
{
    FontProperties props = { .fg_color = 0, .bg_color = 15, .fallback_glyph = 0, .flags = 0 };
    return props;
}

static void draw_hline_local(int32_t x, int32_t y, int32_t length, uint8_t color, uint8_t *framebuffer, int32_t fb_width, int32_t fb_height)
{
    if (y < 0 || y >= fb_height) return;
    int32_t fb_width_bytes = fb_width / 2;
    for (int32_t i = 0; i < length; i++)
    {
        int32_t xx = x + i;
        if (xx < 0 || xx >= fb_width) continue;
        uint8_t *buf_ptr = &framebuffer[y * fb_width_bytes + xx / 2];
        if (xx & 1) *buf_ptr = (*buf_ptr & 0x0F) | (color & 0xF0);
        else        *buf_ptr = (*buf_ptr & 0xF0) | (color & 0x0F);
    }
}

static void draw_char_local(const LGFXfont *font, uint8_t *buffer, int32_t *cursor_x, int32_t cursor_y, uint16_t buf_width_bytes, uint16_t buf_height, uint32_t cp, const FontProperties *props)
{
    LGFXglyph *glyph;
    lgfx_get_glyph(font, cp, &glyph);
    if (!glyph) lgfx_get_glyph(font, props->fallback_glyph, &glyph);
    if (!glyph) return;

    uint32_t offset = glyph->data_offset;
    uint8_t width = glyph->width;
    uint8_t height = glyph->height;
    int32_t left = glyph->left;
    int32_t byte_width = (width + 7) / 8;
    unsigned long bitmap_size = byte_width * height;
    uint8_t *bitmap = NULL;

    if (font->compressed)
    {
        bitmap = (uint8_t *)malloc(bitmap_size);
        if (!bitmap) return;
        if (uncompress(bitmap, &bitmap_size, &font->bitmap[offset], glyph->compressed_size) != Z_OK)
        {
            free(bitmap); return;
        }
    }
    else bitmap = &font->bitmap[offset];

    for (int32_t y = 0; y < height; y++)
    {
        int32_t yy = cursor_y - glyph->top + y;
        if (yy < 0 || yy >= buf_height) continue;
        int32_t start_pos = *cursor_x + left;

        for (int32_t x = 0; x < width; x++)
        {
            int32_t xx = start_pos + x;
            if (xx < 0 || xx >= buf_width_bytes * 2) continue;

            uint8_t src = bitmap[y * byte_width + (x / 8)];
            bool on = (src & (0x80 >> (x & 7))) != 0;
            if (!on) continue;

            uint32_t buf_pos = yy * buf_width_bytes + xx / 2;
            uint8_t old = buffer[buf_pos];
            
            // props->fg_color(0:검정, 15/16:빨강)를 4비트 데이터로 저장
            uint8_t fg = props->fg_color & 0x0F;

            if ((xx & 1) == 0) buffer[buf_pos] = (old & 0xF0) | fg;
            else               buffer[buf_pos] = (old & 0x0F) | (fg << 4);
        }
    }
    if (font->compressed) free(bitmap);
    *cursor_x += glyph->advance_x;
}

static void get_char_bounds_local(const LGFXfont *font, uint32_t cp, int32_t *x, int32_t *y, int32_t *minx, int32_t *miny, int32_t *maxx, int32_t *maxy, const FontProperties *props)
{
    LGFXglyph *glyph;
    lgfx_get_glyph(font, cp, &glyph);
    if (!glyph) lgfx_get_glyph(font, props->fallback_glyph, &glyph);
    if (!glyph) return;

    int32_t x1 = *x + glyph->left;
    int32_t y1 = *y + (glyph->top - glyph->height);
    int32_t x2 = x1 + glyph->width;
    int32_t y2 = y1 + glyph->height;

    if (props->flags & DRAW_BACKGROUND)
    {
        *minx = min_i32(*x, min_i32(*minx, x1));
        *maxx = max_i32(max_i32(*x + glyph->advance_x, x2), *maxx);
        *miny = min_i32(*y + font->descender, min_i32(*miny, y1));
        *maxy = max_i32(font->descender + font->advance_y, max_i32(*maxy, y2));
    }
    else
    {
        if (x1 < *minx) *minx = x1;
        if (y1 < *miny) *miny = y1;
        if (x2 > *maxx) *maxx = x2;
        if (y2 > *maxy) *maxy = y2;
    }
    *x += glyph->advance_x;
}