#include <stdio.h>
#include <malloc.h>
#include <memory.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
#include "hardware/timer.h"
#include "pfnfont.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 TFT_BYTES_PER_PIXEL 2

#define	TFT_COLOR_BLACK   0x0000
#define	TFT_COLOR_BLUE    0x001F
#define	TFT_COLOR_RED     0xF800
#define	TFT_COLOR_GREEN   0x07E0
#define TFT_COLOR_CYAN    0x07FF
#define TFT_COLOR_MAGENTA 0xF81F
#define TFT_COLOR_YELLOW  0xFFE0
#define TFT_COLOR_WHITE   0xFFFF

#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_set_format (SPI_PORT, 8, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
    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_set_format (SPI_PORT, 8, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
    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);
//    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(100);
    sst7735_send_data16((uint16_t *)image, sst7735_rect_len(rect));
}

void sst7735_clear(void)
{
    uint16_t *temp_buf;
    sst7735_rect r = {start_x:0, start_y:0, end_x:TFT_WIDTH, 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 = -1;                         // DMAチャンネル
dma_channel_config dma_conf;             // DMA設定
volatile bool dma_in_prgress = false;    // DMA転送中フラグ


// DMA割り込み
void dma_irq_handler()
{
    dma_channel_acknowledge_irq0(dma_ch);
    // DMA転送中のフラグを落とす
    dma_in_prgress = false;
}

uint8_t *string_line_buffer[2];             // 1行バッファ
int     buffer_selector = 0;                // バッファセレクタ
uint16_t back_color = TFT_COLOR_BLACK;      // 背景色
uint16_t font_color = TFT_COLOR_WHITE;      // 文字色

PFNFont *pfn;       // PFNフォントオブジェクト

// 1行バッファ内にフォントグリフを描くコールバック関数
void draw_font_in_buffer(font_gryph_t *gryph, int x, int y)
{
    // uint16_tポインタとしてバッファを扱う
    uint16_t *line_buf = (uint16_t*)string_line_buffer[buffer_selector];

    for(int r = 0; r < gryph->height; r++) {
        for(int c = 0; c < gryph->width; c++) {
            // 画面外描画をクリップ
            if ((x + c) >= TFT_WIDTH) continue;

            int bit_index = r * gryph->width + c;
            int byte_index = bit_index / 8;
            int bit_offset = bit_index % 8;
            if ((gryph->bitmap[byte_index] >> (7 - bit_offset)) & 1) {
                line_buf[r * TFT_WIDTH + (x + c)] = font_color;
            } else {
                // 背景塗りつぶし済み
            }
        }
    }
}

// 文字列描画用DMA設定関数
void configure_dma_channel(void)
{
    // DMA用のダブルバッファを確保しておく
    size_t line_buffer_size = TFT_WIDTH * pfn->get_max_font_height() * TFT_BYTES_PER_PIXEL;
    string_line_buffer[0] = (uint8_t *)malloc(line_buffer_size);
    string_line_buffer[1] = (uint8_t *)malloc(line_buffer_size);

    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);

    dma_channel_configure(
        dma_ch,
        &dma_conf,
        &spi_get_hw(SPI_PORT)->dr,  // DATAレジスタ（SSPDR）
        string_line_buffer[buffer_selector],// Dummy（後で設定）
        2,                          // Dummy
        false
    );
}

// 文字をLCDにレンダリングする
void render_string(const char *str, int x, int y)
{
    int render_width = 0;
    // DMAが設定されていなければ何もしない
    if(dma_ch < 0)
        return;
    // 1行バッファのサイズ
    size_t line_buffer_size = TFT_WIDTH * pfn->get_max_font_height() * TFT_BYTES_PER_PIXEL;
    // バッファを背景色で塗りつぶし
    uint16_t* current_buffer = (uint16_t*)string_line_buffer[buffer_selector];
    for (int i = 0; i < line_buffer_size / TFT_BYTES_PER_PIXEL; ++i) {
        current_buffer[i] = back_color;
    }
    // 文字をバッファに描画
    render_width = pfn->draw_string(str, x, 0);
    if(render_width == 0 )
        return;

    // 前のDMAを待つ
    while(dma_in_prgress) {
        // SSPのTX FIFOが空になるまで待つ
        while(!(spi_get_hw(SPI_PORT)->sr & 1));
        // 少し間を空けないと死ぬ
        sleep_us(20);
    }

    // SST77xxコマンド発行
    uint8_t cmd_param[4] = {0,0,0,0};
    cmd_param[1] = 0;
    cmd_param[3] = TFT_WIDTH;
    sst7735_send_cmd(SSTCMD_CASET);     // Column Address Set
    sst7735_send_data8(cmd_param, 4);
    cmd_param[1] = y;
    cmd_param[3] = y + pfn->get_max_font_height();
    sst7735_send_cmd(SSTCMD_RASET);     // Row Address Set
    sst7735_send_data8(cmd_param, 4);
    sst7735_send_cmd(SSTCMD_RAMWR);     // RAM RW mode
    sleep_us(20);

    gpio_put(TFT_DC, TFT_DATA);
    spi_set_format (SPI_PORT, 16, SPI_CPOL_1 , SPI_CPHA_1,SPI_MSB_FIRST );
    dma_channel_set_read_addr(dma_ch, string_line_buffer[buffer_selector], false);
    dma_channel_set_trans_count(dma_ch, TFT_WIDTH * pfn->get_max_font_height(), true);
    dma_in_prgress = true;
    // バッファ反転
    buffer_selector = !buffer_selector;
}

// フォントデータ
__asm(\
  ".section \".rodata\" \n"
  ".balign 4\n"

  ".global _pfnfont_data\n"
  ".global _pfnfont_data_len\n"

  "_pfnfont_data:\n"
  ".incbin \"shnmk16u.pfn\"\n"
  ".set _pfnfont_data_len, . - _pfnfont_data\n"
  ".section \".text\"\n"
);

extern const uint8_t _pfnfont_data[];
extern uint32_t _pfnfont_data_len;

int main()
{
    stdio_init_all();
    // SPI
    spi_init(SPI_PORT, 20*1000*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 );
    // 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);
    // TFT INIT
    sst7735_reset();
    sst7735_init();
    sst7735_clear();

    // 描画コールバック関数を渡してPFNFontオブジェクトを生成
    pfn = new PFNFont((uint8_t *)_pfnfont_data, (size_t)&_pfnfont_data_len, draw_font_in_buffer);

    // フォントが正しく読み込めたかチェック
    if (pfn->isValid()) {
        printf("Font loaded successfully.\n");

        // render_string()を使用する前にかならずconfigure_dma_channel()を呼ぶこと
        configure_dma_channel();
        
        int x = 0, y = 0;
        while (true) {
            back_color = TFT_COLOR_BLACK;
            font_color = TFT_COLOR_WHITE;
            render_string("Picoで日本語表示!", x, y);
            font_color = TFT_COLOR_BLUE;
            y += pfn->get_max_font_height();
            render_string("Picoで日本語表示!", x, y);
            font_color = TFT_COLOR_RED;
            y += pfn->get_max_font_height();
            render_string("Picoで日本語表示!", x, y);
            font_color = TFT_COLOR_YELLOW;
            y += pfn->get_max_font_height();
            render_string("Picoで日本語表示!", x, y);
            sleep_ms(500);
            sst7735_clear();
            y = 0;
        }
    }
    else {
        printf("Font loading failed.\n");
        delete pfn;
        while(true) tight_loop_contents();
    }
    return 0;
}
