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

use libc::c_uint;

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

const IOC_I2C_SLAVE: u64 = 0x0703;

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<()> {
        if unsafe { libc::ioctl(self.file.as_raw_fd(), IOC_I2C_SLAVE, address as c_uint) } != 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(())
        }
    }

    fn set_speed_hz(&mut self, speed_hz: u32) -> io::Result<()> {
        // TODO
        let _ = speed_hz;
        Ok(())
    }

    fn smbus_read(&mut self, reg: u8, data: &mut [u8]) -> io::Result<usize> {
        self.write_all(&[reg])?;
        self.read(data)
    }

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

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