use portable_atomic::{AtomicBool, Ordering};

use crate::{
    asynch::AtomicWaker,
    dma::*,
    interrupt::Priority,
    peripheral::Peripheral,
    peripherals::Interrupt,
};

pub(super) type CryptoRegisterBlock = crate::peripherals::crypto_dma::RegisterBlock;

/// The RX half of a Crypto DMA channel.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CryptoDmaRxChannel(pub(crate) CryptoDmaChannel);

impl crate::private::Sealed for CryptoDmaRxChannel {}
impl DmaRxChannel for CryptoDmaRxChannel {}
impl Peripheral for CryptoDmaRxChannel {
    type P = Self;

    unsafe fn clone_unchecked(&self) -> Self::P {
        Self(self.0.clone_unchecked())
    }
}

/// The TX half of a Crypto DMA channel.
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CryptoDmaTxChannel(pub(crate) CryptoDmaChannel);

impl crate::private::Sealed for CryptoDmaTxChannel {}
impl DmaTxChannel for CryptoDmaTxChannel {}
impl Peripheral for CryptoDmaTxChannel {
    type P = Self;

    unsafe fn clone_unchecked(&self) -> Self::P {
        Self(self.0.clone_unchecked())
    }
}

impl RegisterAccess for CryptoDmaTxChannel {
    fn reset(&self) {
        let register_block = self.0.register_block();
        register_block.conf().modify(|_, w| {
            w.out_rst().set_bit();
            w.ahbm_rst().set_bit();
            w.ahbm_fifo_rst().set_bit()
        });
        register_block.conf().modify(|_, w| {
            w.out_rst().clear_bit();
            w.ahbm_rst().clear_bit();
            w.ahbm_fifo_rst().clear_bit()
        });
    }

    fn set_burst_mode(&self, burst_mode: BurstConfig) {
        let register_block = self.0.register_block();
        register_block
            .conf()
            .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled()));
    }

    fn set_descr_burst_mode(&self, burst_mode: bool) {
        let register_block = self.0.register_block();
        register_block
            .conf()
            .modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
    }

    fn set_peripheral(&self, peripheral: u8) {
        use esp32s2::crypto_dma::aes_sha_select::SELECT;
        let peripheral = match peripheral {
            p if p == DmaPeripheral::Aes as u8 => SELECT::Aes,
            p if p == DmaPeripheral::Sha as u8 => SELECT::Sha,
            _ => unreachable!(),
        };
        let register_block = self.0.register_block();
        register_block
            .aes_sha_select()
            .modify(|_, w| w.select().variant(peripheral));
    }

    fn set_link_addr(&self, address: u32) {
        let register_block = self.0.register_block();
        register_block
            .out_link()
            .modify(|_, w| unsafe { w.outlink_addr().bits(address) });
    }

    fn start(&self) {
        let register_block = self.0.register_block();
        register_block
            .out_link()
            .modify(|_, w| w.outlink_start().set_bit());
    }

    fn stop(&self) {
        let register_block = self.0.register_block();
        register_block
            .out_link()
            .modify(|_, w| w.outlink_stop().set_bit());
    }

    fn restart(&self) {
        let register_block = self.0.register_block();
        register_block
            .out_link()
            .modify(|_, w| w.outlink_restart().set_bit());
    }

    fn set_check_owner(&self, check_owner: Option<bool>) {
        if check_owner == Some(true) {
            panic!("Crypto DMA does not support checking descriptor ownership");
        }
    }

    fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
        self.0.is_compatible_with(peripheral)
    }

    #[cfg(psram_dma)]
    fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
        let register_block = self.0.register_block();
        register_block
            .conf1()
            .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
    }

    #[cfg(psram_dma)]
    fn can_access_psram(&self) -> bool {
        true
    }
}

impl TxRegisterAccess for CryptoDmaTxChannel {
    fn set_auto_write_back(&self, enable: bool) {
        // there is no `auto_wrback` for SPI
        assert!(!enable);
    }

    fn last_dscr_address(&self) -> usize {
        let register_block = self.0.register_block();
        register_block
            .out_eof_des_addr()
            .read()
            .out_eof_des_addr()
            .bits() as usize
    }

    fn peripheral_interrupt(&self) -> Option<Interrupt> {
        None
    }

    fn async_handler(&self) -> Option<InterruptHandler> {
        None
    }
}

impl InterruptAccess<DmaTxInterrupt> for CryptoDmaTxChannel {
    fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
        let reg_block = self.0.register_block();
        reg_block.int_ena().modify(|_, w| {
            for interrupt in interrupts {
                match interrupt {
                    DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
                    DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable),
                    DmaTxInterrupt::Eof => w.out_eof().bit(enable),
                    DmaTxInterrupt::Done => w.out_done().bit(enable),
                };
            }
            w
        });
    }

    fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
        let mut result = EnumSet::new();

        let register_block = self.0.register_block();
        let int_ena = register_block.int_ena().read();
        if int_ena.out_total_eof().bit_is_set() {
            result |= DmaTxInterrupt::TotalEof;
        }
        if int_ena.out_dscr_err().bit_is_set() {
            result |= DmaTxInterrupt::DescriptorError;
        }
        if int_ena.out_eof().bit_is_set() {
            result |= DmaTxInterrupt::Eof;
        }
        if int_ena.out_done().bit_is_set() {
            result |= DmaTxInterrupt::Done;
        }

        result
    }

    fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
        let register_block = self.0.register_block();
        register_block.int_clr().write(|w| {
            for interrupt in interrupts.into() {
                match interrupt {
                    DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
                    DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(),
                    DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
                    DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
                };
            }
            w
        });
    }

    fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
        let mut result = EnumSet::new();

        let register_block = self.0.register_block();
        let int_raw = register_block.int_raw().read();
        if int_raw.out_total_eof().bit_is_set() {
            result |= DmaTxInterrupt::TotalEof;
        }
        if int_raw.out_dscr_err().bit_is_set() {
            result |= DmaTxInterrupt::DescriptorError;
        }
        if int_raw.out_eof().bit_is_set() {
            result |= DmaTxInterrupt::Eof;
        }
        if int_raw.out_done().bit_is_set() {
            result |= DmaTxInterrupt::Done;
        }

        result
    }

    fn waker(&self) -> &'static AtomicWaker {
        self.0.tx_waker()
    }

    fn is_async(&self) -> bool {
        self.0.tx_async_flag().load(Ordering::Acquire)
    }

    fn set_async(&self, is_async: bool) {
        self.0.tx_async_flag().store(is_async, Ordering::Release);
    }
}

impl RegisterAccess for CryptoDmaRxChannel {
    fn reset(&self) {
        let register_block = self.0.register_block();
        register_block.conf().modify(|_, w| {
            w.in_rst().set_bit();
            w.ahbm_rst().set_bit();
            w.ahbm_fifo_rst().set_bit()
        });
        register_block.conf().modify(|_, w| {
            w.in_rst().clear_bit();
            w.ahbm_rst().clear_bit();
            w.ahbm_fifo_rst().clear_bit()
        });
    }

    fn set_burst_mode(&self, _burst_mode: BurstConfig) {}

    fn set_descr_burst_mode(&self, burst_mode: bool) {
        let register_block = self.0.register_block();
        register_block
            .conf()
            .modify(|_, w| w.indscr_burst_en().bit(burst_mode));
    }

    fn set_peripheral(&self, peripheral: u8) {
        use esp32s2::crypto_dma::aes_sha_select::SELECT;
        let peripheral = match peripheral {
            p if p == DmaPeripheral::Aes as u8 => SELECT::Aes,
            p if p == DmaPeripheral::Sha as u8 => SELECT::Sha,
            _ => unreachable!(),
        };
        let register_block = self.0.register_block();
        register_block
            .aes_sha_select()
            .modify(|_, w| w.select().variant(peripheral));
    }

    fn set_link_addr(&self, address: u32) {
        let register_block = self.0.register_block();
        register_block
            .in_link()
            .modify(|_, w| unsafe { w.inlink_addr().bits(address) });
    }

    fn start(&self) {
        let register_block = self.0.register_block();
        register_block
            .in_link()
            .modify(|_, w| w.inlink_start().set_bit());
    }

    fn stop(&self) {
        let register_block = self.0.register_block();
        register_block
            .in_link()
            .modify(|_, w| w.inlink_stop().set_bit());
    }

    fn restart(&self) {
        let register_block = self.0.register_block();
        register_block
            .in_link()
            .modify(|_, w| w.inlink_restart().set_bit());
    }

    fn set_check_owner(&self, check_owner: Option<bool>) {
        if check_owner == Some(true) {
            panic!("Crypto DMA does not support checking descriptor ownership");
        }
    }

    fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
        self.0.is_compatible_with(peripheral)
    }

    #[cfg(psram_dma)]
    fn set_ext_mem_block_size(&self, size: DmaExtMemBKSize) {
        let register_block = self.0.register_block();
        register_block
            .conf1()
            .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
    }

    #[cfg(psram_dma)]
    fn can_access_psram(&self) -> bool {
        true
    }
}

impl RxRegisterAccess for CryptoDmaRxChannel {
    fn peripheral_interrupt(&self) -> Option<Interrupt> {
        // We don't know if the channel is used by AES or SHA, so interrupt handler
        // setup is the responsibility of the peripheral driver.
        None
    }

    fn async_handler(&self) -> Option<InterruptHandler> {
        None
    }
}

impl InterruptAccess<DmaRxInterrupt> for CryptoDmaRxChannel {
    fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
        let reg_block = self.0.register_block();
        reg_block.int_ena().modify(|_, w| {
            for interrupt in interrupts {
                match interrupt {
                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
                    DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable),
                    DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable),
                    DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable),
                    DmaRxInterrupt::Done => w.in_done().bit(enable),
                };
            }
            w
        });
    }

    fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
        let mut result = EnumSet::new();

        let register_block = self.0.register_block();
        let int_ena = register_block.int_ena().read();
        if int_ena.in_dscr_err().bit_is_set() {
            result |= DmaRxInterrupt::DescriptorError;
        }
        if int_ena.in_dscr_err().bit_is_set() {
            result |= DmaRxInterrupt::DescriptorEmpty;
        }
        if int_ena.in_suc_eof().bit_is_set() {
            result |= DmaRxInterrupt::SuccessfulEof;
        }
        if int_ena.in_err_eof().bit_is_set() {
            result |= DmaRxInterrupt::ErrorEof;
        }
        if int_ena.in_done().bit_is_set() {
            result |= DmaRxInterrupt::Done;
        }

        result
    }

    fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
        let register_block = self.0.register_block();
        register_block.int_clr().write(|w| {
            for interrupt in interrupts.into() {
                match interrupt {
                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
                    DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(),
                    DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(),
                    DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(),
                    DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
                };
            }
            w
        });
    }

    fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
        let mut result = EnumSet::new();

        let register_block = self.0.register_block();
        let int_raw = register_block.int_raw().read();
        if int_raw.in_dscr_err().bit_is_set() {
            result |= DmaRxInterrupt::DescriptorError;
        }
        if int_raw.in_dscr_empty().bit_is_set() {
            result |= DmaRxInterrupt::DescriptorEmpty;
        }
        if int_raw.in_suc_eof().bit_is_set() {
            result |= DmaRxInterrupt::SuccessfulEof;
        }
        if int_raw.in_err_eof().bit_is_set() {
            result |= DmaRxInterrupt::ErrorEof;
        }
        if int_raw.in_done().bit_is_set() {
            result |= DmaRxInterrupt::Done;
        }

        result
    }

    fn waker(&self) -> &'static AtomicWaker {
        self.0.rx_waker()
    }

    fn is_async(&self) -> bool {
        self.0.rx_async_flag().load(Ordering::Relaxed)
    }

    fn set_async(&self, _is_async: bool) {
        self.0.rx_async_flag().store(_is_async, Ordering::Relaxed);
    }
}

#[doc = "DMA channel suitable for CRYPTO"]
#[non_exhaustive]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CryptoDmaChannel {}

impl crate::private::Sealed for CryptoDmaChannel {}

impl Peripheral for CryptoDmaChannel {
    type P = Self;
    unsafe fn clone_unchecked(&self) -> Self::P {
        Self::steal()
    }
}
impl CryptoDmaChannel {
    #[doc = r" Unsafely constructs a new DMA channel."]
    #[doc = r""]
    #[doc = r" # Safety"]
    #[doc = r""]
    #[doc = r" The caller must ensure that only a single instance is used."]
    pub unsafe fn steal() -> Self {
        Self {}
    }
}
impl DmaChannel for CryptoDmaChannel {
    type Rx = CryptoDmaRxChannel;
    type Tx = CryptoDmaTxChannel;
    unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
        (CryptoDmaRxChannel(Self {}), CryptoDmaTxChannel(Self {}))
    }
}
impl DmaChannelExt for CryptoDmaChannel {
    fn rx_interrupts() -> impl InterruptAccess<DmaRxInterrupt> {
        CryptoDmaRxChannel(Self {})
    }
    fn tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt> {
        CryptoDmaTxChannel(Self {})
    }
}
impl PdmaChannel for CryptoDmaChannel {
    type RegisterBlock = CryptoRegisterBlock;
    fn register_block(&self) -> &Self::RegisterBlock {
        unsafe { &*crate::peripherals::CRYPTO_DMA::PTR }
    }
    fn tx_waker(&self) -> &'static AtomicWaker {
        static WAKER: AtomicWaker = AtomicWaker::new();
        &WAKER
    }
    fn rx_waker(&self) -> &'static AtomicWaker {
        static WAKER: AtomicWaker = AtomicWaker::new();
        &WAKER
    }
    fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
        let compatible_peripherals = [DmaPeripheral::Aes, DmaPeripheral::Sha];
        compatible_peripherals.contains(&peripheral)
    }
    fn peripheral_interrupt(&self) -> Interrupt {
        unreachable!("Crypto DMA has separate interrupts specific to AES and SHA")
    }
    fn async_handler(&self) -> InterruptHandler {
        pub(crate) extern "C" fn __esp_hal_internal_interrupt_handler() {
            super::asynch::handle_in_interrupt::<CryptoDmaChannel>();
            super::asynch::handle_out_interrupt::<CryptoDmaChannel>();
        }
        #[allow(non_upper_case_globals)]
        pub(crate) static interrupt_handler: crate::interrupt::InterruptHandler =
            crate::interrupt::InterruptHandler::new(
                __esp_hal_internal_interrupt_handler,
                Priority::max(),
            );
        interrupt_handler
    }
    fn rx_async_flag(&self) -> &'static AtomicBool {
        static FLAG: AtomicBool = AtomicBool::new(false);
        &FLAG
    }
    fn tx_async_flag(&self) -> &'static AtomicBool {
        static FLAG: AtomicBool = AtomicBool::new(false);
        &FLAG
    }
}
impl DmaChannelConvert<CryptoDmaRxChannel> for CryptoDmaChannel {
    fn degrade(self) -> CryptoDmaRxChannel {
        CryptoDmaRxChannel(self)
    }
}
impl DmaChannelConvert<CryptoDmaTxChannel> for CryptoDmaChannel {
    fn degrade(self) -> CryptoDmaTxChannel {
        CryptoDmaTxChannel(self)
    }
}
