use std::{
    cmp,
    fs::{File, OpenOptions},
    io,
    os::fd::{AsRawFd, RawFd},
    path::Path,
};

use runtime::{abi::io::device::spi as sys_spi, rt::io::device::device_request};

use crate::sys::{SpiClockPhase, SpiClockPolarity, SpiConfig, SpiDevice};

pub struct SpiDeviceImpl {
    file: File,
}

impl SpiDevice for SpiDeviceImpl {
    fn open<P: AsRef<Path>>(device: P, config: &SpiConfig) -> io::Result<Self> {
        let file = OpenOptions::new().read(true).write(true).open(device)?;
        let mut this = Self { file };
        this.set_config(config)?;
        Ok(this)
    }

    fn set_config(&mut self, config: &SpiConfig) -> io::Result<()> {
        let clock_polarity = config.clock_polarity.map(Into::into);
        let clock_phase = config.clock_phase.map(Into::into);

        let sys_config = sys_spi::SpiConfig {
            clock_polarity,
            clock_phase,
            frequency: config.frequency,
            chip_select_index: config.chip_select_index,
        };

        let mut buffer = [0; 256];
        device_request::<sys_spi::SetConfig>(self.file.as_raw_fd(), &mut buffer, &sys_config)?;

        Ok(())
    }

    fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> io::Result<usize> {
        let xfer = sys_spi::SpiTransfer {
            tx_buffer: tx.as_ptr(),
            rx_buffer: rx.as_mut_ptr(),
            len: cmp::min(tx.len(), rx.len()),
        };
        let mut buffer = [0; 32];
        let len = device_request::<sys_spi::Transfer>(self.file.as_raw_fd(), &mut buffer, &xfer)?;
        Ok(len)
    }
}

impl AsRawFd for SpiDeviceImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.file.as_raw_fd()
    }
}

impl From<SpiClockPolarity> for sys_spi::SpiClockPolarity {
    fn from(value: SpiClockPolarity) -> Self {
        match value {
            SpiClockPolarity::IdleLow => Self::IdleLow,
            SpiClockPolarity::IdleHigh => Self::IdleHigh,
        }
    }
}

impl From<SpiClockPhase> for sys_spi::SpiClockPhase {
    fn from(value: SpiClockPhase) -> Self {
        match value {
            SpiClockPhase::CaptureOnFirstTransition => Self::CaptureOnFirstTransition,
            SpiClockPhase::CaptureOnSecondTransition => Self::CaptureOnSecondTransition,
        }
    }
}
