//! I2S interface with DMA data transfer.

use cortex_m::interrupt;
use stm32f4xx_hal::dma;
use stm32f4xx_hal::dma::traits::Stream;
use stm32f4xx_hal::gpio::gpiob::{PB12, PB13, PB14, PB15};
use stm32f4xx_hal::gpio::gpioc::PC6;
use stm32f4xx_hal::gpio::{Alternate, AF5, AF6};
use stm32f4xx_hal::stm32::{DMA1, I2S2EXT, RCC, SPI2};

use crate::audio_buffer::{AudioBuffer, ReadPacket, WritePacket, CHANNELS, SAMPLES_PER_PACKET};

/// Clocks for the I2S peripheral.
#[derive(Clone, Copy)]
pub struct I2SClocks {
    plli2sclk: u32,
}

impl I2SClocks {
    /// Initializes the I2S clocks.
    ///
    /// This function must only be called once at startup. It assumes that the HSE is already
    /// initialized and the HSE frequency is 8MHz.
    pub unsafe fn init() -> I2SClocks {
        // The following values result in a sample frequency of 47991.07Hz:
        // - PLLI2SN = 43
        // - PLLI2SR = 4
        // - I2SDIV = 3
        // - I2SODD = 1

        let rcc = &*RCC::ptr();
        // Configure PLL.
        rcc.plli2scfgr
            .modify(|_, w| w.plli2sn().bits(43).plli2sq().bits(4).plli2sr().bits(4));
        // Enable PLL.
        rcc.cr.modify(|_, w| w.plli2son().set_bit());
        // Wait for PLL to stabilise
        while rcc.cr.read().plli2srdy().bit_is_clear() {}

        // Ensure that the PLL is selected as the I2S clock source.
        rcc.cfgr.modify(|_, w| w.i2ssrc().clear_bit());

        I2SClocks {
            plli2sclk: 86000000,
        }
    }
}

/// I2S interface with DMA data transfer.
pub struct I2S {
    spi: SPI2,
    i2sext: I2S2EXT,
    rx_dma: dma::Stream3<DMA1>,
    tx_dma: dma::Stream4<DMA1>,
    in_: &'static AudioBuffer,
    in_packets: [Option<WritePacket>; 2],
    out: &'static AudioBuffer,
    out_packets: [Option<ReadPacket>; 2],
}

impl I2S {
    /// Initializes the I2S interface to read from/write to the specified buffers.
    ///
    /// The interface is only started when the `start()` function is called.
    pub fn new(
        spi: SPI2,
        i2sext: I2S2EXT,
        mut rx_dma: dma::Stream3<DMA1>,
        mut tx_dma: dma::Stream4<DMA1>,
        mck: PC6<Alternate<AF5>>,
        ws: PB12<Alternate<AF5>>,
        ck: PB13<Alternate<AF5>>,
        sdin: PB14<Alternate<AF6>>,
        sdout: PB15<Alternate<AF5>>,
        in_: &'static AudioBuffer,
        out: &'static AudioBuffer,
        _clocks: I2SClocks,
    ) -> I2S {
        // Enable and reset the peripheral.
        let rcc = unsafe { &*RCC::ptr() };
        rcc.apb1enr.modify(|_, w| w.spi2en().set_bit());
        rcc.apb1rstr.modify(|_, w| w.spi2rst().set_bit());
        rcc.apb1rstr.modify(|_, w| w.spi2rst().clear_bit());
        //rcc.ahb1enr.modify(|_, w| w.dma1en().set_bit());

        // Disable SS output.
        spi.cr2.write(|w| w.ssoe().clear_bit());

        // Configure the pins.
        // TODO

        // Configure the clocks.
        // For the values, see the comment in I2SClocks::init().
        spi.i2spr
            .write(|w| unsafe { w.mckoe().set_bit().odd().set_bit().i2sdiv().bits(3) });
        i2sext
            .i2spr
            .write(|w| unsafe { w.mckoe().set_bit().odd().set_bit().i2sdiv().bits(3) });

        // Configure I2S.
        spi.i2scfgr.modify(|_, w| unsafe {
            w.i2smod()
                .set_bit()
                .i2se()
                .clear_bit()
                .i2scfg()
                .master_tx()
                .i2sstd()
                .bits(0b01)
                .ckpol()
                .idle_high()
                .datlen()
                .bits(0b10)
                .chlen()
                .set_bit()
        });
        i2sext.i2scfgr.modify(|_, w| unsafe {
            w.i2smod()
                .set_bit()
                .i2se()
                .clear_bit()
                .i2scfg()
                .slave_rx()
                .i2sstd()
                .bits(0b01)
                .ckpol()
                .idle_high()
                .datlen()
                .bits(0b10)
                .chlen()
                .set_bit()
        });
        i2sext.cr2.modify(|_, w| w.rxdmaen().set_bit());
        spi.cr2.modify(|_, w| w.txdmaen().set_bit());

        rx_dma.set_channel(dma::Channel3);
        rx_dma.clear_interrupts();
        unsafe {
            rx_dma.set_memory_size(1);
            rx_dma.set_peripheral_size(1);
        }
        rx_dma.set_memory_increment(true);
        rx_dma.set_direction(dma::PeripheralToMemory);
        rx_dma.set_interrupts_enable(true, false, true, true);
        rx_dma.set_double_buffer(true);
        rx_dma.set_fifo_enable(false);
        rx_dma.set_number_of_transfers((SAMPLES_PER_PACKET * CHANNELS * 2) as u16);

        tx_dma.set_channel(dma::Channel0);
        tx_dma.clear_interrupts();
        unsafe {
            tx_dma.set_memory_size(1);
            tx_dma.set_peripheral_size(1);
        }
        tx_dma.set_memory_increment(true);
        tx_dma.set_direction(dma::MemoryToPeripheral);
        tx_dma.set_interrupts_enable(true, false, true, true);
        tx_dma.set_double_buffer(true);
        tx_dma.set_fifo_enable(false);
        tx_dma.set_number_of_transfers((SAMPLES_PER_PACKET * CHANNELS * 2) as u16);

        const DR_OFFSET: usize = 0x0c;
        let spi_dr = SPI2::ptr() as usize + DR_OFFSET;
        let i2sext_dr = I2S2EXT::ptr() as usize + DR_OFFSET;
        rx_dma.set_peripheral_address(i2sext_dr as u32);
        tx_dma.set_peripheral_address(spi_dr as u32);

        I2S {
            spi,
            i2sext,
            rx_dma,
            tx_dma,
            in_,
            in_packets: [None, None],
            out,
            out_packets: [None, None],
        }
    }

    /// Starts the data stream of the I2S interface.
    pub fn start(&mut self) {
        interrupt::free(|cs| {
            for i in 0..2 {
                self.in_packets[i] = self.in_.borrow_write(cs);
            }
            for i in 0..2 {
                self.out_packets[i] = self.out.borrow_read(cs);
            }
        });

        // The system was just started, and some buffers were filled with dummy data, so we can
        // assume that borrowing was successful.
        let in_0 = self.in_packets[0].as_mut().unwrap().as_mut().as_ptr() as usize as u32;
        let in_1 = self.in_packets[1].as_mut().unwrap().as_mut().as_ptr() as usize as u32;
        self.rx_dma.set_memory_address(in_0);
        self.rx_dma.set_memory_double_buffer_address(in_1);
        /*self.dma.st[3].m0ar.write(|w| unsafe { w.bits(in_0) });
        self.dma.st[3].m1ar.write(|w| unsafe { w.bits(in_1) });*/
        let out_0 = self.out_packets[0].as_ref().unwrap().as_ref().as_ptr() as usize as u32;
        let out_1 = self.out_packets[1].as_ref().unwrap().as_ref().as_ptr() as usize as u32;
        self.tx_dma.set_memory_address(out_0);
        self.tx_dma.set_memory_double_buffer_address(out_1);
        /*self.dma.st[4].m0ar.write(|w| unsafe { w.bits(out_0) });
        self.dma.st[4].m1ar.write(|w| unsafe { w.bits(out_1) });*/

        // Enable DMA and activate the I2S peripherals.
        /*self.dma.st[3].cr.modify(|_, w| w.en().set_bit());
        self.dma.st[4].cr.modify(|_, w| w.en().set_bit());*/
        unsafe {
            self.rx_dma.enable();
            self.tx_dma.enable();
        }
        self.i2sext.i2scfgr.modify(|_, w| w.i2se().set_bit());
        self.spi.i2scfgr.modify(|_, w| w.i2se().set_bit());

        /*loop {
            if self.spi.sr.read().txe().bit_is_set() {
                self.spi.dr.write(|w| unsafe { w.bits(0xaaaa) });
            }
            if self.i2sext.sr.read().rxne().bit_is_set() {
                self.i2sext.dr.read();
            }
        }*/
    }

    /// Reads data from the output audio buffer and writes to the input audio buffer as required.
    ///
    /// This function needs to be called from the DMA interrupt handler.
    pub fn poll(&mut self) {
        // TODO
    }
}
