/**
 * DOOM for ESP32-S3 - Arduino Framework Port
 * SPI LCD Driver for ILI9341 (320x240)
 *
 * Using Hardware SPI for speed
 */

#include <Arduino.h>
#include <SPI.h>

// Pin definitions (from build flags)
#ifndef PIN_NUM_MOSI
#define PIN_NUM_MOSI 11
#endif
#ifndef PIN_NUM_CLK
#define PIN_NUM_CLK 12
#endif
#ifndef PIN_NUM_CS
#define PIN_NUM_CS 10
#endif
#ifndef PIN_NUM_DC
#define PIN_NUM_DC 8
#endif
#ifndef PIN_NUM_RST
#define PIN_NUM_RST 9
#endif
#ifndef PIN_NUM_BCKL
#define PIN_NUM_BCKL 2
#endif

// Display dimensions
#define LCD_WIDTH 320
#define LCD_HEIGHT 240

// SPI instance
static SPIClass *hspi = NULL;

// LCD palette (RGB565, from DOOM engine)
extern "C" int16_t lcdpal[256];

// Send command
static void lcd_cmd(uint8_t cmd) {
    digitalWrite(PIN_NUM_DC, LOW);
    digitalWrite(PIN_NUM_CS, LOW);
    hspi->transfer(cmd);
    digitalWrite(PIN_NUM_CS, HIGH);
}

// Send data byte
static void lcd_data(uint8_t data) {
    digitalWrite(PIN_NUM_DC, HIGH);
    digitalWrite(PIN_NUM_CS, LOW);
    hspi->transfer(data);
    digitalWrite(PIN_NUM_CS, HIGH);
}

// Initialize display
static void lcd_init() {
    Serial.println("Reset display...");
    digitalWrite(PIN_NUM_RST, LOW);
    delay(100);
    digitalWrite(PIN_NUM_RST, HIGH);
    delay(200);

    lcd_cmd(0x01); delay(150);  // Software reset
    lcd_cmd(0x11); delay(150);  // Sleep out
    lcd_cmd(0x28); delay(10);   // Display OFF

    // Power Control A
    lcd_cmd(0xCB);
    lcd_data(0x39); lcd_data(0x2C); lcd_data(0x00);
    lcd_data(0x34); lcd_data(0x02);

    // Power Control B
    lcd_cmd(0xCF);
    lcd_data(0x00); lcd_data(0xC1); lcd_data(0x30);

    // Driver Timing Control A
    lcd_cmd(0xE8);
    lcd_data(0x85); lcd_data(0x00); lcd_data(0x78);

    // Driver Timing Control B
    lcd_cmd(0xEA);
    lcd_data(0x00); lcd_data(0x00);

    // Power On Sequence Control
    lcd_cmd(0xED);
    lcd_data(0x64); lcd_data(0x03); lcd_data(0x12); lcd_data(0x81);

    // Pump ratio control
    lcd_cmd(0xF7);
    lcd_data(0x20);

    // Power Control 1
    lcd_cmd(0xC0);
    lcd_data(0x23);

    // Power Control 2
    lcd_cmd(0xC1);
    lcd_data(0x10);

    // VCOM Control 1
    lcd_cmd(0xC5);
    lcd_data(0x3E); lcd_data(0x28);

    // VCOM Control 2
    lcd_cmd(0xC7);
    lcd_data(0x86);

    // Memory Access Control - LANDSCAPE
    lcd_cmd(0x36);
    lcd_data(0x28);

    // Pixel Format - 16 bits
    lcd_cmd(0x3A);
    lcd_data(0x55);

    // Frame Rate
    lcd_cmd(0xB1);
    lcd_data(0x00); lcd_data(0x18);

    // Display Function Control
    lcd_cmd(0xB6);
    lcd_data(0x08); lcd_data(0x82); lcd_data(0x27);

    // Gamma Function Disable
    lcd_cmd(0xF2);
    lcd_data(0x00);

    // Gamma Set
    lcd_cmd(0x26);
    lcd_data(0x01);

    // Positive Gamma
    lcd_cmd(0xE0);
    lcd_data(0x0F); lcd_data(0x31); lcd_data(0x2B); lcd_data(0x0C);
    lcd_data(0x0E); lcd_data(0x08); lcd_data(0x4E); lcd_data(0xF1);
    lcd_data(0x37); lcd_data(0x07); lcd_data(0x10); lcd_data(0x03);
    lcd_data(0x0E); lcd_data(0x09); lcd_data(0x00);

    // Negative Gamma
    lcd_cmd(0xE1);
    lcd_data(0x00); lcd_data(0x0E); lcd_data(0x14); lcd_data(0x03);
    lcd_data(0x11); lcd_data(0x07); lcd_data(0x31); lcd_data(0xC1);
    lcd_data(0x48); lcd_data(0x08); lcd_data(0x0F); lcd_data(0x0C);
    lcd_data(0x31); lcd_data(0x36); lcd_data(0x0F);

    lcd_cmd(0x11); delay(120);  // Sleep Out
    lcd_cmd(0x29); delay(50);   // Display ON

    Serial.println("ILI9341 initialized!");
}

// Set address window
static void setWindow(int x0, int y0, int x1, int y1) {
    lcd_cmd(0x2A);
    lcd_data(x0 >> 8);
    lcd_data(x0 & 0xFF);
    lcd_data(x1 >> 8);
    lcd_data(x1 & 0xFF);

    lcd_cmd(0x2B);
    lcd_data(y0 >> 8);
    lcd_data(y0 & 0xFF);
    lcd_data(y1 >> 8);
    lcd_data(y1 & 0xFF);

    lcd_cmd(0x2C);
}

// Line buffer for faster transfer
static uint8_t lineBuf[LCD_WIDTH * 2];

// Public API functions
extern "C" {

void spi_lcd_init() {
    Serial.println("=== spi_lcd_init() Hardware SPI ===");
    Serial.printf("Pins: MOSI=%d, CLK=%d, CS=%d, DC=%d, RST=%d, BCKL=%d\n",
                  PIN_NUM_MOSI, PIN_NUM_CLK, PIN_NUM_CS, PIN_NUM_DC, PIN_NUM_RST, PIN_NUM_BCKL);

    // Configure control pins
    pinMode(PIN_NUM_DC, OUTPUT);
    pinMode(PIN_NUM_RST, OUTPUT);
    pinMode(PIN_NUM_BCKL, OUTPUT);
    pinMode(PIN_NUM_CS, OUTPUT);

    digitalWrite(PIN_NUM_CS, HIGH);
    digitalWrite(PIN_NUM_RST, HIGH);
    digitalWrite(PIN_NUM_BCKL, HIGH);

    // Initialize hardware SPI
    hspi = new SPIClass(HSPI);
    hspi->begin(PIN_NUM_CLK, -1, PIN_NUM_MOSI, PIN_NUM_CS);
    hspi->setFrequency(40000000);  // 40MHz for fast transfer
    hspi->setDataMode(SPI_MODE0);

    Serial.println("Hardware SPI initialized at 40MHz");

    // Initialize display
    lcd_init();

    Serial.println("Display ready!");
}

void spi_lcd_wait_finish() {
    // Nothing to wait for in synchronous mode
}

void spi_lcd_send(uint16_t *scr) {
    uint8_t *srcPtr = (uint8_t *)scr;

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

    digitalWrite(PIN_NUM_DC, HIGH);
    digitalWrite(PIN_NUM_CS, LOW);

    // Send line by line for better performance
    for (int y = 0; y < LCD_HEIGHT; y++) {
        // Convert one line from indexed to RGB565
        uint8_t *linePtr = lineBuf;
        for (int x = 0; x < LCD_WIDTH; x++) {
            uint16_t color = lcdpal[*srcPtr++];
            *linePtr++ = color >> 8;    // High byte
            *linePtr++ = color & 0xFF;  // Low byte
        }
        // Send the line
        hspi->writeBytes(lineBuf, LCD_WIDTH * 2);
    }

    digitalWrite(PIN_NUM_CS, HIGH);
}

}  // extern "C"
