#include <stdio.h>
#include <malloc.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/dma.h"

#define SPI_PORT spi0
#define PIN_MISO 16
#define PIN_CS   17
#define PIN_SCK  18
#define PIN_MOSI 19

#define TFT_DC  20  // LOW:Command | HIGH:Data
#define TFT_RES 21
#define TFT_COMMAND     false
#define TFT_DATA        true
#define TFT_WIDTH       240     // LCD縦ピクセル数
#define TFT_HEIGHT      240     // LCD横ピクセル数

#define SSTCMD_SWRESET  0x01
#define SSTCMD_SLPOUT   0x11
#define SSTCMD_NORON    0x13
#define SSTCMD_INVOFF   0x20
#define SSTCMD_INVON    0x21
#define SSTCMD_DISPOFF  0x28
#define SSTCMD_DISPON   0x29
#define SSTCMD_MADCTL   0x36
#define SSTCMD_COLMOD   0x3A
#define SSTCMD_INVCTR   0xB4
#define SSTCMD_CASET    0x2A
#define SSTCMD_RASET    0x2B
#define SSTCMD_RAMWR    0x2C

typedef struct {
    uint8_t start_x;
    uint8_t start_y;
    uint8_t end_x;
    uint8_t end_y;
} sst7735_rect;

inline int sst7735_rect_len(sst7735_rect *r)
{
    return (r->end_x-r->start_x) * (r->end_y - r->start_y);
}

static inline void sst7735_send_cmd(uint8_t cmd)
{
    gpio_put(TFT_DC, TFT_COMMAND);
    spi_write_blocking(SPI_PORT, &cmd, sizeof(uint8_t));
    gpio_put(TFT_DC, TFT_DATA);
}

static inline void sst7735_send_data8(uint8_t *data, size_t len)
{
    gpio_put(TFT_DC, TFT_DATA);
    spi_write_blocking(SPI_PORT, data, len);
//    gpio_put(TFT_DC, TFT_DATA);
}

static inline void sst7735_send_data16(uint16_t *data, size_t len)
{
    gpio_put(TFT_DC, TFT_DATA);
    spi_set_format (SPI_PORT, 16, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
    spi_write16_blocking(SPI_PORT, data, len);
    spi_set_format (SPI_PORT, 8, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
//    gpio_put(TFT_DC, TFT_DATA);
}

static inline void sst7735_reset()
{
    gpio_put(TFT_RES, true);
    sleep_ms(500);              // 500ms
    gpio_put(TFT_RES, false);
    sleep_ms(100);              // RESET pulse 100ms
    gpio_put(TFT_RES, true);
    sleep_ms(500);              // 500ms
    sst7735_send_cmd(SSTCMD_SWRESET);   // Software Reset
    sleep_ms(100);              // delay 100ms
}

static inline void sst7735_init(void)
{
    uint8_t cmd_param[4];
    sst7735_send_cmd(SSTCMD_DISPOFF);       // Display Off
    sleep_ms(500);
    sst7735_send_cmd(SSTCMD_SLPOUT);        // Sleep Out
    sleep_ms(500);
    sst7735_send_cmd(SSTCMD_DISPON);        // Display on
    sleep_ms(100);
    sst7735_send_cmd(SSTCMD_COLMOD);        // Color mode
    cmd_param[0] = 0x55;                    // 16bit mode
    sst7735_send_data8(cmd_param, 1);
    sleep_ms(100);
    sst7735_send_cmd(SSTCMD_MADCTL);
    cmd_param[0] = 0x00;
    sst7735_send_data8(cmd_param, 1);        // RGB 16bit mode
    sleep_ms(100);

//    sst7735_send_cmd(SSTCMD_NORON);
    sst7735_send_cmd(SSTCMD_INVON);
}

void sst7735_draw_image(uint16_t *image, sst7735_rect *rect)
{
    uint8_t cmd_param[4] = {0,0,0,0};
    cmd_param[1] = rect->start_x;
    cmd_param[3] = rect->end_x;
    sst7735_send_cmd(SSTCMD_CASET);     // Column Address Set
    sst7735_send_data8(cmd_param, 4);
    cmd_param[1] = rect->start_y;
    cmd_param[3] = rect->end_y;
    sst7735_send_cmd(SSTCMD_RASET);     // Row Address Set
    sst7735_send_data8(cmd_param, 4);

    sst7735_send_cmd(SSTCMD_RAMWR);     // RAM RW mode
    sleep_us(500);
    sst7735_send_data16((uint16_t *)image, sst7735_rect_len(rect));
}

void sst7735_clear(void)
{
    uint16_t *temp_buf;
    sst7735_rect r = {start_x:0, end_x:TFT_WIDTH, start_y:0, end_y:TFT_HEIGHT};

    temp_buf = (uint16_t *)calloc(TFT_HEIGHT * TFT_WIDTH, sizeof(uint16_t));
    sst7735_draw_image(temp_buf, &r);
    free(temp_buf);
}

int dma_ch;
dma_channel_config dma_conf;

// DMA割り込み
void dma_irq_handler()
{
    dma_irqn_acknowledge_channel(DMA_IRQ_0, dma_ch);
    spi_set_format (SPI_PORT, 16, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
    // CSをGPIOで手動操作しているならここでLOWに落とすこと
}

void sst7735_draw_dma(uint16_t *image, sst7735_rect *rect)
{
    // SST77xxコマンド発行
    uint8_t cmd_param[4] = {0,0,0,0};
    cmd_param[1] = rect->start_x;
    cmd_param[3] = rect->end_x;
    sst7735_send_cmd(SSTCMD_CASET);     // Column Address Set
    sst7735_send_data8(cmd_param, 4);
    cmd_param[1] = rect->start_y;
    cmd_param[3] = rect->end_y;
    sst7735_send_cmd(SSTCMD_RASET);     // Row Address Set
    sst7735_send_data8(cmd_param, 4);
    sst7735_send_cmd(SSTCMD_RAMWR);     // RAM RW mode
    sleep_us(500);

    dma_ch = dma_claim_unused_channel(true);
    dma_conf = dma_channel_get_default_config(dma_ch);
    channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_16);
    channel_config_set_read_increment(&dma_conf, true);
    channel_config_set_write_increment(&dma_conf, false);
    uint dreq = spi_get_dreq(SPI_PORT, true);
    channel_config_set_dreq(&dma_conf, dreq);
    // DMA割り込み設定
    dma_channel_set_irq0_enabled(dma_ch, true);
    irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
    irq_set_enabled(DMA_IRQ_0, true);
    // SPIを16bit転送に設定
    spi_set_format (SPI_PORT, 16, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
    gpio_put(TFT_DC, TFT_DATA);
    // DMAスタート
    dma_channel_configure(
        dma_ch,
        &dma_conf,
        &spi_get_hw(SPI_PORT)->dr,  // DATAレジスタ（SSPDR）
        image,
        sst7735_rect_len(rect),
        true
    );
}

// サンプル画像を埋め込む
__asm(\
  ".section \".rodata\" \n"
  ".balign 4\n"

  ".global _sample_picture\n"
  ".global _picture_size\n"

  "_sample_picture:\n"
  ".incbin \"sample_picture.bin\"\n"
  ".set _picture_size, . - _sample_picture\n"
  ".section \".text\"\n"
);

extern const uint16_t _sample_picture[];
extern uint32_t _picture_size[];

int main()
{
    stdio_init_all();
    spi_init(SPI_PORT, 50*1000*1000);
    // spi_init(SPI_PORT, 300*1000);
    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(PIN_CS,   GPIO_FUNC_SPI);
    gpio_set_function(PIN_SCK,  GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
    spi_set_format (SPI_PORT, 8, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
//    gpio_set_dir(PIN_CS, GPIO_OUT);
//    gpio_put(PIN_CS, 1);
    // TFT Pin Init
    gpio_init(TFT_DC);
    gpio_set_dir(TFT_DC, GPIO_OUT);
    gpio_init(TFT_RES);
    gpio_set_dir(TFT_RES, GPIO_OUT);

    sst7735_reset();
    sst7735_init();
    sst7735_clear();

    sst7735_rect r = {start_x:0, end_x:240, start_y:0, end_y:240};
    // sst7735_draw_image((uint16_t *)_sample_picture, &r);
    sst7735_draw_dma((uint16_t *)_sample_picture, &r);
    while (true) {
        tight_loop_contents();
    }
}
