//! Contains the controller interface

use embedded_hal::{
    delay::*,
    digital::{InputPin, OutputPin},
    spi::{Operation, SpiDevice},
};

#[cfg(feature = "defmt")]
use defmt;

/// Interface Error
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
    /// A error in the spi driver
    SpiError,
    /// A error in the gpio driver
    GPIOError,
    /// The display busy check timed out
    BusyTimeout,
    /// Buffer alignment incorrect
    BufferAlignment,
}

/// Trait to describe the interface with the controller
/// The controller supports different hardware interfaces like i2c, usb, spi and i80
pub trait IT8951Interface {
    /// set wait timeout
    /// internally used by the library
    fn set_busy_timeout(&mut self, timeout: core::time::Duration);

    /// active wait while the controller is busy and no new transactions should be issued
    fn wait_while_busy(&mut self) -> Result<(), Error>;

    /// write a 16bit value to the controller
    fn write_data(&mut self, data: u16) -> Result<(), Error>;

    /// write multiple 16bit values to the controller
    /// data must be aligned to u16!
    fn write_multi_data(&mut self, data: &[u8]) -> Result<(), Error>;

    /// issue a command on the controller
    fn write_command(&mut self, cmd: u16) -> Result<(), Error>;

    /// issue a command with arguments on the controller
    fn write_command_with_args(&mut self, cmd: u16, args: &[u16]) -> Result<(), Error> {
        self.write_command(cmd)?;
        for arg in args {
            self.write_data(*arg)?;
        }
        Ok(())
    }

    /// read a single 16 bit value
    fn read_data(&mut self) -> Result<u16, Error>;

    /// read multiple 16bit values
    /// Data must be aligned to u16!
    fn read_multi_data(&mut self, buf: &mut [u8]) -> Result<(), Error>;

    /// reset the controller
    fn reset(&mut self) -> Result<(), Error>;

    /// wait
    fn delay(&mut self, duration: core::time::Duration) -> Result<(), Error>;
}

/// Implements the controller interface for the spi hardware interface
/// Uses embedded_hal spi and gpio driver and a embedded_hal delay driver
pub struct IT8951SPIInterface<SPI, BUSY, RST, DELAY> {
    spi: SPI,
    busy: BUSY,
    rst: Option<RST>,
    delay: DELAY,
    timeout: core::time::Duration,
}

impl<SPI, BUSY, RST, DELAY> IT8951SPIInterface<SPI, BUSY, RST, DELAY>
where
    SPI: SpiDevice,
    BUSY: InputPin,
    RST: OutputPin,
    DELAY: DelayNs,
{
    /// Create a new spi controller interface
    pub fn new(
        spi: SPI,
        busy: BUSY,
        rst: RST,
        delay: DELAY,
    ) -> IT8951SPIInterface<SPI, BUSY, RST, DELAY> {
        IT8951SPIInterface {
            spi,
            busy,
            rst: Some(rst),
            delay,
            timeout: core::time::Duration::from_secs(1),
        }
    }

    /// Create a new spi controller interface when the reset pin of the IT8951 is not connected
    /// to a GPIO pin of the microcontroller (as in for example the M5 Paper)
    pub fn new_no_rst(
        spi: SPI,
        busy: BUSY,
        delay: DELAY,
    ) -> IT8951SPIInterface<SPI, BUSY, RST, DELAY> {
        IT8951SPIInterface {
            spi,
            busy,
            rst: None,
            delay,
            timeout: core::time::Duration::from_secs(1),
        }
    }
}

impl<SPI, BUSY, RST, DELAY> IT8951Interface for IT8951SPIInterface<SPI, BUSY, RST, DELAY>
where
    SPI: SpiDevice,
    BUSY: InputPin,
    RST: OutputPin,
    DELAY: DelayNs,
{
    fn set_busy_timeout(&mut self, timeout: core::time::Duration) {
        self.timeout = timeout
    }

    /*
       Exponential backoff eventually switches to longer delay.
       When ussed with FreeRtos Delay, longer delay allows for other tasks to
       execute instead of busy-loop for longer screen operations
    */
    fn wait_while_busy(&mut self) -> Result<(), Error> {
        let timeout_us = self.timeout.as_micros() as u32;

        // Cap max backoff so we won't overshoot timeout significantly
        // Set approximately to free-rtos tick to allow for other tasks to run
        const BACKOFF_CAP_US: u32 = 1000;

        let mut delay_us = 200_u32;

        // This is estimation of total wait time,
        // Prone to under-estimating but good enough for what it is for
        let mut accumulated_delay_us = 0_u32;

        while self.busy.is_low().map_err(|_| Error::GPIOError)? {
            if accumulated_delay_us > timeout_us {
                #[cfg(feature = "defmt")]
                defmt::warn!("Timeout while waiting, waited {}μs", timeout_us);

                return Err(Error::BusyTimeout);
            }
            self.delay.delay_us(delay_us);
            accumulated_delay_us += delay_us;
            if delay_us < BACKOFF_CAP_US {
                delay_us *= 2;
            }
        }

        Ok(())
    }

    fn write_data(&mut self, data: u16) -> Result<(), Error> {
        self.wait_while_busy()?;

        // Write Data:
        // 0x0000 -> Prefix for a Data Write
        // data; u16 -> 16bit data to write
        let buf = [0x00, 0x00, (data >> 8) as u8, data as u8];

        if self.spi.write(&buf).is_err() {
            #[cfg(feature = "defmt")]
            defmt::warn!("SPI Error while writing");

            return Err(Error::SpiError);
        }

        Ok(())
    }

    fn write_multi_data(&mut self, data: &[u8]) -> Result<(), Error> {
        self.wait_while_busy()?;

        if !data.len().is_multiple_of(2) {
            #[cfg(feature = "defmt")]
            defmt::warn!("Buffer alignment error");

            return Err(Error::BufferAlignment);
        };

        if self
            .spi
            .transaction(&mut [Operation::Write(&[0x00, 0x00]), Operation::Write(data)])
            .is_err()
        {
            #[cfg(feature = "defmt")]
            defmt::warn!("SPI Error while writing");

            return Err(Error::SpiError);
        }

        Ok(())
    }

    fn write_command(&mut self, cmd: u16) -> Result<(), Error> {
        self.wait_while_busy()?;

        // Write Command:
        // 0x6000 -> Prefix for a Command
        // cmd; u16 -> 16bit Command code
        let buf = [0x60, 0x00, (cmd >> 8) as u8, cmd as u8];

        if self.spi.write(&buf).is_err() {
            #[cfg(feature = "defmt")]
            defmt::warn!("SPI Error while writing");

            return Err(Error::SpiError);
        }
        Ok(())
    }

    fn read_data(&mut self) -> Result<u16, Error> {
        self.wait_while_busy()?;

        // Read Data
        // 0x1000 -> Prefix for Read Data
        let mut buf = [0x10, 0x00, 0x00, 0x00, 0x00, 0x00];
        if self.spi.transfer_in_place(&mut buf).is_err() {
            #[cfg(feature = "defmt")]
            defmt::warn!("SPI Error while reading");

            return Err(Error::SpiError);
        }
        // we skip the first 2 bytes -> shifted out while transfer the prefix
        // the next two bytes are only dummies and are skipped to
        // only the last two bytes are the expected data and are stored
        Ok(u16::from_be_bytes([buf[4], buf[5]]))
    }

    fn read_multi_data(&mut self, buf: &mut [u8]) -> Result<(), Error> {
        self.wait_while_busy()?;

        if !buf.len().is_multiple_of(2) {
            #[cfg(feature = "defmt")]
            defmt::warn!("Buffer alignment error");

            return Err(Error::BufferAlignment);
        };

        // 0x1000 prefix for read data
        let cmd = [0x10_u8, 0x00, 0x00, 0x00];
        if self
            .spi
            .transaction(&mut [Operation::Write(&cmd), Operation::TransferInPlace(buf)])
            .is_err()
        {
            #[cfg(feature = "defmt")]
            defmt::warn!("SPI Error while reading");

            return Err(Error::SpiError);
        }

        Ok(())
    }

    fn reset(&mut self) -> Result<(), Error> {
        // If reset pin was not setup we just do nothing here
        let Some(rst) = self.rst.as_mut() else {
            return Ok(());
        };
        if rst.set_high().is_err() {
            #[cfg(feature = "defmt")]
            defmt::warn!("IO Error while resetting");

            return Err(Error::GPIOError);
        }
        self.delay.delay_ms(200);
        if rst.set_low().is_err() {
            #[cfg(feature = "defmt")]
            defmt::warn!("IO Error while resetting");

            return Err(Error::GPIOError);
        }
        self.delay.delay_ms(20);
        if rst.set_high().is_err() {
            #[cfg(feature = "defmt")]
            defmt::warn!("IO Error while resetting");

            return Err(Error::GPIOError);
        }
        self.delay.delay_ms(200);
        Ok(())
    }

    fn delay(&mut self, duration: core::time::Duration) -> Result<(), Error> {
        self.delay.delay_us(duration.as_micros() as u32);
        Ok(())
    }
}
