/*
 * face_lcd.c — STM32 LCD Display Driver for Face Animations
 *
 * Implements low-level LCD framebuffer management and display control.
 * Supports 1-bit monochrome displays (SSD1306, etc.) via SPI.
 *
 * HARDWARE:
 * - SPI2 (PB13=SCK, PB14=MISO, PB15=MOSI) — already configured for OSD
 * - CS (GPIO) for LCD chip select
 * - DC (GPIO) for data/command mode select
 * - RES (GPIO) optional reset
 *
 * NOTE: SPI2 is currently used by OSD (MAX7456). For face LCD, we would
 * typically use a separate SPI or I2C. This implementation assumes
 * a dedicated I2C or separate SPI interface. Configure LCD_INTERFACE
 * in face_lcd.h to match your hardware.
 */

#include "face_lcd.h"
#include <string.h>

/* === State Variables === */
static uint8_t lcd_framebuffer[LCD_FBSIZE];
static volatile uint32_t frame_counter = 0;
static volatile bool flush_requested = false;
static volatile bool transfer_busy = false;

/* === Private Functions === */

/**
 * Initialize hardware (SPI/I2C) and LCD controller.
 * Sends initialization sequence to put display in active mode.
 */
static void lcd_hardware_init(void) {
    /* TODO: Implement hardware-specific initialization
     * - Configure SPI/I2C pins and clock
     * - Send controller init sequence (power on, set contrast, etc.)
     * - Clear display
     *
     * For SSD1306 (common monochrome):
     *   - Send 0xAE (display off)
     *   - Set contrast 0x81, 0x7F
     *   - Set clock div ratio, precharge, comdesat
     *   - Set address mode, column/page range
     *   - Send 0xAF (display on)
     */
}

/**
 * Push framebuffer to display via SPI/I2C DMA transfer.
 * Handles paging for monochrome displays (8 pixels per byte, horizontal pages).
 */
static void lcd_transfer_fb(void) {
    transfer_busy = true;

    /* TODO: Implement DMA/blocking transfer
     * For SSD1306 (8-pixel pages):
     *   For each page (8 rows):
     *     - Send page address command
     *     - Send column address command
     *     - DMA transfer 128 bytes (1 row of page data)
     *
     * Can use SPI DMA for async transfer or blocking transfer.
     * Set transfer_busy=false when complete (in ISR or blocking).
     */

    transfer_busy = false;
}

/* === Public API Implementation === */

void face_lcd_init(void) {
    memset(lcd_framebuffer, 0, LCD_FBSIZE);
    frame_counter = 0;
    flush_requested = false;
    transfer_busy = false;
    lcd_hardware_init();
}

void face_lcd_clear(void) {
    memset(lcd_framebuffer, 0, LCD_FBSIZE);
}

void face_lcd_pixel(uint16_t x, uint16_t y, lcd_color_t color) {
    /* Bounds check */
    if (x >= LCD_WIDTH || y >= LCD_HEIGHT)
        return;

#if LCD_BPP == 1
    /* Monochrome: pack 8 pixels per byte, LSB = leftmost pixel */
    uint16_t byte_idx = (y / 8) * LCD_WIDTH + x;
    uint8_t bit_pos = y % 8;

    if (color)
        lcd_framebuffer[byte_idx] |= (1 << bit_pos);
    else
        lcd_framebuffer[byte_idx] &= ~(1 << bit_pos);
#else
    /* RGB565: 2 bytes per pixel */
    uint16_t pixel_idx = y * LCD_WIDTH + x;
    ((uint16_t *)lcd_framebuffer)[pixel_idx] = color;
#endif
}

void face_lcd_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1,
                   lcd_color_t color) {
    /* Bresenham line algorithm */
    int16_t dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
    int16_t dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
    int16_t sx = (x0 < x1) ? 1 : -1;
    int16_t sy = (y0 < y1) ? 1 : -1;
    int16_t err = (dx > dy) ? (dx / 2) : -(dy / 2);

    int16_t x = x0, y = y0;
    while (1) {
        face_lcd_pixel(x, y, color);
        if (x == x1 && y == y1)
            break;
        int16_t e2 = err;
        if (e2 > -dx) {
            err -= dy;
            x += sx;
        }
        if (e2 < dy) {
            err += dx;
            y += sy;
        }
    }
}

void face_lcd_circle(uint16_t cx, uint16_t cy, uint16_t r, lcd_color_t color) {
    /* Midpoint circle algorithm */
    int16_t x = r, y = 0;
    int16_t err = 0;

    while (x >= y) {
        face_lcd_pixel(cx + x, cy + y, color);
        face_lcd_pixel(cx + y, cy + x, color);
        face_lcd_pixel(cx - y, cy + x, color);
        face_lcd_pixel(cx - x, cy + y, color);
        face_lcd_pixel(cx - x, cy - y, color);
        face_lcd_pixel(cx - y, cy - x, color);
        face_lcd_pixel(cx + y, cy - x, color);
        face_lcd_pixel(cx + x, cy - y, color);

        if (err <= 0) {
            y++;
            err += 2 * y + 1;
        } else {
            x--;
            err -= 2 * x + 1;
        }
    }
}

void face_lcd_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
                        lcd_color_t color) {
    for (uint16_t row = y; row < y + h && row < LCD_HEIGHT; row++) {
        for (uint16_t col = x; col < x + w && col < LCD_WIDTH; col++) {
            face_lcd_pixel(col, row, color);
        }
    }
}

void face_lcd_flush(void) {
    flush_requested = true;
}

bool face_lcd_is_busy(void) {
    return transfer_busy;
}

void face_lcd_tick(void) {
    frame_counter++;

    /* Request flush every N frames to achieve LCD_REFRESH_HZ */
    uint32_t frames_per_flush = (1000 / LCD_REFRESH_HZ) / 33;  /* ~30ms per frame */
    if (frame_counter % frames_per_flush == 0) {
        flush_requested = true;
    }

    /* Perform transfer if requested and not busy */
    if (flush_requested && !transfer_busy) {
        flush_requested = false;
        lcd_transfer_fb();
    }
}

uint8_t *face_lcd_get_fb(void) {
    return lcd_framebuffer;
}
