#include "lcd_2inch.h"
#include <SPI.h>

_lcd_dev lcddev;

void Lcd_Gpio_Init(void)
{
    pinMode(LCD_CS, OUTPUT);
    pinMode(LCD_RST, OUTPUT);
    pinMode(LCD_DC, OUTPUT);
    pinMode(LCD_BL, OUTPUT);
    
    LCD_CS_1;
    LCD_BL_1; // Turn on backlight
}

// Hardware SPI write command
void Spi_write_cmd(uint8_t data)
{
    LCD_CS_0;
    LCD_DC_0;
    SPI.transfer(data);
    LCD_CS_1;
}

// Hardware SPI write data (8-bit)
void Spi_write_data(uint8_t data)
{
    LCD_CS_0;
    LCD_DC_1;
    SPI.transfer(data);
    LCD_CS_1;
}

// Hardware SPI write data (16-bit)
void Spi_write_word(uint16_t data)
{
    LCD_CS_0;
    LCD_DC_1;
    SPI.transfer16(data); 
    LCD_CS_1;
}

// Hardware reset
static void Lcd_Reset(void)
{
    LCD_RST_1;
    delay(10);
    LCD_RST_0;
    delay(10); // ST7789 needs a short reset pulse
    LCD_RST_1;
    delay(120);
}

void Lcd_Init(void)
{
    Lcd_Gpio_Init();
    Lcd_Reset();

    // ST7789 initialization sequence
    Spi_write_cmd(0x11); // Sleep Out
    delay(120);

    Spi_write_cmd(0x3A); // Interface Pixel Format
    Spi_write_data(0x05); // 16bit/pixel

    Spi_write_cmd(0x36); // Memory Data Access Control
    
    // Spi_write_data(0x00);// Normal orientation

    // Spi_write_data(0xC0);Rotate 180 degrees

    Spi_write_data(0x70); //Landscape mode (rotate 90 degrees)
    
    Spi_write_cmd(0xB2); // Porch Setting
    Spi_write_data(0x0C);
    Spi_write_data(0x0C);
    Spi_write_data(0x00);
    Spi_write_data(0x33);
    Spi_write_data(0x33);

    Spi_write_cmd(0xB7); // Gate Control
    Spi_write_data(0x35);

    Spi_write_cmd(0xBB); // VCOM Setting
    Spi_write_data(0x32);

    Spi_write_cmd(0xC2); // VDV and VRH Command Enable
    Spi_write_data(0x01);

    Spi_write_cmd(0xC3); // VRH Set
    Spi_write_data(0x15);

    Spi_write_cmd(0xC4); // VDV Set
    Spi_write_data(0x20);

    Spi_write_cmd(0xC6); // Frame Rate Control
    Spi_write_data(0x0F);

    Spi_write_cmd(0xD0); // Power Control 1
    Spi_write_data(0xA4);
    Spi_write_data(0xA1);

    Spi_write_cmd(0xE0); // Positive Voltage Gamma Control
    Spi_write_data(0xD0);
    Spi_write_data(0x08);
    Spi_write_data(0x0E);
    Spi_write_data(0x09);
    Spi_write_data(0x09);
    Spi_write_data(0x05);
    Spi_write_data(0x31);
    Spi_write_data(0x33);
    Spi_write_data(0x48);
    Spi_write_data(0x17);
    Spi_write_data(0x14);
    Spi_write_data(0x15);
    Spi_write_data(0x31);
    Spi_write_data(0x34);

    Spi_write_cmd(0xE1); // Negative Voltage Gamma Control
    Spi_write_data(0xD0);
    Spi_write_data(0x09);
    Spi_write_data(0x0F);
    Spi_write_data(0x08);
    Spi_write_data(0x07);
    Spi_write_data(0x12);
    Spi_write_data(0x33);
    Spi_write_data(0x33);
    Spi_write_data(0x47);
    Spi_write_data(0x14);
    Spi_write_data(0x15);
    Spi_write_data(0x19);
    Spi_write_data(0x2F);
    Spi_write_data(0x36);

    Spi_write_cmd(0x21); // Display Inversion On

    Spi_write_cmd(0x29); // Display On

    // Initialize LCD parameters structure
    lcddev.width = LCD_H;  // 320
    lcddev.height = LCD_W; // 240
    lcddev.wramcmd = 0x2C;
    lcddev.setxcmd = 0x2A;
    lcddev.setycmd = 0x2B;
    
    LCD_Clear(WHITE);
}

void LCD_Set_Cursor(uint16_t x, uint16_t y)
{
    // Set column address (X)
    Spi_write_cmd(lcddev.setxcmd);
    Spi_write_data(x >> 8);
    Spi_write_data(x & 0xFF);
    Spi_write_data(lcddev.width >> 8); // End address set to max
    Spi_write_data(lcddev.width & 0xFF);

    // Set row address (Y)
    Spi_write_cmd(lcddev.setycmd);
    Spi_write_data(y >> 8);
    Spi_write_data(y & 0xFF);
    Spi_write_data(lcddev.height >> 8);
    Spi_write_data(lcddev.height & 0xFF);
    
    Spi_write_cmd(lcddev.wramcmd); // Prepare for memory write
}

// Draw a single point
void Draw_Point(uint16_t x, uint16_t y, uint16_t color)
{
    LCD_Set_Cursor(x, y);
    Spi_write_word(color);
}

// Clear screen
void LCD_Clear(uint16_t color)
{
    uint32_t total_point = lcddev.width * lcddev.height;
    
    // Set full screen window
    Spi_write_cmd(lcddev.setxcmd);
    Spi_write_data(0); Spi_write_data(0);
    Spi_write_data((lcddev.width-1) >> 8); Spi_write_data((lcddev.width-1) & 0xFF);
    
    Spi_write_cmd(lcddev.setycmd);
    Spi_write_data(0); Spi_write_data(0);
    Spi_write_data((lcddev.height-1) >> 8); Spi_write_data((lcddev.height-1) & 0xFF);
    
    Spi_write_cmd(lcddev.wramcmd);

    LCD_CS_0;
    LCD_DC_1;

    uint8_t color_buf[2] = { (uint8_t)(color >> 8), (uint8_t)(color & 0xFF) };

    uint8_t line_buf[320 * 2]; 
    for(int i=0; i<lcddev.width; i++) {
        line_buf[i*2] = color_buf[0];
        line_buf[i*2+1] = color_buf[1];
    }
    
    for(int y=0; y<lcddev.height; y++) {
         SPI.writeBytes(line_buf, lcddev.width * 2);
    }

    LCD_CS_1;
}

// Fill a rectangular area with a solid color
void Gui_fill_color(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
    uint16_t temp;

    if(x1 > x2) { temp = x1; x1 = x2; x2 = temp; }
    if(y1 > y2) { temp = y1; y1 = y2; y2 = temp; }

    LCD_Set_Cursor(x1, y1); 

    Spi_write_cmd(lcddev.setxcmd);
    Spi_write_data(x1 >> 8);
    Spi_write_data(x1 & 0xFF);
    Spi_write_data(x2 >> 8);
    Spi_write_data(x2 & 0xFF);

    Spi_write_cmd(lcddev.setycmd);
    Spi_write_data(y1 >> 8);
    Spi_write_data(y1 & 0xFF);
    Spi_write_data(y2 >> 8);
    Spi_write_data(y2 & 0xFF);

    Spi_write_cmd(lcddev.wramcmd); // Start writing to memory

    LCD_CS_0;
    LCD_DC_1;

    uint32_t size = (uint32_t)(x2 - x1 + 1) * (y2 - y1 + 1);
    uint8_t color_hi = color >> 8;
    uint8_t color_lo = color & 0xFF;

    uint16_t width = x2 - x1 + 1;
    uint8_t line_buf[width * 2];
    
    // Fill line buffer
    for(uint16_t i = 0; i < width; i++) {
        line_buf[i * 2] = color_hi;
        line_buf[i * 2 + 1] = color_lo;
    }

    uint16_t height = y2 - y1 + 1;
    for(uint16_t y = 0; y < height; y++) {
        SPI.writeBytes(line_buf, width * 2);
    }

    LCD_CS_1;
}

void LCD_ShowFullScreenImage(const unsigned char *img)
{
    Spi_write_cmd(lcddev.setxcmd);
    Spi_write_data(0); Spi_write_data(0);
    Spi_write_data((lcddev.width-1) >> 8); Spi_write_data((lcddev.width-1) & 0xFF);

    Spi_write_cmd(lcddev.setycmd);
    Spi_write_data(0); Spi_write_data(0);
    Spi_write_data((lcddev.height-1) >> 8); Spi_write_data((lcddev.height-1) & 0xFF);

    Spi_write_cmd(lcddev.wramcmd); 
    LCD_CS_0;
    LCD_DC_1;
    uint32_t bytes_per_line = lcddev.width * 2; 
    for(int y = 0; y < lcddev.height; y++) {
        SPI.writeBytes((uint8_t*)&img[y * bytes_per_line], bytes_per_line);
    }
    LCD_CS_1;
}
