#include <nrf_drv_gpiote.h>

#include "peripheral_pin_map.h"
#include "st7789v_register_pinmap.h"
#include "st7789v.h"

#include <stdbool.h>
#include <nrf_spi_mngr.h>
#include <nrf_delay.h>
#include <nrf_lcd.h>


#define NRF_LOG_MODULE_NAME ST7789V
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();


NRF_SPI_MNGR_DEF(m_nrf_st_spi_mngr, SPI_DS_QUEUE_SIZE, 2);

//nrf_drv_spi_config_t const display_spi_config =
//{
//    .sck_pin        = DISPLAY_SCK,
//    .mosi_pin       = DISPLAY_MOSI,
//    .miso_pin       = NRF_DRV_SPI_PIN_NOT_USED,
//    .ss_pin         = DISPLAY_CS,
//    .irq_priority   = APP_IRQ_PRIORITY_MID,
//    .orc            = 0xFF,
//    .frequency      = NRF_DRV_SPI_FREQ_8M,
//    .mode           = NRF_DRV_SPI_MODE_2,
//    .bit_order      = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST
//};

nrfx_spim_config_t const display_spi_config =
{
    .sck_pin        = DISPLAY_SCK,
    .mosi_pin       = DISPLAY_MOSI,
    .miso_pin       = NRF_DRV_SPI_PIN_NOT_USED,
    .ss_pin         = DISPLAY_CS,
	.ss_active_high = false,
    .irq_priority   = APP_IRQ_PRIORITY_MID,
    .orc            = 0xFF,
    .frequency      = NRF_SPIM_FREQ_8M,
    .mode           = NRF_DRV_SPI_MODE_2,
    .bit_order      = NRF_DRV_SPI_BIT_ORDER_MSB_FIRST
};

// Function called before SPI command is send to LCD display. I is used to drive A0 pin low.
static void ST7789V_set_command_cb(void * p_user_data)
{
    nrf_gpio_pin_clear(DISPLAY_DATA_SEL);
}


// Function called before SPI data is send to LCD display. It is used to drive A0 pin high.
static void ST7789V_set_data_cb(void * p_user_data)
{
    nrf_gpio_pin_set(DISPLAY_DATA_SEL);
}

static void set_addr_window(uint16_t x_0, uint16_t y_0, uint16_t x_1, uint16_t y_1)
{
    ASSERT(x_0 <= x_1);
    ASSERT(y_0 <= y_1);

    // copy coordinates
    static uint8_t x_coord[4];
    static uint8_t y_coord[4];
    x_coord[0] = x_0 >> 8;
    x_coord[1] = (uint8_t) x_0;
    x_coord[2] = x_1 >> 8;
    x_coord[3] = (uint8_t) x_1;
    y_coord[0] = y_0 >> 8;
    y_coord[1] = (uint8_t) y_0;
    y_coord[2] = y_1 >> 8;
    y_coord[3] = (uint8_t) y_1;

    static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND transfer[] =
    {
        NRF_SPI_MNGR_TRANSFER(&(ST7789V_CASET), 1, NULL, 0),
        NRF_SPI_MNGR_TRANSFER(&(x_coord), 4, NULL, 0),
        NRF_SPI_MNGR_TRANSFER(&(ST7789V_RASET), 1, NULL, 0),
        NRF_SPI_MNGR_TRANSFER(&(y_coord), 4, NULL, 0),
        NRF_SPI_MNGR_TRANSFER(&(ST7789V_RAMWR), 1, NULL, 0),
    };
    static const int NUM_OF_ADDRESS_TRANS = 5;
    static nrf_spi_mngr_transaction_t const transaction_address_data[] =
	{
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[0],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = &display_spi_config
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[1],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[2],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[3],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[4],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
	};
    for (int i = 0; i < NUM_OF_ADDRESS_TRANS; i++){
        nrf_spi_mngr_schedule(&m_nrf_st_spi_mngr, &transaction_address_data[i]);
    }
}

static void init_command()
{

	static uint8_t init_data[] = {0x08, 0x55, 0x0F};
	static uint8_t gamma_voltage_plus[] = {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x09};
	static uint8_t gamma_voltage_minus[] = {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19};
	static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND reset_transfers[] =
    {
    	// send as command
        NRF_SPI_MNGR_TRANSFER(&(ST7789V_SWRESET), 1, NULL, 0), // 120 ms delay afterwards
        NRF_SPI_MNGR_TRANSFER(&(ST7789V_SLPOUT), 1, NULL, 0), // 150 ms delay afterwards
    };

	ST7789V_set_command_cb(NULL);
	nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, &(reset_transfers[0]), 0, 0);
	nrf_delay_ms(120);
	nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, &(reset_transfers[1]), 0, 0);
	nrf_delay_ms(150);

	static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND transfer[] =
    {
    	// send as command
        NRF_SPI_MNGR_TRANSFER(&(ST7789V_MADCTL), 1, NULL, 0), //
        // send as data up right down mode
		NRF_SPI_MNGR_TRANSFER(&(init_data[0]), 1, NULL, 0),
		// send as command
		NRF_SPI_MNGR_TRANSFER(&(ST7789V_COLMOD), 1, NULL, 0),
		// send as data 18 bit color mode
		NRF_SPI_MNGR_TRANSFER(&(init_data[1]), 1, NULL, 0),
		// send as command
		NRF_SPI_MNGR_TRANSFER(&(ST7789V_FRMCTR2), 1, NULL, 0),
		// send as data 60 Hz frame rate
		NRF_SPI_MNGR_TRANSFER(&(init_data[2]), 1, NULL, 0),
		// send as command
		NRF_SPI_MNGR_TRANSFER(&(ST7789V_PVGAMCTRL), 1, NULL, 0),
		// send as data gamma voltage +
		NRF_SPI_MNGR_TRANSFER(&(gamma_voltage_plus[0]), 14, NULL, 0),
		// send as command
		NRF_SPI_MNGR_TRANSFER(&(ST7789V_NVGAMCTRL), 1, NULL, 0),
		// send as data gamma voltage +
		NRF_SPI_MNGR_TRANSFER(&(gamma_voltage_minus[0]), 14, NULL, 0),
		// send as command
		NRF_SPI_MNGR_TRANSFER(&(ST7789V_INVON), 1, NULL, 0),
		// send as command
		NRF_SPI_MNGR_TRANSFER(&(ST7789V_DISPON), 1, NULL, 0),

    };
	static const int NUM_OF_INIT_TRANS = 12;
    static nrf_spi_mngr_transaction_t transaction_init_data[] =
	{

		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[0],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[1],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[2],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[3],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[4],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[5],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[6],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[7],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[8],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[9],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[10],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &transfer[11],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
	};
    for (int i = 0; i < NUM_OF_INIT_TRANS; i++){
    	APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_st_spi_mngr, &transaction_init_data[i]));
    }
}

static void st7789v_pixel_draw(uint16_t x, uint16_t y, uint32_t color)
{
    set_addr_window(x, y, x, y);

    static uint8_t pixel_data[2];
    uint16_t u_color = (uint16_t) color;
    memcpy(pixel_data, (uint8_t*)&u_color, 2);
    static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND pixel_transfer[] =
	{
		NRF_SPI_MNGR_TRANSFER(&pixel_data[0], 2, NULL, 0)
	};

    static nrf_spi_mngr_transaction_t const transaction_pixel_data[] =
	{
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &pixel_transfer[0],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		}
	};
    APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_st_spi_mngr, &transaction_pixel_data[0]));
}

static void st7789v_pixel_draw_buffered(uint16_t x, uint16_t y, uint16_t* color, uint32_t length)
{
//    set_addr_window(x, y, x, y);
//
//    static uint8_t* img_buf = (uint8_t*)&(color[0]);
//    static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND pixel_transfer[] =
//	{
//		NRF_SPI_MNGR_TRANSFER(img_buf, length<<1, NULL, 0)
//	};
//
//    static nrf_spi_mngr_transaction_t const transaction_pixel_data[] =
//	{
//		{
//			.begin_callback      = ST7789V_set_data_cb,
//			.end_callback        = NULL,
//			.p_user_data         = NULL,
//			.p_transfers         = &pixel_transfer[0],   // page 0
//			.number_of_transfers = 1,
//			.p_required_spi_cfg  = NULL
//		}
//	};
//    APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_st_spi_mngr, &transaction_pixel_data[0]));
}

static void st7789v_rect_draw(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint32_t color)
{
    set_addr_window(x, y, x + width - 1, y + height - 1);

    static  uint8_t data[2];
    memcpy(data, (uint8_t*)&color, 2);

    // Duff's device algorithm for optimizing loop.
    uint32_t i = (height * width + 7) / 8;

    static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND rect_transfer[] =
	{
		NRF_SPI_MNGR_TRANSFER(&data[0], 2, NULL, 0)
	};

    ST7789V_set_data_cb(NULL);
    switch ((height * width) % 8) {
        case 0:
            do {
            	APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
        case 7:
        		APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
        case 6:
        		APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
        case 5:
    			APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
        case 4:
    			APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
        case 3:
    			APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
        case 2:
    			APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
        case 1:
    			APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, rect_transfer, 1, NULL));
            } while (--i > 0);
        default:
            break;
    }
}

static void st7789v_dummy_display(uint8_t * data, uint16_t len, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
{
    if(len == 0xFFFF){
    	set_addr_window(x0, y0, x1, y1);
    }
    else{
        nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND dummy_transfer[] =
    	{
    		NRF_SPI_MNGR_TRANSFER(&data[0], len, NULL, 0)
    	};
        nrf_spi_mngr_transaction_t const transaction_dummy_data[] =
    	{
    		{
    			.begin_callback      = ST7789V_set_data_cb,
    			.end_callback        = NULL,
    			.p_user_data         = NULL,
    			.p_transfers         = &dummy_transfer[0],   // page 0
    			.number_of_transfers = 1,
    			.p_required_spi_cfg  = NULL
    		}
    	};

        APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_st_spi_mngr, &transaction_dummy_data[0]));
    }
}

static void st7789v_rotation_set(nrf_lcd_rotation_t rotation)
{
	static uint8_t data = 0;
    switch (rotation % 4) {
        case NRF_LCD_ROTATE_0:
        	data = ST7789V_MADCTL_MX | ST7789V_MADCTL_MY | ST7789V_MADCTL_RGB;
            //Column address (MX): Right to left
            //Page address (MY): Bottom to top
            //Page/ Column order (MV): normal
            //RGB/BGR order: RGB
            break;
        case NRF_LCD_ROTATE_90:
        	data = ST7789V_MADCTL_MV | ST7789V_MADCTL_RGB;
            //Column address (MX): Left to right
            //Page address (MY): Top to bottom
            //Page/ Column order (MV): reverse
            //RGB/BGR order: RGB
            break;
        case NRF_LCD_ROTATE_180:
        	data = ST7789V_MADCTL_RGB;
            //Column address (MX): Left to right
            //Page address (MY): Top to bottom
            //Page/ Column order (MV): normal
            //RGB/BGR order: RGB
            break;
        case NRF_LCD_ROTATE_270:
        	data = ST7789V_MADCTL_MX | ST7789V_MADCTL_MV | ST7789V_MADCTL_RGB;
            //Column address (MX): Right to left
            //Page address (MY): Top to bottom
            //Page/ Column order (MV): reverse
            //RGB/BGR order: RGB
            break;
        default:
            break;
    }

	static nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND rotation_transfers[] =
    {
    	// send as command
        NRF_SPI_MNGR_TRANSFER(&(ST7789V_MADCTL), 1, NULL, 0),
		NRF_SPI_MNGR_TRANSFER(&data, 1, NULL, 0),
    };

    static nrf_spi_mngr_transaction_t transaction_rotation_data[] =
	{

		{
			.begin_callback      = ST7789V_set_command_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &rotation_transfers[0],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
		{
			.begin_callback      = ST7789V_set_data_cb,
			.end_callback        = NULL,
			.p_user_data         = NULL,
			.p_transfers         = &rotation_transfers[1],   // page 0
			.number_of_transfers = 1,
			.p_required_spi_cfg  = NULL
		},
	};
	for (int i = 0; i < 2; i++){
		APP_ERROR_CHECK(nrf_spi_mngr_schedule(&m_nrf_st_spi_mngr, &transaction_rotation_data[i]));
	}
}

static void st7789v_display_invert(bool invert)
{
	uint8_t cmd = invert ? ST7789V_INVON : ST7789V_INVOFF;

    nrf_spi_mngr_transfer_t NRF_SPI_MNGR_BUFFER_LOC_IND inv_transfer[] =
	{
		NRF_SPI_MNGR_TRANSFER(&cmd, 1, NULL, 0),
	};
    ST7789V_set_command_cb(NULL);

	APP_ERROR_CHECK(nrf_spi_mngr_perform(&m_nrf_st_spi_mngr, NULL, inv_transfer, 1, NULL));
}

static lcd_cb_t st7789v_cb = {
    .height = 320,
    .width = 240
};

static ret_code_t st7789v_init(){
	ret_code_t err_code;
	if (!nrf_drv_gpiote_is_init()){
		err_code = nrf_drv_gpiote_init();
		APP_ERROR_CHECK(err_code);
		NRF_LOG_INFO("GPIO initialized");
	}

	// configure GPIO pins
	nrf_gpio_cfg_output(DISPLAY_DATA_SEL);
	nrf_gpio_pin_set(DISPLAY_DATA_SEL);
	nrf_delay_us(20);
	nrf_gpio_pin_clear(DISPLAY_DATA_SEL);
	nrf_gpio_cfg_output(DISPLAY_RESET);

	// set standard values
	nrf_gpio_pin_set(DISPLAY_RESET);
	nrf_gpio_pin_clear(DISPLAY_RESET);
	nrf_delay_us(20);
	nrf_gpio_pin_set(DISPLAY_RESET);

	// init display driver
	err_code = nrf_spi_mngr_init(&m_nrf_st_spi_mngr, &display_spi_config);
	init_command();

	NRF_LOG_INFO("Initialized!");

	return err_code;
}

static void st7789v_uninit(void)
{
	// set standard values
	nrf_gpio_pin_set(DISPLAY_RESET);
	nrf_gpio_pin_clear(DISPLAY_RESET);
	nrf_delay_us(20);
	nrf_gpio_pin_set(DISPLAY_RESET);
	nrf_spi_mngr_uninit(&m_nrf_st_spi_mngr);
}


const nrf_lcd_t nrf_lcd_st7789v = {
    .lcd_init = st7789v_init,
    .lcd_uninit = st7789v_uninit,
    .lcd_pixel_draw = st7789v_pixel_draw,
    .lcd_rect_draw = st7789v_rect_draw,
    .lcd_display = st7789v_dummy_display,
    .lcd_rotation_set = st7789v_rotation_set,
    .lcd_display_invert = st7789v_display_invert,
    .p_lcd_cb = &st7789v_cb
};
