//! DSI HOST

use core::marker::PhantomData;

use embassy_hal_internal::{into_ref, PeripheralRef};

//use crate::gpio::{AnyPin, SealedPin};
use crate::gpio::{AfType, AnyPin, OutputType, Speed};
use crate::rcc::{self, RccPeripheral};
use crate::{peripherals, Peripheral};

/// Performs a busy-wait delay for a specified number of microseconds.
pub fn blocking_delay_ms(ms: u32) {
    #[cfg(feature = "time")]
    embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64));
    #[cfg(not(feature = "time"))]
    cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms);
}

/// PacketTypes extracted from CubeMX
#[repr(u8)]
#[allow(dead_code)]
pub enum PacketType {
    /// DCS short write, no parameters
    DcsShortPktWriteP0,
    /// DCS short write, one parameter
    DcsShortPktWriteP1,
    /// Generic short write, no parameters
    GenShortPktWriteP0,
    /// Generic short write, one parameter
    GenShortPktWriteP1,
    /// Generic short write, two parameters
    GenShortPktWriteP2,
    /// DCS long write
    DcsLongPktWrite,
    /// Generic long write
    GenLongPktWrite,
    /// DCS short read
    DcsShortPktRead(u8),
    /// Generic short read, no parameters
    GenShortPktReadP0,
    /// Generic short read, one parameter
    GenShortPktReadP1(u8),
    /// Generic short read, two parameters
    GenShortPktReadP2(u8, u8),
    /// Used to set the maximum return packet size for reading data
    MaxReturnPktSize,
}

impl From<PacketType> for u8 {
    fn from(packet_type: PacketType) -> u8 {
        match packet_type {
            PacketType::DcsShortPktWriteP0 => 0x05,
            PacketType::DcsShortPktWriteP1 => 0x15,
            PacketType::GenShortPktWriteP0 => 0x03,
            PacketType::GenShortPktWriteP1 => 0x13,
            PacketType::GenShortPktWriteP2 => 0x23,
            PacketType::DcsLongPktWrite => 0x39,
            PacketType::GenLongPktWrite => 0x29,
            PacketType::DcsShortPktRead(_) => 0x06,
            PacketType::GenShortPktReadP0 => 0x04,
            PacketType::GenShortPktReadP1(_) => 0x14,
            PacketType::GenShortPktReadP2(_, _) => 0x24,
            PacketType::MaxReturnPktSize => 0x37,
        }
    }
}

/// DSIHOST driver.
pub struct DsiHost<'d, T: Instance> {
    _peri: PhantomData<&'d mut T>,
    _te: PeripheralRef<'d, AnyPin>,
}

impl<'d, T: Instance> DsiHost<'d, T> {
    /// Note: Full-Duplex modes are not supported at this time
    pub fn new(_peri: impl Peripheral<P = T> + 'd, te: impl Peripheral<P = impl TePin<T>> + 'd) -> Self {
        into_ref!(te);

        rcc::enable_and_reset::<T>();

        // Set Tearing Enable pin according to CubeMx example
        te.set_as_af(te.af_num(), AfType::output(OutputType::PushPull, Speed::Low));
        /*
                T::regs().wcr().modify(|w| {
                    w.set_dsien(true);
                });
        */
        Self {
            _peri: PhantomData,
            _te: te.map_into(),
        }
    }

    /// Get the DSIHOST hardware version. Found in the reference manual for comparison.
    pub fn get_version(&self) -> u32 {
        T::regs().vr().read().version()
    }

    /// Set the enable bit in the control register and assert that it has been enabled
    pub fn enable(&mut self) {
        T::regs().cr().modify(|w| w.set_en(true));
        assert!(T::regs().cr().read().en())
    }

    /// Unset the enable bit in the control register and assert that it has been disabled
    pub fn disable(&mut self) {
        T::regs().cr().modify(|w| w.set_en(false));
        assert!(!T::regs().cr().read().en())
    }

    /// Set the DSI enable bit in the wrapper control register and assert that it has been enabled
    pub fn enable_wrapper_dsi(&mut self) {
        T::regs().wcr().modify(|w| w.set_dsien(true));
        assert!(T::regs().wcr().read().dsien())
    }

    /// Unset the DSI enable bit in the wrapper control register and assert that it has been disabled
    pub fn disable_wrapper_dsi(&mut self) {
        T::regs().wcr().modify(|w| w.set_dsien(false));
        assert!(!T::regs().wcr().read().dsien())
    }

    /// DCS or Generic short/long write command
    pub fn write_cmd(&mut self, channel_id: u8, address: u8, data: &[u8]) -> Result<(), Error> {
        assert!(data.len() > 0);

        if data.len() == 1 {
            self.short_write(channel_id, PacketType::DcsShortPktWriteP1, address, data[0])
        } else {
            self.long_write(
                channel_id,
                PacketType::DcsLongPktWrite, // FIXME: This might be a generic long packet, as well...
                address,
                data,
            )
        }
    }

    fn short_write(&mut self, channel_id: u8, packet_type: PacketType, param1: u8, param2: u8) -> Result<(), Error> {
        #[cfg(feature = "defmt")]
        defmt::debug!("short_write: BEGIN wait for command fifo empty");

        // Wait for Command FIFO empty
        self.wait_command_fifo_empty()?;
        #[cfg(feature = "defmt")]
        defmt::debug!("short_write: END wait for command fifo empty");

        // Configure the packet to send a short DCS command with 0 or 1 parameters
        // Update the DSI packet header with new information
        self.config_packet_header(channel_id, packet_type, param1, param2);

        self.wait_command_fifo_empty()?;

        let status = T::regs().isr1().read().0;
        if status != 0 {
            error!("ISR1 after short_write(): {:b}", status);
        }
        Ok(())
    }

    fn config_packet_header(&mut self, channel_id: u8, packet_type: PacketType, param1: u8, param2: u8) {
        T::regs().ghcr().write(|w| {
            w.set_dt(packet_type.into());
            w.set_vcid(channel_id);
            w.set_wclsb(param1);
            w.set_wcmsb(param2);
        });
    }

    /// Write long DCS or long Generic command.
    ///
    /// `params` is expected to contain at least 2 elements. Use [`short_write`] for a single element.
    fn long_write(&mut self, channel_id: u8, packet_type: PacketType, address: u8, data: &[u8]) -> Result<(), Error> {
        // Must be a long packet if we do the long write, obviously.
        assert!(matches!(
            packet_type,
            PacketType::DcsLongPktWrite | PacketType::GenLongPktWrite
        ));

        // params needs to have at least 2 elements, otherwise short_write should be used
        assert!(data.len() >= 2);

        #[cfg(feature = "defmt")]
        defmt::debug!("long_write: BEGIN wait for command fifo empty");

        self.wait_command_fifo_empty()?;

        #[cfg(feature = "defmt")]
        defmt::debug!("long_write: DONE wait for command fifo empty");

        // Note: CubeMX example "NbParams" is always one LESS than params.len()
        // DCS code (last element of params) must be on payload byte 1 and if we have only 2 more params,
        // then they must go into data2 and data3
        T::regs().gpdr().write(|w| {
            // data[2] may or may not exist.
            if let Some(x) = data.get(2) {
                w.set_data4(*x);
            }
            // data[0] and [1] have to exist if long_write is called.
            w.set_data3(data[1]);
            w.set_data2(data[0]);

            // DCS Code
            w.set_data1(address);
        });

        self.wait_command_fifo_empty()?;

        // These steps are only necessary if more than 1x 4 bytes need to go into the FIFO
        if data.len() >= 4 {
            // Generate an iterator that iterates over chunks of exactly 4 bytes
            let iter = data[3..data.len()].chunks_exact(4);
            // Obtain remainder before consuming iter
            let remainder = iter.remainder();

            // Keep filling the buffer with remaining data
            for param in iter {
                self.wait_command_fifo_not_full()?;
                T::regs().gpdr().write(|w| {
                    w.set_data4(param[3]);
                    w.set_data3(param[2]);
                    w.set_data2(param[1]);
                    w.set_data1(param[0]);
                });

                self.wait_command_fifo_empty().unwrap();
            }

            // If the remaining data was not devisible by 4 we get a remainder
            if remainder.len() >= 1 {
                self.wait_command_fifo_not_full()?;
                T::regs().gpdr().write(|w| {
                    if let Some(x) = remainder.get(2) {
                        w.set_data3(*x);
                    }
                    if let Some(x) = remainder.get(1) {
                        w.set_data2(*x);
                    }
                    w.set_data1(remainder[0]);
                });
                self.wait_command_fifo_empty().unwrap();
            }
        }
        // Configure the packet to send a long DCS command
        self.config_packet_header(
            channel_id,
            packet_type,
            ((data.len() + 1) & 0x00FF) as u8,        // +1 to account for address byte
            (((data.len() + 1) & 0xFF00) >> 8) as u8, // +1 to account for address byte
        );

        self.wait_command_fifo_empty()?;

        let status = T::regs().isr1().read().0;
        if status != 0 {
            error!("ISR1 after long_write(): {:b}", status);
        }
        Ok(())
    }

    /// Read DSI Register
    pub fn read(
        &mut self,
        channel_id: u8,
        packet_type: PacketType,
        read_size: u16,
        data: &mut [u8],
    ) -> Result<(), Error> {
        if data.len() != read_size as usize {
            return Err(Error::InvalidReadSize);
        }

        // Set the maximum return packet size
        self.short_write(
            channel_id,
            PacketType::MaxReturnPktSize,
            (read_size & 0xFF) as u8,
            ((read_size & 0xFF00) >> 8) as u8,
        )?;

        // Set the packet header according to the packet_type
        use PacketType::*;
        match packet_type {
            DcsShortPktRead(cmd) => self.config_packet_header(channel_id, packet_type, cmd, 0),
            GenShortPktReadP0 => self.config_packet_header(channel_id, packet_type, 0, 0),
            GenShortPktReadP1(param1) => self.config_packet_header(channel_id, packet_type, param1, 0),
            GenShortPktReadP2(param1, param2) => self.config_packet_header(channel_id, packet_type, param1, param2),
            _ => return Err(Error::InvalidPacketType),
        }

        self.wait_read_not_busy()?;

        // Obtain chunks of 32-bit so the entire FIFO data register can be read
        for bytes in data.chunks_exact_mut(4) {
            self.wait_payload_read_fifo_not_empty()?;

            // Only perform a single read on the entire register to avoid unintended side-effects
            let gpdr = T::regs().gpdr().read();
            bytes[0] = gpdr.data1();
            bytes[1] = gpdr.data2();
            bytes[2] = gpdr.data3();
            bytes[3] = gpdr.data4();
        }

        // Collect the remaining chunks and read the corresponding number of bytes from the FIFO
        let remainder = data.chunks_exact_mut(4).into_remainder();
        if !remainder.is_empty() {
            self.wait_payload_read_fifo_not_empty()?;
            // Only perform a single read on the entire register to avoid unintended side-effects
            let gpdr = T::regs().gpdr().read();
            if let Some(x) = remainder.get_mut(0) {
                *x = gpdr.data1()
            }
            if let Some(x) = remainder.get_mut(1) {
                *x = gpdr.data2()
            }
            if let Some(x) = remainder.get_mut(2) {
                *x = gpdr.data3()
            }
        }

        /*
        // Used this to check whether there are read errors. Does not seem like it.
        if !self.read_busy() {
            defmt::debug!("Read not busy!");
            if self.packet_size_error() {
                return Err(Error::ReadError);
            }
        }
        */
        Ok(())
    }

    fn wait_command_fifo_empty(&self) -> Result<(), Error> {
        for _ in 1..1000 {
            // Wait for Command FIFO empty
            if T::regs().gpsr().read().cmdfe() {
                return Ok(());
            }
            blocking_delay_ms(1);
        }
        Err(Error::FifoTimeout)
    }

    fn wait_command_fifo_not_full(&self) -> Result<(), Error> {
        for _ in 1..1000 {
            // Wait for Command FIFO not empty
            if !T::regs().gpsr().read().cmdff() {
                return Ok(());
            }
            blocking_delay_ms(1);
        }
        Err(Error::FifoTimeout)
    }

    fn wait_read_not_busy(&self) -> Result<(), Error> {
        for _ in 1..1000 {
            // Wait for read not busy
            if !self.read_busy() {
                return Ok(());
            }
            blocking_delay_ms(1);
        }
        Err(Error::ReadTimeout)
    }

    fn wait_payload_read_fifo_not_empty(&self) -> Result<(), Error> {
        for _ in 1..1000 {
            // Wait for payload read FIFO not empty
            if !T::regs().gpsr().read().prdfe() {
                return Ok(());
            }
            blocking_delay_ms(1);
        }
        Err(Error::FifoTimeout)
    }

    fn _packet_size_error(&self) -> bool {
        T::regs().isr1().read().pse()
    }

    fn read_busy(&self) -> bool {
        T::regs().gpsr().read().rcb()
    }
}

/// Possible Error Types for DSI HOST
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
    /// Waiting for FIFO empty flag timed out
    FifoTimeout,
    /// The specified `PacketType` is invalid for the selected operation
    InvalidPacketType,
    /// Provided read size does not match the read buffer length
    InvalidReadSize,
    /// Error during read
    ReadError,
    /// Read operation timed out
    ReadTimeout,
}

impl<'d, T: Instance> Drop for DsiHost<'d, T> {
    fn drop(&mut self) {}
}

trait SealedInstance: crate::rcc::SealedRccPeripheral {
    fn regs() -> crate::pac::dsihost::Dsihost;
}

/// DSI instance trait.
#[allow(private_bounds)]
pub trait Instance: SealedInstance + RccPeripheral + 'static {}

pin_trait!(TePin, Instance);

foreach_peripheral!(
    (dsihost, $inst:ident) => {
        impl crate::dsihost::SealedInstance for peripherals::$inst {
            fn regs() -> crate::pac::dsihost::Dsihost {
                crate::pac::$inst
            }
        }

        impl crate::dsihost::Instance for peripherals::$inst {}
    };
);
