//! LCD_CAM Camera and DPI tests

//% CHIPS: esp32s3

#![no_std]
#![no_main]

use esp_hal::{
    dma::{DmaChannel, DmaRxBuf, DmaTxBuf},
    dma_buffers,
    gpio::Level,
    lcd_cam::{
        cam::{self, Camera, RxEightBits},
        lcd::{
            dpi,
            dpi::{Dpi, Format, FrameTiming},
            ClockMode,
            Phase,
            Polarity,
        },
        LcdCam,
    },
    peripherals::Peripherals,
};
use fugit::RateExtU32;
use hil_test as _;

struct Context {
    peripherals: Peripherals,
    dma_tx_buf: DmaTxBuf,
    dma_rx_buf: DmaRxBuf,
}

#[cfg(test)]
#[embedded_test::tests]
mod tests {
    use super::*;

    #[init]
    fn init() -> Context {
        let peripherals = esp_hal::init(esp_hal::Config::default());

        let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(50 * 50);
        let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
        let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();

        Context {
            peripherals,
            dma_tx_buf,
            dma_rx_buf,
        }
    }

    #[test]
    fn test_camera_can_receive_from_rgb(ctx: Context) {
        let peripherals = ctx.peripherals;

        let lcd_cam = LcdCam::new(peripherals.LCD_CAM);

        let (rx_channel, tx_channel) = peripherals.DMA_CH2.split();

        let (vsync_in, vsync_out) = peripherals.GPIO6.split();
        let (hsync_in, hsync_out) = peripherals.GPIO7.split();
        let (de_in, de_out) = peripherals.GPIO14.split();
        let (pclk_in, pclk_out) = peripherals.GPIO13.split();
        let (d0_in, d0_out) = peripherals.GPIO11.split();
        let (d1_in, d1_out) = peripherals.GPIO9.split();
        let (d2_in, d2_out) = peripherals.GPIO8.split();
        let (d3_in, d3_out) = peripherals.GPIO47.split();
        let (d4_in, d4_out) = peripherals.GPIO12.split();
        let (d5_in, d5_out) = peripherals.GPIO18.split();
        let (d6_in, d6_out) = peripherals.GPIO17.split();
        let (d7_in, d7_out) = peripherals.GPIO16.split();

        let mut config = dpi::Config::default();
        config.clock_mode = ClockMode {
            polarity: Polarity::IdleHigh,
            phase: Phase::ShiftLow,
        };
        config.frequency = 500u32.kHz();
        config.format = Format {
            enable_2byte_mode: false,
            ..Default::default()
        };
        // Send a 50x50 video
        config.timing = FrameTiming {
            horizontal_total_width: 65,
            hsync_width: 5,
            horizontal_blank_front_porch: 10,
            horizontal_active_width: 50,

            vertical_total_height: 65,
            vsync_width: 5,
            vertical_blank_front_porch: 10,
            vertical_active_height: 50,

            hsync_position: 0,
        };
        config.vsync_idle_level = Level::High;
        config.hsync_idle_level = Level::High;
        config.de_idle_level = Level::Low;
        config.disable_black_region = false;

        let dpi = Dpi::new(lcd_cam.lcd, tx_channel, config)
            .unwrap()
            .with_vsync(vsync_out)
            .with_hsync(hsync_out)
            .with_de(de_out)
            .with_pclk(pclk_out)
            .with_data0(d0_out)
            .with_data1(d1_out)
            .with_data2(d2_out)
            .with_data3(d3_out)
            .with_data4(d4_out)
            .with_data5(d5_out)
            .with_data6(d6_out)
            .with_data7(d7_out);

        let mut cam_config = cam::Config::default();
        cam_config.frequency = 1u32.MHz();

        let camera = Camera::new(
            lcd_cam.cam,
            rx_channel,
            RxEightBits::new(d0_in, d1_in, d2_in, d3_in, d4_in, d5_in, d6_in, d7_in),
            cam_config,
        )
        .unwrap()
        .with_ctrl_pins_and_de(vsync_in, hsync_in, de_in)
        .with_pixel_clock(pclk_in);

        let mut dma_tx_buf = ctx.dma_tx_buf;
        let mut dma_rx_buf = ctx.dma_rx_buf;

        for (i, b) in dma_tx_buf.as_mut_slice().iter_mut().enumerate() {
            *b = ((i + 0) % 256) as u8;
        }

        let camera_transfer = camera.receive(dma_rx_buf).map_err(|e| e.0).unwrap();
        // Note: next_frame_en is not false because the RGB driver doesn't send a VSYNC
        // at the end of the frame, which means the DMA doesn't flush the last
        // few bytes it receives.
        let dpi_transfer = dpi.send(true, dma_tx_buf).map_err(|e| e.0).unwrap();

        (_, _, dma_rx_buf) = camera_transfer.wait();
        (_, dma_tx_buf) = dpi_transfer.stop();

        assert_eq!(dma_tx_buf.as_slice(), dma_rx_buf.as_slice());
    }
}
