#![no_std]
#![no_main]
#![deny(
    clippy::mem_forget,
    reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
    holding buffers for the duration of a data transfer."
)]
#![feature(iter_intersperse)]
#![feature(array_windows)]
#![feature(allocator_api)]

use core::alloc::{GlobalAlloc, Layout};
use core::future::IntoFuture;
use core::iter::once;

use acid_firmware::st7701s::{
    CmdCn2bkxsel, CmdCn2bkxselArg0, CmdCn2bkxselArg1, CmdCn2bkxselArg2, CmdCn2bkxselArg3,
    CmdCn2bkxselArg4, CmdColmod, CmdColmodArg0, CmdDispon, CmdInvsel, CmdInvselArg0, CmdInvselArg1,
    CmdLneset, CmdLnesetArg0, CmdLnesetArg1, CmdMadctl, CmdMadctlArg0, CmdMipiset1,
    CmdMipiset1Arg0, CmdNvgamctrl, CmdNvgamctrlArg0, CmdNvgamctrlArg1, CmdNvgamctrlArg10,
    CmdNvgamctrlArg11, CmdNvgamctrlArg12, CmdNvgamctrlArg13, CmdNvgamctrlArg14, CmdNvgamctrlArg15,
    CmdNvgamctrlArg2, CmdNvgamctrlArg3, CmdNvgamctrlArg4, CmdNvgamctrlArg5, CmdNvgamctrlArg6,
    CmdNvgamctrlArg7, CmdNvgamctrlArg8, CmdNvgamctrlArg9, CmdPclks2, CmdPclks2Arg0, CmdPdr1,
    CmdPdr1Arg0, CmdPdr2, CmdPdr2Arg0, CmdPorctrl, CmdPorctrlArg0, CmdPorctrlArg1, CmdPvgamctrl,
    CmdPvgamctrlArg0, CmdPvgamctrlArg1, CmdPvgamctrlArg10, CmdPvgamctrlArg11, CmdPvgamctrlArg12,
    CmdPvgamctrlArg13, CmdPvgamctrlArg14, CmdPvgamctrlArg15, CmdPvgamctrlArg2, CmdPvgamctrlArg3,
    CmdPvgamctrlArg4, CmdPvgamctrlArg5, CmdPvgamctrlArg6, CmdPvgamctrlArg7, CmdPvgamctrlArg8,
    CmdPvgamctrlArg9, CmdPwctrl1, CmdPwctrl1Arg0, CmdPwctrl2, CmdPwctrl2Arg0, CmdPwctrl3,
    CmdPwctrl3Arg0, CmdRgbctrl, CmdRgbctrlArg0, CmdRgbctrlArg1, CmdRgbctrlArg2, CmdSlpout,
    CmdTescmd, CmdTescmdArg0, CmdVcoms, CmdVcomsArg0, CmdVghss, CmdVghssArg0, CmdVgls, CmdVglsArg0,
    CmdVrhs, CmdVrhsArg0, Command, CustomCommand,
};
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_alloc::{EspHeap, HeapRegion, MemoryCapability};
use esp_backtrace as _;
use esp_hal::clock::CpuClock;
use esp_hal::delay::Delay;
use esp_hal::dma::{
    BurstConfig, Channel, DmaChannel, DmaDescriptor, DmaDescriptorFlags, DmaLoopBuf, DmaRxTxBuf,
    DmaTxBuf, ExternalBurstConfig,
};
use esp_hal::gpio::{
    self, Flex, Input, InputConfig, Level, Output, OutputConfig, OutputPin, Pin, Pull,
};
use esp_hal::i2c::master::{I2c, I2cAddress};
use esp_hal::lcd_cam::lcd::dpi::{self, Dpi, Format, FrameTiming};
use esp_hal::lcd_cam::lcd::i8080::I8080;
use esp_hal::lcd_cam::lcd::{self, ClockMode, DelayMode, Lcd, Phase, Polarity};
use esp_hal::lcd_cam::{BitOrder, LcdCam};
use esp_hal::mcpwm::operator::PwmPin;
use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
use esp_hal::peripherals::PSRAM;
use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock};
use esp_hal::rtc_cntl::sleep;
use esp_hal::spi::master::{Address, Command as SpiCommand, DataMode, Spi};
use esp_hal::time::Rate;
use esp_hal::timer::systimer::SystemTimer;
use esp_hal::{dma_buffers, dma_loop_buffer, dma_tx_buffer, Async};
use itertools::chain;
use lazy_static::lazy_static;
use log::info;

extern crate alloc;

// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();

static PSRAM_ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty();

#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
    // generator version: 0.5.0

    esp_println::logger::init_logger_from_env();
    info!("Logger initialized!");

    let config = esp_hal::Config::default()
        .with_cpu_clock(CpuClock::max())
        .with_psram(PsramConfig {
            size: PsramSize::AutoDetect,
            core_clock: Some(SpiTimingConfigCoreClock::SpiTimingConfigCoreClock80m),
            flash_frequency: FlashFreq::default(),
            ram_frequency: SpiRamFreq::Freq80m,
        });
    let peripherals: esp_hal::peripherals::Peripherals = esp_hal::init(config);
    info!("System initialized!");

    // Use the internal DRAM as the heap.
    esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64 * 1024);
    info!("Heap initialized! {:#?}", esp_alloc::HEAP.stats());

    let timer0 = SystemTimer::new(peripherals.SYSTIMER);
    esp_hal_embassy::init(timer0.alarm0);
    info!("Embassy initialized!");

    // Initialize the PSRAM allocator.
    {
        let (psram_offset, psram_size) = esp_hal::psram::psram_raw_parts(&peripherals.PSRAM);
        unsafe {
            PSRAM_ALLOCATOR.add_region(HeapRegion::new(
                psram_offset,
                psram_size,
                MemoryCapability::External.into(),
            ));
        }
    }
    const BUFFER_LEN: usize = core::mem::size_of::<u16>()
        * (360 + /* TODO: Figure out why more bytes are needed: */ 8)
        * 960;
    // Note: We just leak this buffer.
    let buffer_ptr = unsafe {
        // ⚠️ Note: For chips that support DMA to/from PSRAM (ESP32-S3) DMA transfers to/from PSRAM
        // have extra alignment requirements. The address and size of the buffer pointed to by each
        // descriptor must be a multiple of the cache line (block) size. This is 32 bytes on ESP32-S3.
        PSRAM_ALLOCATOR.alloc(Layout::from_size_align(BUFFER_LEN, 32).unwrap())
    };
    let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_ptr, BUFFER_LEN) };
    let burst_config: BurstConfig = ExternalBurstConfig::Size16.into();

    info!(
        "PSRAM SPI burst config: max_compatible_chunk_size={}",
        burst_config.max_compatible_chunk_size()
    );

    let dma_buf_descs_len =
        esp_hal::dma::descriptor_count(BUFFER_LEN, burst_config.max_compatible_chunk_size(), false);
    // Descriptors are initialized by `DmaTxBuf::new`.
    let dma_buf_descs = Box::leak(vec![DmaDescriptor::EMPTY; dma_buf_descs_len].into_boxed_slice());
    let mut dma_buf = DmaTxBuf::new(dma_buf_descs, buffer).unwrap();
    // let mut dma_buf = dma_tx_buffer!(BUFFER_LEN).unwrap();

    // TODO: Spawn some tasks
    let _ = spawner;

    // Enable LDO2
    let _ = Output::new(peripherals.GPIO17, Level::High, OutputConfig::default());

    // let mut x = Spi::new(
    //     peripherals.SPI2,
    //     esp_hal::spi::master::Config::default().with_frequency(Rate::from_mhz(1)), // For debugging
    // )
    // .unwrap()
    // .with_mosi(peripherals.GPIO35)
    // .with_sck(peripherals.GPIO36);

    // TODO: Use PWM to control the pwm_pin.
    let mut _pwm = McPwm::new(peripherals.MCPWM0, PeripheralClockConfig::with_prescaler(1));
    let mut _pwm_pin = Output::new(peripherals.GPIO21, Level::High, OutputConfig::default());

    let mut matrix_interrupt = Input::new(
        peripherals.GPIO7,
        InputConfig::default().with_pull(Pull::Up),
    );
    // let mut sda = Flex::new(peripherals.GPIO8);
    // let mut scl = Output::new(peripherals.GPIO9, Level::High, OutputConfig::default());
    let mut i2c = I2c::new(
        peripherals.I2C0,
        esp_hal::i2c::master::Config::default().with_frequency(Rate::from_khz(400)),
    )
    .unwrap()
    .with_sda(peripherals.GPIO8)
    .with_scl(peripherals.GPIO9);

    const I2C_ADDR_MATRIX_LEFT: I2cAddress = I2cAddress::SevenBit(0b0100000);
    const I2C_ADDR_MATRIX_RIGHT: I2cAddress = I2cAddress::SevenBit(0b0100001);

    for addr in [I2C_ADDR_MATRIX_LEFT, I2C_ADDR_MATRIX_RIGHT] {
        i2c.write(addr, &[0b10010000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
            .unwrap();
    }

    let mut sck = Output::new(peripherals.GPIO36, Level::High, OutputConfig::default());
    let mut mosi = Flex::new(peripherals.GPIO35);
    let mut cs = Output::new(peripherals.GPIO6, Level::High, OutputConfig::default());

    mosi.set_input_enable(false);
    mosi.set_output_enable(true);

    info!("init sequence writing");

    // cs.set_low();
    // for op in INIT_SEQUENCE {
    //     match *op {
    //         InitOp::WriteComm(data) => spi_write_word(false, data, &mut mosi, &mut sck),
    //         InitOp::WriteData(data) => spi_write_word(true, data, &mut mosi, &mut sck),
    //         InitOp::SleepMs(ms) => Timer::after_millis(ms).await,
    //         _ => panic!(),
    //     }
    // }
    // cs.set_high();
    for (subsequence, delay_ms) in &*INIT_SEQUENCE_COMMANDS {
        for command in subsequence {
            spi_write(
                command.address(),
                command.args_iter().copied(),
                &mut mosi,
                &mut sck,
                &mut cs,
            );
            info!("COMM 0x{:02X}", command.address());
            for arg in command.args_iter() {
                info!("DATA 0x{arg:02X}");
            }
        }
        Timer::after_millis(*delay_ms).await
    }

    info!("init sequence written");

    // Timer::after_secs(1).await;

    // x.half_duplex_write(
    //     DataMode::Single,
    //     Command::_9Bit(0xFFFF, DataMode::Single),
    //     Address::None,
    //     0,
    //     &[],
    // )
    // .unwrap();

    // info!("Derp");

    let mut lcd = LcdCam::new(peripherals.LCD_CAM).into_async().lcd;
    let lcd_config = lcd::dpi::Config::default()
        // Internal memory can use the full 16 MHz, but when external PSRAM is used, it cannot keep up with the display.
        // For that reason, we choose the highest value for which it doesn't glitch by showing black
        // stripes on the screen.
        //
        // There are three knobs you can turn to improve the bandwidth situation.
        // - increase psram frequency
        // - decrease the peripheral's frequency
        // - prevent flash from being used whilst your program is running. (There's a PR to make
        //   this easy to do)
        //   https://github.com/esp-rs/esp-hal/pull/3024
        .with_frequency(Rate::from_mhz(11)) // From Adafruit
        .with_clock_mode(ClockMode {
            polarity: Polarity::IdleLow, // From Adafruit
            phase: Phase::ShiftHigh,     // From Adafruit
        })
        .with_format(Format {
            enable_2byte_mode: true,
            ..Default::default()
        })
        .with_timing({
            // Adafruit's config for this LCD:
            // https://github.com/adafruit/Adafruit_CircuitPython_Qualia/blob/742d336e05e6a4d8bdaa46e15bbf60c9f30d2eba/adafruit_qualia/displays/bar240x960.py#L81-L97
            // https://github.com/adafruit/Adafruit_CircuitPython_Qualia/blob/742d336e05e6a4d8bdaa46e15bbf60c9f30d2eba/adafruit_qualia/displays/__init__.py#L59-L62
            // CircuitPython code handling Adafruit's config
            // https://github.com/adafruit/circuitpython/blob/97c6617817e95b1f6aa2ce458778aaa8371de39b/ports/espressif/common-hal/dotclockframebuffer/DotClockFramebuffer.c#L63
            // ESP-IDF peripheral configuration code:
            // https://github.com/espressif/esp-idf/blob/800f141f94c0f880c162de476512e183df671307/components/esp_lcd/rgb/esp_lcd_panel_rgb.c#L556
            // Espressif's docs:
            // https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32s3/api-reference/peripherals/lcd/rgb_lcd.html#structures

            // TODO: Investigate PORCTRL instruction in datasheet of ST7701
            let horizontal_resolution: usize = 240;
            let vertical_resolution = 960;
            let overscan_left = 120;
            let vsync_width = 8;
            let hsync_width = 8;
            let horizontal_blank_front_porch = 20;
            let horizontal_blank_back_porch = 20;
            let vertical_blank_front_porch = 20;
            let vertical_blank_back_porch = 20;
            let hsync_position = 0;
            let horizontal_active_width = (horizontal_resolution + overscan_left).div_ceil(16) * 16; // Round up to a multiple of 16.
            let vertical_active_height = vertical_resolution;
            FrameTiming {
                horizontal_total_width: hsync_width
                    + horizontal_blank_back_porch
                    + horizontal_active_width
                    + horizontal_blank_front_porch,
                vertical_total_height: vsync_width
                    + vertical_blank_back_porch
                    + vertical_active_height
                    + vertical_blank_front_porch,
                horizontal_blank_front_porch: horizontal_blank_front_porch + hsync_width,
                vertical_blank_front_porch: vertical_blank_front_porch + vsync_width,
                horizontal_active_width,
                vertical_active_height,
                vsync_width,
                hsync_width,
                hsync_position,
            }
        })
        .with_hsync_idle_level(Level::High)
        .with_vsync_idle_level(Level::High)
        .with_de_idle_level(Level::Low);

    // let mut dma_buf = dma_loop_buffer!(2 * 32);
    // let mut dma_buf = dma_tx_buffer!(2 * 960 * 320 / 4).unwrap();

    // let tx_buf = dma_tx_buffer!(core::mem::size_of::<u16>() * 280 * 1000).unwrap();
    // let (c_rx, c_tx) = peripherals.DMA_CH0.split();
    // TODO: https://github.com/esp-rs/esp-hal/blob/main/qa-test/src/bin/lcd_dpi.rs
    let mut dpi = Dpi::new(lcd, peripherals.DMA_CH2, lcd_config)
        .unwrap()
        .with_de(peripherals.GPIO37)
        .with_pclk(peripherals.GPIO34)
        .with_hsync(peripherals.GPIO44)
        .with_vsync(peripherals.GPIO43)
        // Blue
        .with_data0(peripherals.GPIO38)
        .with_data1(peripherals.GPIO39)
        .with_data2(peripherals.GPIO40)
        .with_data3(peripherals.GPIO41)
        .with_data4(peripherals.GPIO42)
        // Green
        .with_data5(peripherals.GPIO5)
        .with_data6(peripherals.GPIO12)
        .with_data7(peripherals.GPIO13)
        .with_data8(peripherals.GPIO14)
        .with_data9(peripherals.GPIO15)
        .with_data10(peripherals.GPIO16)
        // Red
        .with_data11(peripherals.GPIO0)
        .with_data12(peripherals.GPIO1)
        .with_data13(peripherals.GPIO2)
        .with_data14(peripherals.GPIO3)
        .with_data15(peripherals.GPIO4);

    const MAX_RED: u8 = (1 << 5) - 1;
    const MAX_GREEN: u8 = (1 << 6) - 1;
    const MAX_BLUE: u8 = (1 << 5) - 1;

    fn rgb(r: u8, g: u8, b: u8) -> u16 {
        (((r & MAX_RED) as u16) << 11) | (((g & MAX_GREEN) as u16) << 5) | ((b & MAX_BLUE) as u16)
    }

    // let mut colors = core::iter::empty()
    //     // Start with red and gradually add green
    //     .chain((0..=MAX_GREEN).map(|g| rgb(MAX_RED, g, 0)))
    //     // Then remove the red
    //     .chain((0..=MAX_RED).rev().map(|r| rgb(r, MAX_GREEN, 0)))
    //     // Then add blue
    //     .chain((0..=MAX_BLUE).map(|b| rgb(0, MAX_GREEN, b)))
    //     // Then remove green
    //     .chain((0..=MAX_GREEN).rev().map(|g| rgb(0, g, MAX_BLUE)))
    //     // Then add red
    //     .chain((0..=MAX_RED).map(|r| rgb(r, 0, MAX_BLUE)))
    //     // Then remove blue
    //     .chain((0..=MAX_BLUE).rev().map(|b| rgb(MAX_RED, 0, b)))
    //     // Once we get we have red, and we can start again.
    //     .cycle();
    // let mut colors = colors
    //     .take(len - 10)
    //     .chain(core::iter::repeat_n(0, 10))
    //     .cycle();

    // This is wrong, only a left padding of 120 should be needed.
    // const padding_left: usize = 120;
    // const padding_right: usize = 0;
    const padding_left: usize = 121;
    const padding_right: usize = 7;

    const colors_width: usize = 240;
    const colors_height: usize = 960;

    fn row(edge: u16) -> impl Iterator<Item = u16> + Clone {
        chain![
            core::iter::repeat_n(rgb(0, 0, 0xFF), padding_left),
            core::iter::once(rgb(0xFF, 0xFF, 0xFF)),
            core::iter::repeat_n(edge, colors_width - 2),
            core::iter::once(rgb(0xFF, 0xFF, 0xFF)),
            core::iter::repeat_n(rgb(0, 0, 0xFF), padding_right),
        ]
    };

    let mut colors = chain![
        row(rgb(0xFF, 0, 0)),
        core::iter::repeat_n(row(0), colors_height - 2).flatten(),
        row(rgb(0xFF, 0xFF, 0)),
    ];

    // for _ in 0..120 {
    //     colors.next();
    // }

    // let write = |command: u8, args: &[u8]| {
    //     // TODO: 3-wire SPI requires 9-bit words, where each 8-bit word is prefixed by 1 bit
    //     // signifying whether the word is a parameter or not.
    //     // Interleave into a buffer and terminate with NOP commands.
    //     x.half_duplex_write(
    //         DataMode::Single,
    //         Command::_9Bit(command as u16, DataMode::Single),
    //         Address::None,
    //         0,
    //         &[],
    //     )
    //     .unwrap();
    // };

    let mut i = 0;

    loop {
        // LCD stuff
        {
            // let mut buf = [0u8; 3];
            // x.half_duplex_read(
            //     DataMode::Single,
            //     Command::_9Bit(0x04, DataMode::Single),
            //     Address::None,
            //     1,
            //     &mut buf,
            // )
            // .unwrap();
            let mut buf = [0_u8, 0, 0];
            spi_read(
                0xDA,
                false,
                &mut mosi,
                &mut sck,
                &mut cs,
                &mut buf[0..][..1],
            );
            spi_read(
                0xDB,
                false,
                &mut mosi,
                &mut sck,
                &mut cs,
                &mut buf[1..][..1],
            );
            spi_read(
                0xDC,
                false,
                &mut mosi,
                &mut sck,
                &mut cs,
                &mut buf[2..][..1],
            );
            let mut buf2 = [0_u8, 0, 0];
            spi_read(0x04, true, &mut mosi, &mut sck, &mut cs, &mut buf2);
            // info!("Hello world! {buf:02x?} {buf2:02x?}");

            if i == 0 {
                for (chunk, color) in dma_buf.as_mut_slice().chunks_mut(2).zip(&mut colors) {
                    chunk.copy_from_slice(&color.to_le_bytes());
                }

                info!("chunk addr: {}", dma_buf.as_slice().as_ptr() as usize);
                // colors.next(); // Shift colors
            }

            let transfer = dpi.send(false, dma_buf).map_err(|e| e.0).unwrap();
            (_, dpi, dma_buf) = transfer.wait();
        }

        // IO stuff
        {
            let mut input_register_banks = [0_u8; 10];
            i2c.write_read(
                I2C_ADDR_MATRIX_LEFT,
                &[0x80],
                &mut input_register_banks[..5],
            )
            .unwrap();
            i2c.write_read(
                I2C_ADDR_MATRIX_RIGHT,
                &[0x80],
                &mut input_register_banks[5..],
            )
            .unwrap();
            let mut rows = [0_u16; 5];
            for (row, (left, right)) in rows.iter_mut().zip(core::iter::zip(
                &input_register_banks[..5],
                &input_register_banks[5..],
            )) {
                *row = *left as u16 | ((*right as u16) << 6);
            }
            info!("rows: {rows:x?}")
        }

        i += 1;

        // x.half_duplex_write(
        //     DataMode::Single,
        //     Command::_9Bit(0xFFFF, DataMode::Single),
        //     Address::None,
        //     0,
        //     &[0x77],
        // )
        // .unwrap();
        // Timer::after(Duration::from_secs(1)).await;
    }

    // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0-rc.0/examples/src/bin
}

fn spi_delay() {
    Delay::new().delay_micros(1);
}

fn spi_dummy_bit(sck: &mut Output) {
    sck.set_low();
    spi_delay();
    sck.set_high();
    spi_delay();
}

fn spi_write_bit(bit: bool, mosi: &mut Flex, sck: &mut Output) {
    mosi.set_level(if bit { Level::High } else { Level::Low });
    sck.set_low();
    spi_delay();
    sck.set_high();
    spi_delay();
}

fn spi_read_bit(mosi: &mut Flex, sck: &mut Output) -> bool {
    sck.set_low();
    spi_delay();
    sck.set_high();
    spi_delay();
    mosi.is_high()
}

fn spi_write_bits(bits: impl Iterator<Item = bool>, mosi: &mut Flex, sck: &mut Output) {
    for bit in bits {
        spi_write_bit(bit, mosi, sck);
    }
}

fn spi_write_word(is_param: bool, data: u8, mosi: &mut Flex, sck: &mut Output) {
    assert!(sck.is_set_high());
    spi_write_bits(
        once(is_param).chain((0..8).map(|i| (data >> i) & 1 != 0).rev()),
        mosi,
        sck,
    );
}

fn spi_write(
    command: u8,
    args: impl IntoIterator<Item = u8>,
    mosi: &mut Flex,
    sck: &mut Output,
    cs: &mut Output,
) {
    cs.set_low();
    spi_write_word(false, command, mosi, sck);

    for arg in args {
        spi_write_word(true, arg, mosi, sck);
    }

    cs.set_high();
}

fn spi_read(
    command: u8,
    dummy_cycle: bool,
    mosi: &mut Flex,
    sck: &mut Output,
    cs: &mut Output,
    output_buffer: &mut [u8],
) {
    output_buffer.fill(0);

    cs.set_low();
    spi_write_word(false, command, mosi, sck);

    mosi.set_output_enable(false);
    mosi.set_input_enable(true);

    if dummy_cycle {
        spi_dummy_bit(sck);
    }

    for output_byte in output_buffer {
        for i in (0..8).rev() {
            if spi_read_bit(mosi, sck) {
                *output_byte |= 1 << i;
            }
        }
    }

    mosi.set_input_enable(false);
    mosi.set_output_enable(true);

    cs.set_high();
}

#[derive(Clone, Copy)]
enum InitOp {
    Command(u8, &'static [u8]),
    WriteComm(u8),
    WriteData(u8),
    SleepMs(u64),
}

// impl InitOp {
//     async fn execute<'a>(&'a self, mosi: &'a mut Output<'a>, sck: &'a mut Output<'a>) {
//         match *self {
//             InitOp::WriteComm(data) => spi_write(false, data, mosi, sck),
//             InitOp::WriteData(data) => spi_write(true, data, mosi, sck),
//             InitOp::SleepMs(ms) => Timer::after_millis(ms).await,
//         }
//     }
// }

use InitOp::*;

lazy_static! {
    static ref INIT_SEQUENCE_COMMANDS: Vec<(Vec<Box<dyn Command + Send + Sync>>, u64)> = vec![
        (vec![
            Box::new(CmdCn2bkxsel(
                CmdCn2bkxselArg0::new(),
                CmdCn2bkxselArg1::new(),
                CmdCn2bkxselArg2::new(),
                CmdCn2bkxselArg3::new(),
                CmdCn2bkxselArg4::new().with_bksel(0x3).with_cn2(true),
            )),
            Box::new(CustomCommand {
                address: 0xEF,
                args: &[0x08],
            }),
            Box::new(CmdCn2bkxsel(
                CmdCn2bkxselArg0::new(),
                CmdCn2bkxselArg1::new(),
                CmdCn2bkxselArg2::new(),
                CmdCn2bkxselArg3::new(),
                CmdCn2bkxselArg4::new().with_bksel(0x0).with_cn2(true),
            )),
            Box::new(CmdLneset(
                CmdLnesetArg0::new().with_bar(119).with_lde_en(false),
                CmdLnesetArg1::new().with_line_delta(0),
            )),
            Box::new(CmdPorctrl(
                CmdPorctrlArg0::new().with_vbp(17),
                CmdPorctrlArg1::new().with_vfp(12),
            )),
            Box::new(CmdInvsel(
                CmdInvselArg0::new().with_nlinv(0b111),
                CmdInvselArg1::new().with_rtni(2),
            )),
            Box::new(CustomCommand {
                address: 0xCC,
                args: &[0x30],
            }),
            Box::new(CmdPvgamctrl(
                CmdPvgamctrlArg0::new().with_vc0p(6).with_aj0p(0),
                CmdPvgamctrlArg1::new().with_vc4p(15).with_aj1p(3),
                CmdPvgamctrlArg2::new().with_vc8p(14).with_aj2p(0),
                CmdPvgamctrlArg3::new().with_vc16p(12),
                CmdPvgamctrlArg4::new().with_vc24p(15).with_aj3p(0),
                CmdPvgamctrlArg5::new().with_vc52p(3),
                CmdPvgamctrlArg6::new().with_vc80p(0),
                CmdPvgamctrlArg7::new().with_vc108p(10),
                CmdPvgamctrlArg8::new().with_vc147p(7),
                CmdPvgamctrlArg9::new().with_vc175p(27),
                CmdPvgamctrlArg10::new().with_vc203p(3),
                CmdPvgamctrlArg11::new().with_vc231p(18).with_aj4p(0),
                CmdPvgamctrlArg12::new().with_vc239p(16),
                CmdPvgamctrlArg13::new().with_vc247p(37).with_aj5p(0),
                CmdPvgamctrlArg14::new().with_vc251p(54).with_aj6p(0),
                CmdPvgamctrlArg15::new().with_vc255p(30).with_aj7p(0),
            )),
            Box::new(CmdNvgamctrl(
                CmdNvgamctrlArg0::new().with_vc0n(12).with_aj0n(0),
                CmdNvgamctrlArg1::new().with_vc4n(14).with_aj1n(3),
                CmdNvgamctrlArg2::new().with_vc8n(18).with_aj2n(0),
                CmdNvgamctrlArg3::new().with_vc16n(12),
                CmdNvgamctrlArg4::new().with_vc24n(14).with_aj3n(0),
                CmdNvgamctrlArg5::new().with_vc52n(6),
                CmdNvgamctrlArg6::new().with_vc80n(3),
                CmdNvgamctrlArg7::new().with_vc108n(6),
                CmdNvgamctrlArg8::new().with_vc147n(8),
                CmdNvgamctrlArg9::new().with_vc175n(35),
                CmdNvgamctrlArg10::new().with_vc203n(6),
                CmdNvgamctrlArg11::new().with_vc231n(18).with_aj4n(0),
                CmdNvgamctrlArg12::new().with_vc239n(16),
                CmdNvgamctrlArg13::new().with_vc247n(48).with_aj5n(0),
                CmdNvgamctrlArg14::new().with_vc251n(47).with_aj6n(0),
                CmdNvgamctrlArg15::new().with_vc255n(31).with_aj7n(0),
            )),
            Box::new(CmdCn2bkxsel(
                CmdCn2bkxselArg0::new(),
                CmdCn2bkxselArg1::new(),
                CmdCn2bkxselArg2::new(),
                CmdCn2bkxselArg3::new(),
                CmdCn2bkxselArg4::new().with_bksel(0x1).with_cn2(true),
            )),
            Box::new(CmdVrhs(
                CmdVrhsArg0::new().with_vrha(115),
            )),
            Box::new(CmdVcoms(
                CmdVcomsArg0::new().with_vcom(124),
            )),
            Box::new(CmdVghss(
                // The first bit is set to 1 in the original init code, but not here.
                CmdVghssArg0::new().with_vghss(0x3), // 13 V
            )),
            Box::new(CmdTescmd(
                CmdTescmdArg0::new(),
            )),
            Box::new(CmdVgls(
                CmdVglsArg0::new().with_vgls(0x9) // -10.17 V
            )),
            Box::new(CmdPwctrl1(
                CmdPwctrl1Arg0::new()
                .with_apos(0x3) // Max
                .with_apis(0x1) // Min
                .with_ap(0x2) // Middle
            )),
            Box::new(CmdPwctrl2(
                CmdPwctrl2Arg0::new()
                    .with_avcl(0x3) // -5 V
                    .with_avdd(0x3) // 6.8 V
            )),
            Box::new(CmdPwctrl3(
                CmdPwctrl3Arg0::new()
                .with_svno_pum(0) // Cell setting 4
                .with_svpo_pum(0x1)  // Cell setting 5
            )),
            Box::new(CmdPclks2(
                CmdPclks2Arg0::new().with_sbstcks(0x3)
            )),
            Box::new(CmdPdr1(
                CmdPdr1Arg0::new().with_t2d(8) // 1.6 us
            )),
            Box::new(CmdPdr2(
                CmdPdr2Arg0::new().with_t3d(8) // 6.4 us
            )),
            Box::new(CmdMipiset1(
                CmdMipiset1Arg0::new().with_err_sel(0).with_eotp_en(true),
            )),
            Box::new(CustomCommand {
                address: 0xE0,
                args: &[
                    0x00,
                    0x00,
                    0x02,
                    0x00,
                    0x00,
                    0x0C,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE1,
                args: &[
                    0x05,
                    0x96,
                    0x07,
                    0x96,
                    0x06,
                    0x96,
                    0x08,
                    0x96,
                    0x00,
                    0x44,
                    0x44,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE2,
                args: &[
                    0x00,
                    0x00,
                    0x03,
                    0x03,
                    0x00,
                    0x00,
                    0x02,
                    0x00,
                    0x00,
                    0x00,
                    0x02,
                    0x00,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE3,
                args: &[
                    0x00,
                    0x00,
                    0x33,
                    0x33,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE4,
                args: &[
                    0x44,
                    0x44,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE5,
                args: &[
                    0x0D,
                    0xD4,
                    0x28,
                    0x8C,
                    0x0F,
                    0xD6,
                    0x28,
                    0x8C,
                    0x09,
                    0xD0,
                    0x28,
                    0x8C,
                    0x0B,
                    0xD2,
                    0x28,
                    0x8C,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE6,
                args: &[
                    0x00,
                    0x00,
                    0x33,
                    0x33,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE7,
                args: &[
                    0x44,
                    0x44,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xE8,
                args: &[
                    0x0E,
                    0xD5,
                    0x28,
                    0x8C,
                    0x10,
                    0xD7,
                    0x28,
                    0x8C,
                    0x0A,
                    0xD1,
                    0x28,
                    0x8C,
                    0x0C,
                    0xD3,
                    0x28,
                    0x8C,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xEB,
                args: &[
                    0x00,
                    0x01,
                    0xE4,
                    0xE4,
                    0x44,
                    0x00,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xED,
                args: &[
                    0xF3,
                    0xC1,
                    0xBA,
                    0x0F,
                    0x66,
                    0x77,
                    0x44,
                    0x55,
                    0x55,
                    0x44,
                    0x77,
                    0x66,
                    0xF0,
                    0xAB,
                    0x1C,
                    0x3F,
                ]
            }),
            Box::new(CustomCommand {
                address: 0xEF,
                args: &[
                    0x10,
                    0x0D,
                    0x04,
                    0x08,
                    0x3F,
                    0x1F,
                ]
            }),
            Box::new(CmdCn2bkxsel(
                CmdCn2bkxselArg0::new(),
                CmdCn2bkxselArg1::new(),
                CmdCn2bkxselArg2::new(),
                CmdCn2bkxselArg3::new(),
                CmdCn2bkxselArg4::new().with_bksel(0x3).with_cn2(true),
            )),
            Box::new(CustomCommand {
                address: 0xE8,
                args: &[
                    0x00,
                    0x0E,
                ],
            }),
            Box::new(CmdSlpout()),
        ], 120),
        (vec![
            Box::new(CustomCommand {
                address: 0xE8,
                args: &[
                    0x00,
                    0x0C,
                ],
            }),
        ], 10),
        (vec![
            Box::new(CustomCommand {
                address: 0xE8,
                args: &[
                    0x40,
                    0x00,
                ],
            }),
            Box::new(CmdCn2bkxsel(
                CmdCn2bkxselArg0::new(),
                CmdCn2bkxselArg1::new(),
                CmdCn2bkxselArg2::new(),
                CmdCn2bkxselArg3::new(),
                CmdCn2bkxselArg4::new().with_bksel(0).with_cn2(false),
            )),
            Box::new(CmdMadctl(
                CmdMadctlArg0::new().with_bgr(false).with_ml(false),
            )),
            Box::new(CmdColmod(
                CmdColmodArg0::new().with_vipf(6) // 18-bit pixel
            )),
            Box::new(CmdDispon()),
        ], 20),
        // (vec![
        //     Box::new(CmdCn2bkxsel(
        //         CmdCn2bkxselArg0::new(),
        //         CmdCn2bkxselArg1::new(),
        //         CmdCn2bkxselArg2::new(),
        //         CmdCn2bkxselArg3::new(),
        //         CmdCn2bkxselArg4::new().with_bksel(0).with_cn2(true),
        //     )),
        //     Box::new(CmdPorctrl(
        //         CmdPorctrlArg0::new().with_vbp(0xFF),
        //         CmdPorctrlArg1::new().with_vfp(0x0),
        //     )),
        //     // Box::new(CmdRgbctrl(
        //     //     CmdRgbctrlArg0::new()
        //     //         .with_ep(false)
        //     //         .with_dp(false)
        //     //         .with_hsp(false)
        //     //         .with_vsp(false)
        //     //         .with_de_hv(true),
        //     //     CmdRgbctrlArg1::new()
        //     //         .with_hbp_hvrgb(16),
        //     //     CmdRgbctrlArg2::new()
        //     //         .with_vbp_hvrgb(8),
        //     // )),
        //     Box::new(CmdCn2bkxsel(
        //         CmdCn2bkxselArg0::new(),
        //         CmdCn2bkxselArg1::new(),
        //         CmdCn2bkxselArg2::new(),
        //         CmdCn2bkxselArg3::new(),
        //         CmdCn2bkxselArg4::new().with_bksel(0).with_cn2(false),
        //     )),
        // ], 20),
    ];
}

const INIT_SEQUENCE: &[InitOp] = &[
    WriteComm(0xff),
    WriteData(0x77),
    WriteData(0x01),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x13),
    WriteComm(0xef),
    WriteData(0x08),
    WriteComm(0xff),
    WriteData(0x77),
    WriteData(0x01),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x10),
    WriteComm(0xc0),
    WriteData(0x77),
    WriteData(0x00),
    WriteComm(0xc1),
    WriteData(0x11),
    WriteData(0x0c),
    WriteComm(0xc2),
    WriteData(0x07),
    WriteData(0x02),
    WriteComm(0xcc),
    WriteData(0x30),
    //
    WriteComm(0xB0),
    WriteData(0x06),
    WriteData(0xCF),
    WriteData(0x14),
    WriteData(0x0C),
    WriteData(0x0F),
    WriteData(0x03),
    WriteData(0x00),
    WriteData(0x0A),
    WriteData(0x07),
    WriteData(0x1B),
    WriteData(0x03),
    WriteData(0x12),
    WriteData(0x10),
    WriteData(0x25),
    WriteData(0x36),
    WriteData(0x1E),
    //
    WriteComm(0xB1),
    WriteData(0x0C),
    WriteData(0xD4),
    WriteData(0x18),
    WriteData(0x0C),
    WriteData(0x0E),
    WriteData(0x06),
    WriteData(0x03),
    WriteData(0x06),
    WriteData(0x08),
    WriteData(0x23),
    WriteData(0x06),
    WriteData(0x12),
    WriteData(0x10),
    WriteData(0x30),
    WriteData(0x2F),
    WriteData(0x1F),
    //
    WriteComm(0xff),
    WriteData(0x77),
    WriteData(0x01),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x11),
    //
    WriteComm(0xb0),
    WriteData(0x73),
    //
    WriteComm(0xb1),
    WriteData(0x7C),
    //
    WriteComm(0xb2),
    WriteData(0x83),
    //
    WriteComm(0xb3),
    WriteData(0x80),
    //
    WriteComm(0xb5),
    WriteData(0x49),
    //
    WriteComm(0xb7),
    WriteData(0x87),
    //
    WriteComm(0xb8),
    WriteData(0x33),
    //
    WriteComm(0xb9),
    WriteData(0x10),
    WriteData(0x1f), // ???
    //
    WriteComm(0xbb),
    WriteData(0x03),
    //
    WriteComm(0xc1),
    WriteData(0x08),
    //
    WriteComm(0xc2),
    WriteData(0x08),
    //
    WriteComm(0xd0),
    WriteData(0x88),
    // TODO: Unknown command
    WriteComm(0xe0),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x02),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x0c),
    // TODO: Unknown command
    WriteComm(0xe1),
    WriteData(0x05),
    WriteData(0x96),
    WriteData(0x07),
    WriteData(0x96),
    WriteData(0x06),
    WriteData(0x96),
    WriteData(0x08),
    WriteData(0x96),
    WriteData(0x00),
    WriteData(0x44),
    WriteData(0x44),
    // TODO: Unknown command
    WriteComm(0xe2),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x03),
    WriteData(0x03),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x02),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x02),
    WriteData(0x00),
    // TODO: Unknown command
    WriteComm(0xe3),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x33),
    WriteData(0x33),
    // TODO: Unknown command
    WriteComm(0xe4),
    WriteData(0x44),
    WriteData(0x44),
    // TODO: Unknown command
    WriteComm(0xe5),
    WriteData(0x0d),
    WriteData(0xd4),
    WriteData(0x28),
    WriteData(0x8c),
    WriteData(0x0f),
    WriteData(0xd6),
    WriteData(0x28),
    WriteData(0x8c),
    WriteData(0x09),
    WriteData(0xd0),
    WriteData(0x28),
    WriteData(0x8c),
    WriteData(0x0b),
    WriteData(0xd2),
    WriteData(0x28),
    WriteData(0x8c),
    // TODO: Unknown command
    WriteComm(0xe6),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x33),
    WriteData(0x33),
    // TODO: Unknown command
    WriteComm(0xe7),
    WriteData(0x44),
    WriteData(0x44),
    // TODO: Unknown command
    WriteComm(0xe8),
    WriteData(0x0e),
    WriteData(0xd5),
    WriteData(0x28),
    WriteData(0x8c),
    WriteData(0x10),
    WriteData(0xd7),
    WriteData(0x28),
    WriteData(0x8c),
    WriteData(0x0a),
    WriteData(0xd1),
    WriteData(0x28),
    WriteData(0x8c),
    WriteData(0x0c),
    WriteData(0xd3),
    WriteData(0x28),
    WriteData(0x8c),
    // TODO: Unknown command
    WriteComm(0xeb),
    WriteData(0x00),
    WriteData(0x01),
    WriteData(0xe4),
    WriteData(0xe4),
    WriteData(0x44),
    WriteData(0x00),
    // TODO: Unknown command
    WriteComm(0xed),
    WriteData(0xf3),
    WriteData(0xc1),
    WriteData(0xba),
    WriteData(0x0f),
    WriteData(0x66),
    WriteData(0x77),
    WriteData(0x44),
    WriteData(0x55),
    WriteData(0x55),
    WriteData(0x44),
    WriteData(0x77),
    WriteData(0x66),
    WriteData(0xf0),
    WriteData(0xab),
    WriteData(0x1c),
    WriteData(0x3f),
    // TODO: Unknown command
    WriteComm(0xef),
    WriteData(0x10),
    WriteData(0x0d),
    WriteData(0x04),
    WriteData(0x08),
    WriteData(0x3f),
    WriteData(0x1f),
    //
    WriteComm(0xff),
    WriteData(0x77),
    WriteData(0x01),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x13),
    // TODO: Unknown command
    WriteComm(0xe8),
    WriteData(0x00),
    WriteData(0x0e),
    //
    WriteComm(0x11),
    SleepMs(120),
    // TODO: Unknown command
    WriteComm(0xe8),
    WriteData(0x00),
    WriteData(0x0c),
    SleepMs(10),
    // TODO: Unknown command
    WriteComm(0xe8),
    WriteData(0x40),
    WriteData(0x00),
    //
    WriteComm(0xff),
    WriteData(0x77),
    WriteData(0x01),
    WriteData(0x00),
    WriteData(0x00),
    WriteData(0x00),
    //
    WriteComm(0x36),
    WriteData(0x00),
    //
    WriteComm(0x3A),
    WriteData(0x66),
    //
    WriteComm(0x29),
    SleepMs(20),
    /*
    WriteComm (0xff);
    WriteData (0x77);
    WriteData (0x01);
    WriteData (0x00);
    WriteData (0x00);
    WriteData (0x10);
    WriteComm (0xe5);
    WriteData (0x00);
    WriteData (0x00);
    */
];
