#include "lcd_reg.h"

#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"

#define LCD_SPI_HOST SPI2_HOST

#define LCD_PIN_BL 1
#define LCD_PIN_CS 8
#define LCD_PIN_DC 9
#define LCD_PIN_RES 10
#define LCD_PIN_SDA 11
#define LCD_PIN_SCL 12

#define LCD_CMD_SWRESET 0x01
#define LCD_CMD_SLPOUT 0x11
#define LCD_CMD_COLMOD 0x3A
#define LCD_CMD_MADCTL 0x36
#define LCD_CMD_CASET 0x2A
#define LCD_CMD_RASET 0x2B
#define LCD_CMD_RAMWR 0x2C
#define LCD_CMD_INVON 0x21
#define LCD_CMD_DISPON 0x29

static spi_device_handle_t lcd_spi;
static SemaphoreHandle_t lcd_mutex;

/* Timing */

void lcd_delay_ms(uint32_t ms)
{
    vTaskDelay(pdMS_TO_TICKS(ms));
}

/* Low-level bus and command helpers */

static void lcd_spi_write(const void *data, uint32_t length)
{
    spi_transaction_t transaction = {
        .length = length * 8,
        .tx_buffer = data,
    };

    spi_device_polling_transmit(lcd_spi, &transaction);
}

static void lcd_write(uint8_t dc_level, uint16_t value, uint8_t length)
{
    uint8_t bytes[2] = {
        (uint8_t)(value >> 8),
        (uint8_t)value,
    };
    const uint8_t *data = length == 1 ? &bytes[1] : bytes;

    gpio_set_level(LCD_PIN_DC, dc_level);
    lcd_spi_write(data, length);
}

void lcd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
    lcd_write(0, LCD_CMD_CASET, 1);
    lcd_write(1, x0, 2);
    lcd_write(1, x1, 2);

    lcd_write(0, LCD_CMD_RASET, 1);
    lcd_write(1, y0, 2);
    lcd_write(1, y1, 2);

    lcd_write(0, LCD_CMD_RAMWR, 1);
}

void lcd_write_pixels(const void *data, uint32_t length)
{
    gpio_set_level(LCD_PIN_DC, 1);
    lcd_spi_write(data, length);
}

/* Initialization */

static void lcd_gpio_init(void)
{
    gpio_config_t gpio_config_data = {
        .pin_bit_mask = (1ULL << LCD_PIN_BL) | (1ULL << LCD_PIN_DC) | (1ULL << LCD_PIN_RES),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE,
    };

    gpio_config(&gpio_config_data);
    gpio_set_level(LCD_PIN_BL, 1);
    gpio_set_level(LCD_PIN_DC, 0);
    gpio_set_level(LCD_PIN_RES, 1);
}

static void lcd_spi_init(void)
{
    spi_bus_config_t bus_config = {
        .mosi_io_num = LCD_PIN_SDA,
        .miso_io_num = -1,
        .sclk_io_num = LCD_PIN_SCL,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = LCD_WIDTH * 40 * 2,
    };

    spi_bus_initialize(LCD_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO);

    spi_device_interface_config_t device_config = {
        .clock_speed_hz = 40000000,
        .mode = 0,
        .spics_io_num = LCD_PIN_CS,
        .queue_size = 8,
    };

    spi_bus_add_device(LCD_SPI_HOST, &device_config, &lcd_spi);
}

static void lcd_reset(void)
{
    gpio_set_level(LCD_PIN_RES, 1);
    lcd_delay_ms(20);
    gpio_set_level(LCD_PIN_RES, 0);
    lcd_delay_ms(20);
    gpio_set_level(LCD_PIN_RES, 1);
    lcd_delay_ms(120);
}

static void lcd_controller_init(void)
{
    lcd_write(0, LCD_CMD_SWRESET, 1);
    lcd_delay_ms(150);

    lcd_write(0, LCD_CMD_SLPOUT, 1);
    lcd_delay_ms(120);

    lcd_write(0, LCD_CMD_COLMOD, 1);
    lcd_write(1, 0x05, 1);

    lcd_write(0, LCD_CMD_MADCTL, 1);
    lcd_write(1, 0x60, 1);

    lcd_write(0, LCD_CMD_INVON, 1);

    lcd_write(0, LCD_CMD_DISPON, 1);
    lcd_delay_ms(120);
}

void lcd_reg_init(void)
{
    lcd_mutex = xSemaphoreCreateMutex();

    lcd_gpio_init();
    lcd_spi_init();
    lcd_reset();
    lcd_controller_init();
}
