use core::task::{Context, Poll};

use alloc::boxed::Box;
use async_trait::async_trait;
use device_api::{
    clock::Hertz,
    i2c::{I2CAddress, I2CDevice, I2CMessage, I2CTransfer},
};
use yggdrasil_abi::{
    error::Error,
    io::device::i2c::{self, I2CRequestVariant},
    option::RequestValue,
    process::ProcessId,
};

use crate::{
    device::char::CharDevice,
    task::{mem::ForeignPointer, thread::Thread},
    vfs::FileReadiness,
};

// TODO timeouts and better access
#[async_trait]
impl CharDevice for I2CDevice {
    async fn read(&self, buffer: &mut [u8]) -> Result<usize, Error> {
        let pid = Thread::current().process_id();
        self.check_lock(pid)?;
        let mut message = [I2CMessage::Read(buffer)];
        let mut xfer = I2CTransfer {
            messages: &mut message,
            repeat_start: false,
        };
        self.i2c_transfer(self.slave_address(), &mut xfer).await
    }
    async fn write(&self, buffer: &[u8]) -> Result<usize, Error> {
        let pid = Thread::current().process_id();
        self.check_lock(pid)?;
        let mut message = [I2CMessage::Write(buffer)];
        let mut xfer = I2CTransfer {
            messages: &mut message,
            repeat_start: false,
        };
        self.i2c_transfer(self.slave_address(), &mut xfer).await
    }
    fn read_nonblocking(&self, buffer: &mut [u8]) -> Result<usize, Error> {
        let _ = buffer;
        Err(Error::InvalidOperation)
    }
    fn write_nonblocking(&self, buffer: &[u8]) -> Result<usize, Error> {
        let _ = buffer;
        Err(Error::InvalidOperation)
    }
    fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
        if let Ok(req) = I2CRequestVariant::try_from(option) {
            match req {
                I2CRequestVariant::SetAddress => {
                    let address = i2c::SetAddress::load_request(&buffer[..len])?;
                    let address = I2CAddress::try_from(address)?;
                    self.set_slave_address(address);
                    Ok(0)
                }
                I2CRequestVariant::SetSpeed => {
                    let speed = i2c::SetSpeed::load_request(&buffer[..len])?;
                    let speed = Hertz::from(speed);
                    let actual = self.set_speed(speed)?.0 as u32;
                    let len = i2c::SetSpeed::store_response(&actual, buffer)?;
                    Ok(len)
                }
                I2CRequestVariant::GetMasterInfo => {
                    let info = self.capabilities();
                    let len = i2c::GetMasterInfo::store_response(&info, buffer)?;
                    Ok(len)
                }
                I2CRequestVariant::SmbusReadReg => {
                    let thread = Thread::current();
                    let pid = thread.process_id();
                    self.check_lock(pid)?;
                    let read_reg = i2c::SmbusReadReg::load_request(&buffer[..len])?;
                    let space = thread.address_space();
                    let rx_buffer = unsafe {
                        read_reg
                            .value_buffer
                            .validate_user_slice_mut(read_reg.value_buffer_len, space)
                    }?;
                    let tx_buffer = [read_reg.reg];
                    let mut messages = [I2CMessage::Write(&tx_buffer), I2CMessage::Read(rx_buffer)];
                    let mut xfer = I2CTransfer {
                        messages: &mut messages,
                        repeat_start: true,
                    };
                    let len = block!(self.i2c_transfer(self.slave_address(), &mut xfer).await)??;
                    let len = i2c::SmbusReadReg::store_response(&len.saturating_sub(1), buffer)?;
                    Ok(len)
                }
            }
        } else {
            Err(Error::InvalidArgument)
        }
    }

    fn open(&self, process: ProcessId) -> Result<(), Error> {
        I2CDevice::lock(self, process)
    }
    fn close(&self, process: ProcessId) -> Result<(), Error> {
        I2CDevice::release(self, process)
    }
}
impl FileReadiness for I2CDevice {
    fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        let _ = cx;
        todo!()
    }
}
