#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "hub75.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_rom_sys.h"

static const char *TAG = "led matrix";

#define HUB75_CLOCK_HZ     (10 * 1000 * 1000)
#define HUB75_COL_COUNT    (64)
#define HUB75_ROW_COUNT    (32)
#define HUB75_COL_UNIT_COUNT    (32)    /* 实际扫描的时候送入数据的列数 */
#define HUB75_SCAN_LINE_CNT     (16)    /* 多少扫的 */

#define HUB75_LINE_ADDR_A_PIN_NUM         33 
#define HUB75_LINE_ADDR_B_PIN_NUM         34
#define HUB75_LINE_ADDR_C_PIN_NUM         35
#define HUB75_LINE_ADDR_D_PIN_NUM         36
#define HUB75_LAT_PIN_NUM                 8
#define HUB75_OE_PIN_NUM                  21
#define HUB75_CLK_PIN_NUM                 7

#define HUB75_R0    1
#define HUB75_G0    2
#define HUB75_B0    3
#define HUB75_R1    4
#define HUB75_G1    5
#define HUB75_B1    6
#define HUB75_R2    37
#define HUB75_G2    38
#define HUB75_B2    39
#define HUB75_R3    40
#define HUB75_G3    41
#define HUB75_B3    42

//#define LVGL_TICK_PERIOD_MS    2
#define LINE_SCAN_LOOP_PERIOD_MS    1000    /* 暂时测试用 */
#define LINE_SCAN_CNT               8       /* 用于全彩显示,RGB323色彩,最宽3bit取值0-7,需要扫描8次进行灰度调节 */
#define PSRAM_DATA_ALIGNMENT        64

#define get_rgb_one_value(n, x)     (n < (x) ? 1 : 0)  /* 判断r、g、b其中一个本次是否应该点亮 */
/* 输入一个byte,从高到低:r 3bit, g 2bit, b 3bit */
/* 输出一个3bit值,从高到低:b, g, r */
#define get_rgb_value(n, x)         (get_rgb_one_value(n, (x >> 5) & 0x07) | (get_rgb_one_value(n, (x >> 3) & 0x03) << 1)  | (get_rgb_one_value(n, x & 0x07) << 2))  

typedef unsigned char uint8_t;
typedef unsigned short int  uint16_t;

uint8_t ledbuf[HUB75_ROW_COUNT][HUB75_COL_COUNT];



static int line = 0;            /* 本次扫描行,32行16扫,一次显示两行 */
void get_one_line(int *line_ret, uint16_t *line_data)
{
    int i;
    static int line_scan_cnt = 0;   /* 本行已经扫描多少次,8次 */

    /* 准备一行数据 */
    for (i = 0; i < HUB75_COL_UNIT_COUNT; i++)
    {
        /* 后面的注释为bit从低到高,对应i80总线的数据线 */
        line_data[i] = get_rgb_value(line_scan_cnt, ledbuf[line][i]) |  /* r0, g0, b0 */
                        (get_rgb_value(line_scan_cnt, ledbuf[line + HUB75_SCAN_LINE_CNT][i]) << 3) |    /* r1, g1, b1 */
                        (get_rgb_value(line_scan_cnt, ledbuf[line][i + HUB75_COL_UNIT_COUNT]) << 6) |   /* r2, g2, b2 */
                        (get_rgb_value(line_scan_cnt, ledbuf[line + HUB75_SCAN_LINE_CNT][i + HUB75_COL_UNIT_COUNT]) << 9);     /* r3, g3, b3 */
    }
    *line_ret = line;

    /* 更新索引 */
    line_scan_cnt++;
    if (line_scan_cnt >= HUB75_SCAN_LINE_CNT)
        line ++;
    if (line >= HUB75_SCAN_LINE_CNT)
    {
        line = 0;
        line_scan_cnt = 0;
    }
}


static void line_scan_loop_cb(void *args)
{
    int line;
    uint16_t line_data[HUB75_COL_UNIT_COUNT];   
    esp_lcd_panel_io_handle_t io_handle = (esp_lcd_panel_io_handle_t)args;
    
    get_one_line(&line, line_data);

    esp_lcd_panel_io_tx_color(io_handle, -1, line_data, sizeof(line_data));   /* 发送行数据 */
}

static bool line_trans_done_cb(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    gpio_set_level(HUB75_OE_PIN_NUM, 1);                        /* 失能显示 */
    gpio_set_level(HUB75_LINE_ADDR_A_PIN_NUM, line & 1);        /* 行地址,A是低位(待求证) */
    gpio_set_level(HUB75_LINE_ADDR_B_PIN_NUM, (line >> 1) & 1);
    gpio_set_level(HUB75_LINE_ADDR_C_PIN_NUM, (line >> 2) & 1);
    gpio_set_level(HUB75_LINE_ADDR_D_PIN_NUM, (line >> 3) & 1);
    gpio_set_level(HUB75_LAT_PIN_NUM, 1);                       /* 锁存拉高 */

    gpio_set_level(HUB75_LAT_PIN_NUM, 0);   /* 拉低锁存,下降沿锁存 */     
    gpio_set_level(HUB75_LAT_PIN_NUM, 1);
    gpio_set_level(HUB75_OE_PIN_NUM, 0);    /* 使能显示 */

    ESP_LOGI(TAG, "line trans done");
    return false;
}


void app_main(void)
{
    /* 8080总线配置 */
    ESP_LOGI(TAG, "Initialize Intel 8080 bus");
    esp_lcd_i80_bus_handle_t i80_bus = NULL;
    esp_lcd_i80_bus_config_t bus_config = {
        .clk_src = HUB75_CLK_PIN_NUM,
        .dc_gpio_num = -1,
        .wr_gpio_num = HUB75_CLK_PIN_NUM,
        .data_gpio_nums = {
            HUB75_R0,
            HUB75_G0,
            HUB75_B0,
            HUB75_R1,
            HUB75_G1,
            HUB75_B1,
            HUB75_R2,
            HUB75_G2,
            HUB75_B2,
            HUB75_R3,
            HUB75_G3,
            HUB75_B3,
            -1,
            -1,
            -1,
            -1,
        },
        .bus_width = 16,
        .max_transfer_bytes = HUB75_COL_COUNT * sizeof(uint16_t),   /* 每次发一行 */
        .psram_trans_align = PSRAM_DATA_ALIGNMENT,
        .sram_trans_align = 4,
    };
    ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));

    /* 8080IO设备申请 */
    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_i80_config_t io_config = {
        .cs_gpio_num = -1,
        .pclk_hz = HUB75_CLOCK_HZ,
        .trans_queue_depth = 10,
        // .dc_levels = {
        //     .dc_idle_level = 0,
        //     .dc_cmd_level = 0,
        //     .dc_dummy_level = 0,
        //     .dc_data_level = 1,
        // },
        // .flags = {
        //     .swap_color_bytes = !LV_COLOR_16_SWAP, // Swap can be done in LvGL (default) or DMA
        // },
        .on_color_trans_done = line_trans_done_cb,
        .user_ctx = NULL,
        .lcd_cmd_bits = 0,
        .lcd_param_bits = 0,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));

    /* hub75特定类型申请 */
    esp_lcd_panel_handle_t panel_handle = NULL;
    ESP_LOGI(TAG, "Install LCD driver of hub75");
    hub75_panel_config_t panel_config = {
        .line_addr_num = 4,
        .line_addr_gpio_num = {HUB75_LINE_ADDR_A_PIN_NUM, HUB75_LINE_ADDR_B_PIN_NUM, HUB75_LINE_ADDR_C_PIN_NUM, HUB75_LINE_ADDR_D_PIN_NUM},
        .lat_gpio_num = HUB75_LAT_PIN_NUM,
        .oe_gpio_num = HUB75_OE_PIN_NUM,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_hub75(io_handle, &panel_config, &panel_handle));

    /* 定时进行行扫描 */
    ESP_LOGI(TAG, "Create a timer to do line scan");
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = line_scan_loop_cb,
        .arg = io_handle, 
        .name = "line_scan_loop"      
    };
    esp_timer_handle_t line_scan_loop_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &line_scan_loop_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(line_scan_loop_timer, LINE_SCAN_LOOP_PERIOD_MS * 1000)); 


    // /* 测试,发送一次数据 */
    // ESP_LOGI(TAG, "Test send data to LCD");
    // uint16_t color_data[HUB75_COL_COUNT] = {1, 2, 4};
    // gpio_set_level(HUB75_OE_PIN_NUM, 1);
    // gpio_set_level(HUB75_LINE_ADDR_A_PIN_NUM, 0);
    // gpio_set_level(HUB75_LINE_ADDR_B_PIN_NUM, 0);
    // gpio_set_level(HUB75_LINE_ADDR_C_PIN_NUM, 0);
    // gpio_set_level(HUB75_LINE_ADDR_D_PIN_NUM, 0);
    // gpio_set_level(HUB75_LAT_PIN_NUM, 1);
    // esp_lcd_panel_io_tx_color(io_handle, -1, color_data, sizeof(color_data));



    while (1) {
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}