/// Error type
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Error {
    /// Not a valid device ID
    InvalidDevice(u8),
    /// No more empty device slots
    Full,
}

pub type Result<T> = core::result::Result<T, Error>;

/// Implemented by every SPI device
/// Provides an SPI interface
pub trait SpiDevice {
    /// Selects the device
    fn select(&mut self, _sel: bool) {}

    /// Clocks the spi device
    /// Returns MISO
    fn clock(&mut self, mosi: bool) -> bool;
}

impl SpiDevice for Box<dyn SpiDevice> {
    fn select(&mut self, sel: bool) {
        self.as_mut().select(sel)
    }

    fn clock(&mut self, mosi: bool) -> bool {
        self.as_mut().clock(mosi)
    }
}

impl<T: SpiDevice> SpiDevice for core::cell::RefCell<T> {
    fn select(&mut self, sel: bool) {
        self.borrow_mut().select(sel)
    }

    fn clock(&mut self, mosi: bool) -> bool {
        self.borrow_mut().clock(mosi)
    }
}

impl<T: SpiDevice> SpiDevice for std::rc::Rc<core::cell::RefCell<T>> {
    fn select(&mut self, sel: bool) {
        self.borrow_mut().select(sel)
    }

    fn clock(&mut self, mosi: bool) -> bool {
        self.borrow_mut().clock(mosi)
    }
}

/// A host controller for SPI
#[derive(Default)]
pub struct SpiHost {
    /// Devices (up to 8)
    devices: [Option<Box<dyn SpiDevice>>; 8],
    /// Control register
    ctrl: u8,
    /// Data in
    data_in: u8,
    /// Device select
    device_sel: u8,

    /// Last system clock
    last_sys_clk: bool,
    /// Last external clock
    last_ext_clk: bool,

    /// Data out
    data_out: u8,
    /// Bits remaining to clock
    rem: u8,

    /// Transmission complete
    tc: bool,
    /// Interrupt status
    int: bool,
}

const STS_TC: u8 = 1 << 7;
const STS_BSY: u8 = 1 << 6;

const CTRL_MASK: u8 = 0b00111111;
/// Software Reset
const CTRL_SR: u8 = 1 << 7;
/// Endianness (true = MSB-first, false = LSB-first)
const CTRL_END: u8 = 1 << 6;
/// Interrupt Enable
const CTRL_IER: u8 = 1 << 5;
/// Fast Transfer
const CTRL_FRX: u8 = 1 << 4;
/// Tristate MO
const CTRL_TMO: u8 = 1 << 3;
/// External Clock Enable
const CTRL_ECE: u8 = 1 << 2;
/// Clock Polarity
const CTRL_CPOL: u8 = 1 << 1;
/// Clock Phase
const CTRL_CPHA: u8 = 1 << 0;

impl SpiHost {
    /// Creates a new SPI controller
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    /// Creates a new SPI controller with initial clock state
    #[inline]
    pub fn with_clock(clk: bool) -> Self {
        Self {
            last_sys_clk: clk,
            last_ext_clk: clk,
            ..Default::default()
        }
    }

    /// Add a device, returning the ID
    pub fn add_device(&mut self, device: Box<dyn SpiDevice>) -> Result<u8> {
        let id = self
            .devices
            .iter()
            .enumerate()
            .filter(|(_, v)| v.is_none())
            .map(|(k, _)| k)
            .next()
            .ok_or(Error::Full)?;

        self.devices[id] = Some(device);
        Ok(id as u8)
    }

    /// Remove a device by ID
    pub fn remove_device(&mut self, id: u8) -> Result<()> {
        if id >= 8 || self.devices[id as usize].is_none() {
            return Err(Error::InvalidDevice(id));
        }

        self.devices[id as usize] = None;
        Ok(())
    }

    /// Called when there is a clock transition
    fn clock(&mut self, mut clk: bool) {
        // clk is false if neg transition or true if pos transition

        // false = rising, true = falling
        let cpol = self.ctrl & CTRL_CPOL != 0;
        // false = leading, true = trailing
        let cpha = self.ctrl & CTRL_CPHA != 0;

        // invert if cpol (so clk represents polarity-aligned state)
        if cpol {
            clk = !clk;
        }

        // invert if trailing
        if cpha {
            clk = !clk;
        }

        // clk now represents whether we should clock in a bit
        if !clk {
            return;
        }

        let endian = self.ctrl & CTRL_END != 0;

        let rem = self.rem;
        let bit = if rem > 0 {
            // shift out a bit

            let bit = if endian {
                let bit = self.data_out & 0x80 != 0;
                self.data_out <<= 1;
                bit
            } else {
                let bit = self.data_out & 1 != 0;
                self.data_out >>= 1;
                bit
            };

            self.rem -= 1;

            if self.rem == 0 {
                // set tc
                self.tc = true;
                // trigger interrupt
                self.int = true;
            }

            bit
        } else {
            return;
        };

        let bit = bit && self.ctrl & CTRL_TMO == 0;
        let miso = self.drive_devices(bit);

        // shift in
        if rem > 0 {
            if endian {
                self.data_in <<= 1;
                self.data_in |= if miso { 1 } else { 0 };
            } else {
                self.data_in >>= 1;
                self.data_in |= if miso { 0x80 } else { 0 };
            }
        }
    }

    /// Called every system clock cycle
    pub fn sys_clock(&mut self, clk: bool) {
        if clk == self.last_sys_clk {
            return;
        }

        self.last_sys_clk = clk;

        if !self.ece() {
            self.clock(clk);
        }
    }

    /// Called every external clock cycle (if an external clock is hooked up)
    pub fn ext_clock(&mut self, clk: bool) {
        if clk == self.last_ext_clk {
            return;
        }

        self.last_ext_clk = clk;

        if self.ece() {
            self.clock(clk);
        }
    }

    /// External shift clock enable
    #[inline(always)]
    fn ece(&self) -> bool {
        self.ctrl & CTRL_ECE != 0
    }

    /// Drive device lines and give MISO result
    fn drive_devices(&mut self, mosi: bool) -> bool {
        let mut miso = true;
        let mut mask = self.device_sel;

        for i in 0..8 {
            if let Some(dev) = self.devices[i].as_mut() {
                if mask & 1 != 0 {
                    miso &= dev.clock(mosi);
                }
            }

            mask >>= 1;
        }

        miso
    }

    /// Read a register from CPU
    pub fn read(&mut self, a: u8) -> u8 {
        match a & 3 {
            0 => {
                self.tc = false;
                self.int = false;

                if self.ctrl & CTRL_FRX != 0 {
                    self.data_out = self.data_in;
                    self.trigger_shift();
                }

                self.data_in
            }
            1 => self.status(),
            2 | 3 => self.device_sel,
            _ => unreachable!(),
        }
    }

    /// Write a register from CPU
    pub fn write(&mut self, a: u8, d: u8) {
        match a & 3 {
            0 => self.data_out(d),
            1 => self.control(d),
            2 | 3 => {
                self.select(d);
            }
            _ => unreachable!(),
        }
    }

    /// Selects devices
    fn select(&mut self, mut d: u8) {
        self.device_sel = d;

        for i in 0..8 {
            if let Some(dev) = self.devices[i].as_mut() {
                dev.select(d & 1 != 0);
            }

            d >>= 1;
        }
    }

    /// Resets the controller
    pub fn reset(&mut self) {
        self.ctrl = 0;
        self.select(0xff);
        self.tc = false;
        self.int = false;
        self.rem = 0;
    }

    /// Interrupt status
    #[inline(always)]
    pub fn int(&self) -> bool {
        self.ctrl & CTRL_IER != 0 && self.int
    }

    /// Set data output
    fn data_out(&mut self, d: u8) {
        self.data_out = d;

        if self.rem == 0 {
            self.trigger_shift()
        } else {
            log::warn!("writing to spi data reg while transmitting, output is corrupted");
        }
    }

    /// Returns status register
    #[inline]
    fn status(&self) -> u8 {
        let tc = if self.tc { STS_TC } else { 0 };
        let bsy = if self.rem > 0 { STS_BSY } else { 0 };

        tc | bsy | (self.ctrl & CTRL_MASK)
    }

    /// Set control lines
    #[inline]
    fn control(&mut self, ctrl: u8) {
        if ctrl & CTRL_SR != 0 {
            self.reset();
        }

        self.ctrl = ctrl & CTRL_MASK;
    }

    /// Trigger a shift
    #[inline(always)]
    fn trigger_shift(&mut self) {
        self.rem = 8;
        self.tc = false;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn init(mode: u8) -> SpiHost {
        let mut ctrlr = SpiHost::default();
        ctrlr.write(1, 0b10000000 | (mode & 3)); // reset + mode
        ctrlr.write(0, 0); // out byte
        ctrlr.write(2, 0); // device select
        ctrlr
    }

    #[test]
    fn mode0() {
        let mut ctrlr = init(0);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 8);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 6);
    }

    #[test]
    fn mode1() {
        let mut ctrlr = init(1);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 6);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 6);
    }

    #[test]
    fn mode2() {
        let mut ctrlr = init(2);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 8);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 6);
    }

    #[test]
    fn mode3() {
        let mut ctrlr = init(3);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 7);
        ctrlr.clock(true);
        assert_eq!(ctrlr.rem, 6);
        ctrlr.clock(false);
        assert_eq!(ctrlr.rem, 6);
    }
}
