#![macro_use]

//! I2S

//use core::future::poll_fn;
//use core::sync::atomic::{compiler_fence, Ordering};
//use core::task::Poll;

//use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef};

//use crate::gpio::sealed::Pin as _;
use crate::gpio::{AnyPin, Pin as GpioPin};
use crate::interrupt::Interrupt;
use crate::pac::i2s::CONFIG;
use crate::Peripheral;

// TODO: Define those in lib.rs somewhere else
//
// I2S EasyDMA MAXCNT bit length = 14
const MAX_DMA_MAXCNT: u32 = 1 << 14;

// Limits for Easy DMA - it can only read from data ram
pub const SRAM_LOWER: usize = 0x2000_0000;
pub const SRAM_UPPER: usize = 0x3000_0000;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
    BufferTooLong,
    BufferZeroLength,
    DMABufferNotInDataMemory,
    BufferMisaligned,
    // TODO: add other error variants.
}

#[derive(Clone)]
#[non_exhaustive]
pub struct Config {
    pub ratio: Ratio,
    pub swidth: SampleWidth,
    pub align: Align,
    pub format: Format,
    pub channels: Channels,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            ratio: Ratio::_32x,
            swidth: SampleWidth::_16bit,
            align: Align::Left,
            format: Format::I2S,
            channels: Channels::Stereo,
        }
    }
}

/// MCK / LRCK ratio.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Ratio {
    _32x,
    _48x,
    _64x,
    _96x,
    _128x,
    _192x,
    _256x,
    _384x,
    _512x,
}

impl From<Ratio> for u8 {
    fn from(variant: Ratio) -> Self {
        variant as _
    }
}

#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum SampleWidth {
    _8bit,
    _16bit,
    _24bit,
}

impl From<SampleWidth> for u8 {
    fn from(variant: SampleWidth) -> Self {
        variant as _
    }
}

/// Alignment of sample within a frame.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Align {
    Left,
    Right,
}

impl From<Align> for bool {
    fn from(variant: Align) -> Self {
        match variant {
            Align::Left => false,
            Align::Right => true,
        }
    }
}

/// Frame format.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Format {
    I2S,
    Aligned,
}

impl From<Format> for bool {
    fn from(variant: Format) -> Self {
        match variant {
            Format::I2S => false,
            Format::Aligned => true,
        }
    }
}

/// Enable channels.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Channels {
    Stereo,
    Left,
    Right,
}

impl From<Channels> for u8 {
    fn from(variant: Channels) -> Self {
        variant as _
    }
}

/// I2S Mode
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Mode {
    Controller,
    Peripheral,
}

// /// Master clock generator frequency.
// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
// pub enum MckFreq {
//     _32MDiv8 = 0x20000000,
//     _32MDiv10 = 0x18000000,
//     _32MDiv11 = 0x16000000,
//     _32MDiv15 = 0x11000000,
//     _32MDiv16 = 0x10000000,
//     _32MDiv21 = 0x0C000000,
//     _32MDiv23 = 0x0B000000,
//     _32MDiv30 = 0x08800000,
//     _32MDiv31 = 0x08400000,
//     _32MDiv32 = 0x08000000,
//     _32MDiv42 = 0x06000000,
//     _32MDiv63 = 0x04100000,
//     _32MDiv125 = 0x020C0000,
// }

/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct I2S<'d, T: Instance> {
    output: I2sOutput<'d, T>,
    input: I2sInput<'d, T>,
}

/// Transmitter interface to the UARTE peripheral obtained
/// via [Uarte]::split.
pub struct I2sOutput<'d, T: Instance> {
    _p: PeripheralRef<'d, T>,
}

/// Receiver interface to the UARTE peripheral obtained
/// via [Uarte]::split.
pub struct I2sInput<'d, T: Instance> {
    _p: PeripheralRef<'d, T>,
}

impl<'d, T: Instance> I2S<'d, T> {
    /// Create a new I2S
    pub fn new(
        i2s: impl Peripheral<P = T> + 'd,
        // irq: impl Peripheral<P = T::Interrupt> + 'd,
        mck: impl Peripheral<P = impl GpioPin> + 'd,
        sck: impl Peripheral<P = impl GpioPin> + 'd,
        lrck: impl Peripheral<P = impl GpioPin> + 'd,
        sdin: impl Peripheral<P = impl GpioPin> + 'd,
        sdout: impl Peripheral<P = impl GpioPin> + 'd,
        config: Config,
    ) -> Self {
        into_ref!(mck, sck, lrck, sdin, sdout);
        Self::new_inner(
            i2s,
            // irq,
            mck.map_into(),
            sck.map_into(),
            lrck.map_into(),
            sdin.map_into(),
            sdout.map_into(),
            config,
        )
    }

    fn new_inner(
        i2s: impl Peripheral<P = T> + 'd,
        // irq: impl Peripheral<P = T::Interrupt> + 'd,
        mck: PeripheralRef<'d, AnyPin>,
        sck: PeripheralRef<'d, AnyPin>,
        lrck: PeripheralRef<'d, AnyPin>,
        sdin: PeripheralRef<'d, AnyPin>,
        sdout: PeripheralRef<'d, AnyPin>,
        config: Config,
    ) -> Self {
        into_ref!(i2s, /* irq, */ mck, sck, lrck, sdin, sdout);

        let r = T::regs();

        Self::apply_config(&r.config, &config);

        r.psel.mck.write(|w| {
            unsafe { w.bits(mck.psel_bits()) };
            w.connect().connected()
        });

        r.psel.sck.write(|w| {
            unsafe { w.bits(sck.psel_bits()) };
            w.connect().connected()
        });

        r.psel.lrck.write(|w| {
            unsafe { w.bits(lrck.psel_bits()) };
            w.connect().connected()
        });

        r.psel.sdin.write(|w| {
            unsafe { w.bits(sdin.psel_bits()) };
            w.connect().connected()
        });

        r.psel.sdout.write(|w| {
            unsafe { w.bits(sdout.psel_bits()) };
            w.connect().connected()
        });

        r.enable.write(|w| w.enable().enabled());

        Self {
            output: I2sOutput {
                _p: unsafe { i2s.clone_unchecked() },
            },
            input: I2sInput { _p: i2s },
        }
    }

    /// Enables the I2S module.
    #[inline(always)]
    pub fn enable(&self) -> &Self {
        let r = T::regs();
        r.enable.write(|w| w.enable().enabled());
        self
    }

    /// Disables the I2S module.
    #[inline(always)]
    pub fn disable(&self) -> &Self {
        let r = T::regs();
        r.enable.write(|w| w.enable().disabled());
        self
    }

    /// Starts I2S transfer.
    #[inline(always)]
    pub fn start(&self) -> &Self {
        let r = T::regs();
        self.enable();
        r.tasks_start.write(|w| unsafe { w.bits(1) });
        self
    }

    /// Stops the I2S transfer and waits until it has stopped.
    #[inline(always)]
    pub async fn stop(&self) {
        todo!()
    }

    /// Enables/disables I2S transmission (TX).
    #[inline(always)]
    pub fn set_tx_enabled(&self, enabled: bool) -> &Self {
        let r = T::regs();
        r.config.txen.write(|w| w.txen().bit(enabled));
        self
    }

    /// Enables/disables I2S reception (RX).
    #[inline(always)]
    pub fn set_rx_enabled(&self, enabled: bool) -> &Self {
        let r = T::regs();
        r.config.rxen.write(|w| w.rxen().bit(enabled));
        self
    }

    /// Transmits the given `buffer`.
    /// Buffer address must be 4 byte aligned and located in RAM.
    pub async fn tx<B>(&mut self, buffer: B) -> Result<(), Error>
    where
        B: Buffer,
    {
        self.output.tx(buffer).await
    }

    /// Receives data into the given `buffer` until it's filled.
    /// Buffer address must be 4 byte aligned and located in RAM.
    pub async fn rx<B>(&mut self, buffer: B) -> Result<(), Error>
    where
        B: Buffer,
    {
        self.input.rx(buffer).await
    }

    fn apply_config(c: &CONFIG, config: &Config) {
        // TODO support slave too
        c.mcken.write(|w| w.mcken().enabled());
        c.mckfreq.write(|w| w.mckfreq()._32mdiv16());
        c.mode.write(|w| w.mode().master());

        c.ratio.write(|w| unsafe { w.ratio().bits(config.ratio.into()) });
        c.swidth.write(|w| unsafe { w.swidth().bits(config.swidth.into()) });
        c.align.write(|w| w.align().bit(config.align.into()));
        c.format.write(|w| w.format().bit(config.format.into()));
        c.channels
            .write(|w| unsafe { w.channels().bits(config.channels.into()) });
    }
}

impl<'d, T: Instance> I2sOutput<'d, T> {
    /// Transmits the given `buffer`.
    /// Buffer address must be 4 byte aligned and located in RAM.
    #[allow(unused_mut)]
    pub async fn tx<B>(&mut self, buffer: B) -> Result<(), Error>
    where
        B: Buffer,
    {
        let ptr = buffer.bytes_ptr();
        let len = buffer.bytes_len();

        if ptr as u32 % 4 != 0 {
            return Err(Error::BufferMisaligned);
        }
        if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER {
            return Err(Error::DMABufferNotInDataMemory);
        }
        let maxcnt = ((len + core::mem::size_of::<u32>() - 1) / core::mem::size_of::<u32>()) as u32;
        if maxcnt > MAX_DMA_MAXCNT {
            return Err(Error::BufferTooLong);
        }

        let r = T::regs();
        let _s = T::state();

        // TODO we can not progress until the last buffer written in TXD.PTR
        // has started the transmission.
        // We can use some sync primitive from `embassy-sync`.

        r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) });
        r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) });

        Ok(())
    }
}

impl<'d, T: Instance> I2sInput<'d, T> {
    /// Receives into the given `buffer`.
    /// Buffer address must be 4 byte aligned and located in RAM.
    #[allow(unused_mut)]
    pub async fn rx<B>(&mut self, buffer: B) -> Result<(), Error>
    where
        B: Buffer,
    {
        let ptr = buffer.bytes_ptr();
        let len = buffer.bytes_len();

        if ptr as u32 % 4 != 0 {
            return Err(Error::BufferMisaligned);
        }
        if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER {
            return Err(Error::DMABufferNotInDataMemory);
        }
        let maxcnt = ((len + core::mem::size_of::<u32>() - 1) / core::mem::size_of::<u32>()) as u32;
        if maxcnt > MAX_DMA_MAXCNT {
            return Err(Error::BufferTooLong);
        }

        let r = T::regs();
        let _s = T::state();

        // TODO we can not progress until the last buffer written in RXD.PTR
        // has started the transmission.
        // We can use some sync primitive from `embassy-sync`.

        r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) });
        r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) });

        Ok(())
    }
}

pub trait Buffer: Sized {
    fn bytes_ptr(&self) -> *const u8;
    fn bytes_len(&self) -> usize;
}

impl Buffer for &[u8] {
    #[inline]
    fn bytes_ptr(&self) -> *const u8 {
        self.as_ptr()
    }

    #[inline]
    fn bytes_len(&self) -> usize {
        self.len()
    }
}

impl Buffer for &[i16] {
    #[inline]
    fn bytes_ptr(&self) -> *const u8 {
        self.as_ptr() as *const u8
    }

    #[inline]
    fn bytes_len(&self) -> usize {
        self.len() * core::mem::size_of::<i16>()
    }
}

impl Buffer for &[i32] {
    #[inline]
    fn bytes_ptr(&self) -> *const u8 {
        self.as_ptr() as *const u8
    }

    #[inline]
    fn bytes_len(&self) -> usize {
        self.len() * core::mem::size_of::<i16>()
    }
}

pub(crate) mod sealed {
    use core::sync::atomic::AtomicU8;

    use embassy_sync::waitqueue::AtomicWaker;

    //use super::*;

    pub struct State {
        pub input_waker: AtomicWaker,
        pub output_waker: AtomicWaker,
        pub buffers_refcount: AtomicU8,
    }
    impl State {
        pub const fn new() -> Self {
            Self {
                input_waker: AtomicWaker::new(),
                output_waker: AtomicWaker::new(),
                buffers_refcount: AtomicU8::new(0),
            }
        }
    }

    pub trait Instance {
        fn regs() -> &'static crate::pac::i2s::RegisterBlock;
        fn state() -> &'static State;
    }
}

pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
    type Interrupt: Interrupt;
}

// TODO: Unsure why this macro is flagged as unused by CI when in fact it's used elsewhere?
#[allow(unused_macros)]
macro_rules! impl_i2s {
    ($type:ident, $pac_type:ident, $irq:ident) => {
        impl crate::i2s::sealed::Instance for peripherals::$type {
            fn regs() -> &'static pac::i2s::RegisterBlock {
                unsafe { &*pac::$pac_type::ptr() }
            }
            fn state() -> &'static crate::i2s::sealed::State {
                static STATE: crate::i2s::sealed::State = crate::i2s::sealed::State::new();
                &STATE
            }
        }
        impl crate::i2s::Instance for peripherals::$type {
            type Interrupt = crate::interrupt::$irq;
        }
    };
}