//! DMA Mem2Mem Tests

//% CHIPS: esp32s3 esp32c2 esp32c3 esp32c6 esp32h2

#![no_std]
#![no_main]

use defmt_rtt as _;
use esp_backtrace as _;
use esp_hal::{
    clock::ClockControl,
    dma::{Dma, DmaError, DmaPriority, Mem2Mem},
    dma_buffers,
    dma_buffers_chunk_size,
    dma_descriptors,
    peripherals::Peripherals,
    system::SystemControl,
};

const DATA_SIZE: usize = 1024 * 10;

#[cfg(test)]
#[embedded_test::tests]
mod tests {
    use defmt::assert_eq;

    use super::*;

    #[init]
    fn init() {}

    #[test]
    fn test_internal_mem2mem() {
        let peripherals = Peripherals::take();
        let system = SystemControl::new(peripherals.SYSTEM);
        let _clocks = ClockControl::boot_defaults(system.clock_control).freeze();

        let (tx_buffer, tx_descriptors, mut rx_buffer, rx_descriptors) = dma_buffers!(DATA_SIZE);

        let dma = Dma::new(peripherals.DMA);
        let channel = dma.channel0.configure(false, DmaPriority::Priority0);
        #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
        let dma_peripheral = peripherals.SPI2;
        #[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
        let dma_peripheral = peripherals.MEM2MEM1;

        let mut mem2mem =
            Mem2Mem::new(channel, dma_peripheral, tx_descriptors, rx_descriptors).unwrap();

        for i in 0..core::mem::size_of_val(tx_buffer) {
            tx_buffer[i] = (i % 256) as u8;
        }
        let dma_wait = mem2mem.start_transfer(&tx_buffer, &mut rx_buffer).unwrap();
        dma_wait.wait().unwrap();
        for i in 0..core::mem::size_of_val(tx_buffer) {
            assert_eq!(rx_buffer[i], tx_buffer[i]);
        }
    }

    #[test]
    fn test_internal_mem2mem_chunk_size() {
        const CHUNK_SIZE: usize = 2048;
        let peripherals = Peripherals::take();
        let system = SystemControl::new(peripherals.SYSTEM);
        let _clocks = ClockControl::boot_defaults(system.clock_control).freeze();

        let (tx_buffer, tx_descriptors, mut rx_buffer, rx_descriptors) =
            dma_buffers_chunk_size!(DATA_SIZE, CHUNK_SIZE);

        let dma = Dma::new(peripherals.DMA);
        let channel = dma.channel0.configure(false, DmaPriority::Priority0);
        #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
        let dma_peripheral = peripherals.SPI2;
        #[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
        let dma_peripheral = peripherals.MEM2MEM1;

        let mut mem2mem = Mem2Mem::new_with_chunk_size(
            channel,
            dma_peripheral,
            tx_descriptors,
            rx_descriptors,
            CHUNK_SIZE,
        )
        .unwrap();

        for i in 0..core::mem::size_of_val(tx_buffer) {
            tx_buffer[i] = (i % 256) as u8;
        }
        let dma_wait = mem2mem.start_transfer(&tx_buffer, &mut rx_buffer).unwrap();
        dma_wait.wait().unwrap();
        for i in 0..core::mem::size_of_val(tx_buffer) {
            assert_eq!(rx_buffer[i], tx_buffer[i]);
        }
    }

    #[test]
    fn test_mem2mem_errors_zero_tx() {
        use esp_hal::dma::CHUNK_SIZE;

        let peripherals = Peripherals::take();
        let system = SystemControl::new(peripherals.SYSTEM);
        let _clocks = ClockControl::boot_defaults(system.clock_control).freeze();

        let dma = Dma::new(peripherals.DMA);
        let channel = dma.channel0.configure(false, DmaPriority::Priority0);
        #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
        let dma_peripheral = peripherals.SPI2;
        #[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
        let dma_peripheral = peripherals.MEM2MEM1;

        let (tx_descriptors, rx_descriptors) = dma_descriptors!(0, 1024);
        match Mem2Mem::new_with_chunk_size(
            channel,
            dma_peripheral,
            tx_descriptors,
            rx_descriptors,
            CHUNK_SIZE,
        ) {
            Err(DmaError::OutOfDescriptors) => (),
            _ => panic!("Expected OutOfDescriptors"),
        }
    }

    #[test]
    fn test_mem2mem_errors_zero_rx() {
        use esp_hal::dma::CHUNK_SIZE;

        let peripherals = Peripherals::take();
        let system = SystemControl::new(peripherals.SYSTEM);
        let _clocks = ClockControl::boot_defaults(system.clock_control).freeze();

        let dma = Dma::new(peripherals.DMA);
        let channel = dma.channel0.configure(false, DmaPriority::Priority0);
        #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
        let dma_peripheral = peripherals.SPI2;
        #[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
        let dma_peripheral = peripherals.MEM2MEM1;

        let (tx_descriptors, rx_descriptors) = dma_descriptors!(1024, 0);
        match Mem2Mem::new_with_chunk_size(
            channel,
            dma_peripheral,
            tx_descriptors,
            rx_descriptors,
            CHUNK_SIZE,
        ) {
            Err(DmaError::OutOfDescriptors) => (),
            _ => panic!("Expected OutOfDescriptors"),
        }
    }

    #[test]
    fn test_mem2mem_errors_chunk_size_too_small() {
        let peripherals = Peripherals::take();
        let system = SystemControl::new(peripherals.SYSTEM);
        let _clocks = ClockControl::boot_defaults(system.clock_control).freeze();

        let dma = Dma::new(peripherals.DMA);
        let channel = dma.channel0.configure(false, DmaPriority::Priority0);
        #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
        let dma_peripheral = peripherals.SPI2;
        #[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
        let dma_peripheral = peripherals.MEM2MEM1;

        let (tx_descriptors, rx_descriptors) = dma_descriptors!(1024, 1024);
        match Mem2Mem::new_with_chunk_size(
            channel,
            dma_peripheral,
            tx_descriptors,
            rx_descriptors,
            0,
        ) {
            Err(DmaError::InvalidChunkSize) => (),
            _ => panic!("Expected InvalidChunkSize"),
        }
    }

    #[test]
    fn test_mem2mem_errors_chunk_size_too_big() {
        let peripherals = Peripherals::take();
        let system = SystemControl::new(peripherals.SYSTEM);
        let _clocks = ClockControl::boot_defaults(system.clock_control).freeze();

        let dma = Dma::new(peripherals.DMA);
        let channel = dma.channel0.configure(false, DmaPriority::Priority0);
        #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))]
        let dma_peripheral = peripherals.SPI2;
        #[cfg(not(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3")))]
        let dma_peripheral = peripherals.MEM2MEM1;

        let (tx_descriptors, rx_descriptors) = dma_descriptors!(1024, 1024);
        match Mem2Mem::new_with_chunk_size(
            channel,
            dma_peripheral,
            tx_descriptors,
            rx_descriptors,
            4093,
        ) {
            Err(DmaError::InvalidChunkSize) => (),
            _ => panic!("Expected InvalidChunkSize"),
        }
    }
}
