#include <string.h>
#include <stdlib.h>		
#include <wiringPi.h>
#include <wiringPiSPI.h>
#include <softPwm.h>
#include "lcd_2inch.h"

// SPI Channel 0, Speed 32MHz (Adjust if screen is unstable)
#define SPI_CHANNEL 0
#define SPI_SPEED   16000000 

void Lcd_Gpio_Init(void)
{
	printf("Init gpio for wiringPi\r\n");
    
    // Initialize wiringPi using BCM pin numbering
    if (wiringPiSetup() == -1) {
        printf("WiringPi Setup Failed\n");
        exit(1);
    }

    // Initialize SPI
    if (wiringPiSPISetup(SPI_CHANNEL, SPI_SPEED) == -1) {
        printf("SPI Setup Failed\n");
        exit(1);
    }

	pinMode(PIN_RST, OUTPUT);
    pinMode(PIN_DC, OUTPUT);
    pinMode(PIN_BL, OUTPUT);
    // Backlight Control using SoftPWM
    // Range 0-100. Initial value 90.
    softPwmCreate(PIN_BL, 0, 100);
	softPwmWrite(PIN_BL, 90); 
}

/*******************************************************************************
function:
	Hardware reset
*******************************************************************************/
static void Lcd_Reset(void)
{
	digitalWrite(PIN_RST, HIGH);
	delay(100);
    digitalWrite(PIN_RST, LOW);
	delay(100);
    digitalWrite(PIN_RST, HIGH);
	delay(100);
}

/*******************************************************************************
function:
		Write data and commands
*******************************************************************************/
static void Spi_write_cmd(u_char data)	 
{	
	digitalWrite(PIN_DC, LOW); // Command mode
    // wiringPiSPIDataRW overwrites the buffer, so we pass a pointer
	wiringPiSPIDataRW(SPI_CHANNEL, &data, 1);
}

static void Spi_write_data(u_char data) 
{	
	digitalWrite(PIN_DC, HIGH); // Data mode
	wiringPiSPIDataRW(SPI_CHANNEL, &data, 1);
}  

void Spi_write_word(u_int16_t data)
{
	u_char buffer[2];
	digitalWrite(PIN_DC, HIGH);
    
	buffer[0] = (data >> 8) & 0xff;
	buffer[1] = data & 0xff;
    
	wiringPiSPIDataRW(SPI_CHANNEL, buffer, 2);
}	  


/******************************************************************************
function:	
		Common register initialization
******************************************************************************/
void Lcd_Init(void)
{
	Lcd_Gpio_Init();
	Lcd_Reset();
    
	Spi_write_cmd(0x36);
	Spi_write_data(0x00); 

	Spi_write_cmd(0x3A); 
	Spi_write_data(0x05);

	Spi_write_cmd(0x21); 

	Spi_write_cmd(0xB2);
	Spi_write_data(0x0C);
	Spi_write_data(0x0C);
	Spi_write_data(0x00);
	Spi_write_data(0x33);
	Spi_write_data(0x33);

	Spi_write_cmd(0xB7); 
	Spi_write_data(0x35);  

	Spi_write_cmd(0xBB);
	Spi_write_data(0x1F);

	Spi_write_cmd(0xC0);
	Spi_write_data(0x2C);

	Spi_write_cmd(0xC2);
	Spi_write_data(0x01);

	Spi_write_cmd(0xC3);
	Spi_write_data(0x12);

	Spi_write_cmd(0xC4);
	Spi_write_data(0x20);

	Spi_write_cmd(0xC6); 
	Spi_write_data(0x0F); 

	Spi_write_cmd(0xD0); 
	Spi_write_data(0xA4);
	Spi_write_data(0xA1);

	Spi_write_cmd(0xE0);
	Spi_write_data(0xD0); Spi_write_data(0x08); Spi_write_data(0x11); Spi_write_data(0x08);
	Spi_write_data(0x0C); Spi_write_data(0x15); Spi_write_data(0x39); Spi_write_data(0x33);
	Spi_write_data(0x50); Spi_write_data(0x36); Spi_write_data(0x13); Spi_write_data(0x14);
	Spi_write_data(0x29); Spi_write_data(0x2D);

	Spi_write_cmd(0xE1);
	Spi_write_data(0xD0); Spi_write_data(0x08); Spi_write_data(0x10); Spi_write_data(0x08);
	Spi_write_data(0x06); Spi_write_data(0x06); Spi_write_data(0x39); Spi_write_data(0x44);
	Spi_write_data(0x51); Spi_write_data(0x0B); Spi_write_data(0x16); Spi_write_data(0x14);
	Spi_write_data(0x2F); Spi_write_data(0x31);

	Spi_write_cmd(0x21); 
	Spi_write_cmd(0x11); 
	delay(120);
	Spi_write_cmd(0x29);
}

//select the srceen area(from point(start_x,start_y) to point(end_x,end_y))
void Set_Window(u_int16_t start_x, u_int16_t start_y, u_int16_t end_x, u_int16_t  end_y)
{ 
	Spi_write_cmd(0x2a);
	Spi_write_data(start_x >>8);
	Spi_write_data(start_x & 0xff);
	Spi_write_data((end_x - 1) >> 8);
	Spi_write_data((end_x - 1) & 0xff);

	Spi_write_cmd(0x2b);
	Spi_write_data(start_y >>8);
	Spi_write_data(start_y & 0xff);
	Spi_write_data((end_y - 1) >> 8);
	Spi_write_data((end_y - 1) & 0xff);

	Spi_write_cmd(0x2C);
}


//set cursor to the point(x, y)
void Set_Cursor(u_int16_t x, u_int16_t y)
{ 
	Spi_write_cmd(0x2a);
	Spi_write_data(x >> 8);
	Spi_write_data(x);
	Spi_write_data(x >> 8);
	Spi_write_data(x);

	Spi_write_cmd(0x2b);
	Spi_write_data(y >> 8);
	Spi_write_data(y);
	Spi_write_data(y >> 8);
	Spi_write_data(y);

	Spi_write_cmd(0x2C);
}

//refresh the entire screen to color
void Lcd_Clear(u_int16_t color)
{
	u_int16_t i;
	// Create a line buffer
    // Note: allocating large arrays on stack can be risky on small systems, 
    // but 240 * 2 bytes is small enough.
	u_int16_t line_buf[LCD_W]; 
    
	for(i=0; i<LCD_W; i++)
	{
		line_buf[i] = (color >> 8) | (color & 0xff) << 8;
	}
    
	Set_Window(0, 0, LCD_W, LCD_H);
	digitalWrite(PIN_DC, HIGH);
    
	for(i = 0; i < LCD_H; i++)
	{
        // Must send buffer content, not pointer to buffer logic
        wiringPiSPIDataRW(SPI_CHANNEL, (unsigned char *)line_buf, LCD_W * 2);
	}
}


//display the image to whole screen 
void Display(u_int16_t *image)
{
	u_int16_t i;
	u_char line_buffer[LCD_W*2];
	
	Set_Window(0, 0, LCD_W, LCD_H);
	digitalWrite(PIN_DC, HIGH);
    
    // Send line by line to handle potential large transfer limits in some SPI drivers
	for(i = 0; i < LCD_H; i++)
	{
		memcpy(line_buffer,(u_char *)image+LCD_W*2*i,LCD_W*2);
		wiringPiSPIDataRW(SPI_CHANNEL,line_buffer, LCD_W * 2);
	}
}

//set the point(x,y) to color
void Draw_Point(u_int16_t x, u_int16_t y, u_int16_t Color)
{
	Set_Cursor(x, y);
	Spi_write_word(Color); 	    
}

// Write block of color
void LCD_Write_Block(u_int16_t x_start, u_int16_t y_start, u_int16_t w, u_int16_t h, u_int16_t color)
{
    u_int16_t x_end = x_start + w - 1;
    u_int16_t y_end = y_start + h - 1;
    u_int32_t total_pixels = w * h;
    u_int32_t i;
    
    // Prepare buffer for color
    u_int8_t color_hi = (color >> 8) & 0xFF;
    u_int8_t color_lo = color & 0xFF;
    
    Set_Window(x_start, y_start, x_end, y_end);
    digitalWrite(PIN_DC, HIGH); 
    
    // Optimization: Create a small buffer to send multiple pixels at once
    // instead of byte by byte which is very slow in wiringPi
    #define BUF_SIZE 128
    u_int8_t buf[BUF_SIZE];
    
    // Fill buffer with the color
    for(int k=0; k<BUF_SIZE; k+=2) {
        buf[k] = color_hi;
        buf[k+1] = color_lo;
    }
    
    u_int32_t bytes_to_send = total_pixels * 2;
    u_int32_t chunks = bytes_to_send / BUF_SIZE;
    u_int32_t remainder = bytes_to_send % BUF_SIZE;
    
    for(i=0; i<chunks; i++) {
        wiringPiSPIDataRW(SPI_CHANNEL, buf, BUF_SIZE);
    }
    
    if(remainder > 0) {
        wiringPiSPIDataRW(SPI_CHANNEL, buf, remainder);
    }
}
