#include "write.h"

#include "eye_image.h"
#include "icon_image.h"
#include "lcd_reg.h"

typedef struct {
    uint16_t icon_id;
    uint16_t x;
    uint16_t y;
} icon_draw_t;

static uint16_t lcd_swap_color(uint16_t color)
{
    return (uint16_t)((color >> 8) | (color << 8));
}

static const icon_image_source_t *write_get_icon(uint16_t icon_id)
{
#if ICON_IMAGE_SOURCE_COUNT == 0
    (void)icon_id;
    return 0;
#else
    if (icon_id >= ICON_IMAGE_SOURCE_COUNT) {
        return 0;
    }

    return &icon_image_sources[icon_id];
#endif
}

static const eye_image_frame_t *write_get_frame(uint16_t image_id)
{
    if (image_id >= EYE_IMAGE_SOURCE_COUNT) {
        return 0;
    }

    const eye_image_source_t *source = &eye_image_sources[image_id];

    if (source->frame_count == 0) {
        return 0;
    }

    return &source->frames[0];
}

static void lcd_overlay_logic(uint16_t *pixel_buffer, uint16_t row_start, uint16_t row_count, const icon_draw_t *icons, uint32_t icon_count)
{
    for (uint32_t icon_index = 0; icon_index < icon_count; icon_index++) {
        const icon_draw_t *draw = &icons[icon_index];
        const icon_image_source_t *icon = write_get_icon(draw->icon_id);

        if (icon == 0 || icon->width == 0 || icon->height == 0) {
            continue;
        }
        if (icon->frame_count == 0) {
            continue;
        }
        if (draw->x >= LCD_WIDTH || draw->y >= LCD_HEIGHT) {
            continue;
        }

        const eye_image_frame_t *frame = &icon->frames[0];

        if (frame->runs == 0 || frame->run_count == 0) {
            continue;
        }

        uint16_t draw_width = icon->width;
        uint16_t draw_height = icon->height;

        if (draw_width > LCD_WIDTH - draw->x) {
            draw_width = LCD_WIDTH - draw->x;
        }
        if (draw_height > LCD_HEIGHT - draw->y) {
            draw_height = LCD_HEIGHT - draw->y;
        }

        uint16_t row_end = row_start + row_count;
        uint16_t overlay_y0 = draw->y;
        uint16_t overlay_y1 = draw->y + draw_height;

        if (overlay_y1 <= row_start || overlay_y0 >= row_end) {
            continue;
        }

        uint16_t copy_y0 = overlay_y0 > row_start ? overlay_y0 : row_start;
        uint16_t copy_y1 = overlay_y1 < row_end ? overlay_y1 : row_end;

        for (uint16_t y = copy_y0; y < copy_y1; y++) {
            uint16_t local_y = y - row_start;
            uint16_t image_y = y - draw->y;
            uint32_t icon_pixel_start = (uint32_t)image_y * icon->width;
            uint32_t icon_pixel_end = icon_pixel_start + draw_width;
            uint32_t icon_pixel_pos = 0;

            for (uint32_t run_index = 0; run_index < frame->run_count && icon_pixel_pos < icon_pixel_end; run_index++) {
                uint32_t run_start = icon_pixel_pos;
                uint32_t run_end = run_start + frame->runs[run_index].count;

                if (run_end > icon_pixel_start && run_start < icon_pixel_end) {
                    uint32_t copy_start = run_start > icon_pixel_start ? run_start : icon_pixel_start;
                    uint32_t copy_end = run_end < icon_pixel_end ? run_end : icon_pixel_end;
                    uint16_t color = lcd_swap_color(frame->runs[run_index].color);

                    for (uint32_t x = copy_start; x < copy_end; x++) {
                        pixel_buffer[(uint32_t)local_y * LCD_WIDTH + draw->x + (x - icon_pixel_start)] = color;
                    }
                }

                icon_pixel_pos = run_end;
            }
        }
    }
}

static void lcd_draw_image(const rle_structure_t *runs, uint32_t run_count, const icon_draw_t *icons, uint32_t icon_count)
{
    enum { ROWS_PER_TRANSFER = 8 };
    enum { PIXELS_PER_TRANSFER = LCD_WIDTH * ROWS_PER_TRANSFER };
    static uint16_t pixel_buffer[PIXELS_PER_TRANSFER];
    uint32_t run_index = 0;
    uint32_t run_remaining = 0;
    uint16_t run_color = 0;

    if (runs == 0) {
        return;
    }

    lcd_set_window(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);

    for (uint16_t row_start = 0; row_start < LCD_HEIGHT; row_start += ROWS_PER_TRANSFER) {
        uint16_t row_count = LCD_HEIGHT - row_start;
        uint32_t pixels_needed;
        uint32_t buffered_pixels = 0;

        if (row_count > ROWS_PER_TRANSFER) {
            row_count = ROWS_PER_TRANSFER;
        }
        pixels_needed = (uint32_t)LCD_WIDTH * row_count;

        while (buffered_pixels < pixels_needed) {
            if (run_remaining == 0) {
                if (run_index >= run_count) {
                    break;
                }
                run_color = lcd_swap_color(runs[run_index].color);
                run_remaining = runs[run_index].count;
                run_index++;
            }

            uint32_t pixels_to_copy = run_remaining;
            uint32_t pixels_left = pixels_needed - buffered_pixels;

            if (pixels_to_copy > pixels_left) {
                pixels_to_copy = pixels_left;
            }

            for (uint32_t i = 0; i < pixels_to_copy; i++) {
                pixel_buffer[buffered_pixels + i] = run_color;
            }

            buffered_pixels += pixels_to_copy;
            run_remaining -= pixels_to_copy;
        }

        while (buffered_pixels < pixels_needed) {
            pixel_buffer[buffered_pixels++] = 0;
        }

        if (icons != 0 && icon_count > 0) {
            lcd_overlay_logic(pixel_buffer, row_start, row_count, icons, icon_count);
        }

        lcd_write_pixels(pixel_buffer, pixels_needed * 2);
    }
}

void lcd_play_image(uint16_t image_id, uint16_t icon_id, uint16_t x, uint16_t y, uint32_t delay_ms)
{
    const icon_draw_t icon = {icon_id, x, y};
    const eye_image_frame_t *frame = write_get_frame(image_id);
    const icon_draw_t *icons = icon_id == LCD_NO_ICON ? 0 : &icon;
    uint32_t icon_count = icon_id == LCD_NO_ICON ? 0 : 1;

    if (frame != 0 && frame->runs != 0) {
        lcd_draw_image(frame->runs, frame->run_count, icons, icon_count);
    }

    lcd_delay_ms(delay_ms);
}
