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

use runtime::{
    abi::io::device::i2c,
    rt::io::device::{device_request, i2c::SmbusRead},
};

use crate::sys::{I2CMaster, I2CMasterConfig};

pub struct I2CMasterImpl {
    file: File,
}

impl Read for I2CMasterImpl {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.file.read(buf)
    }
}

impl Write for I2CMasterImpl {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.file.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.file.flush()
    }
}

impl I2CMaster for I2CMasterImpl {
    fn open<P: AsRef<Path>>(device: P, config: &I2CMasterConfig) -> io::Result<Self> {
        let file = OpenOptions::new().read(true).write(true).open(device)?;
        let mut this = Self { file };
        if let Some(address) = config.slave_address {
            this.set_slave_address(address)?;
        }
        if let Some(speed_hz) = config.speed_hz {
            this.set_speed_hz(speed_hz)?;
        }
        Ok(this)
    }

    fn set_slave_address(&mut self, address: u16) -> io::Result<()> {
        let mut buffer = [0; 4];
        device_request::<i2c::SetAddress>(self.file.as_raw_fd(), &mut buffer, &address)?;
        Ok(())
    }

    fn set_speed_hz(&mut self, speed_hz: u32) -> io::Result<()> {
        let mut buffer = [0; 4];
        device_request::<i2c::SetSpeed>(self.file.as_raw_fd(), &mut buffer, &speed_hz)?;
        Ok(())
    }

    fn smbus_read(&mut self, reg: u8, data: &mut [u8]) -> io::Result<usize> {
        let mut buffer = [0; 32];
        let xfer = SmbusRead {
            reg,
            value_buffer: data.as_mut_ptr(),
            value_buffer_len: data.len(),
        };
        let len = device_request::<i2c::SmbusReadReg>(self.file.as_raw_fd(), &mut buffer, &xfer)?;
        Ok(len)
    }

    fn smbus_write(&mut self, reg: u8, data: &[u8]) -> io::Result<usize> {
        // TODO incorrect
        self.write_all(&[reg])?;
        self.write(data)
    }
}

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