use alloc::{boxed::Box, vec, vec::Vec};
use core::iter::once;
use embassy_time::{Duration, Timer};
use esp_hal::gpio::{Flex, Level, Output};
use lazy_static::lazy_static;

use crate::peripherals::st7701s::Command;

async fn spi_delay() {
    Timer::after(Duration::from_micros(1)).await;
}

async fn spi_dummy_bit(sck: &mut Output<'_>) {
    sck.set_low();
    spi_delay().await;
    sck.set_high();
    spi_delay().await;
}

async 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().await;
    sck.set_high();
    spi_delay().await;
}

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

async 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).await;
    }
}

async 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,
    )
    .await;
}

pub async 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).await;

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

    cs.set_high();
}

#[expect(unused)]
pub async 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).await;

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

    if dummy_cycle {
        spi_dummy_bit(sck).await;
    }

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

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

    cs.set_high();
}

use crate::peripherals::st7701s::*;

// struct InitSequenceAction {
//     command: ArrayCommand<8>,
//     sleep: u64,
// }

// pub const INIT_SEQUENCE: [InitSequenceAction; _] = [CmdCn2bkxsel(
//     CmdCn2bkxselArg0::new(),
//     CmdCn2bkxselArg1::new(),
//     CmdCn2bkxselArg2::new(),
//     CmdCn2bkxselArg3::new(),
//     CmdCn2bkxselArg4::new().with_bksel(0x3).with_cn2(true),
// )];

lazy_static! {
    pub 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),
    ];
}
