use crate::chromium_ec::command::EcCommands;
use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResult};
use crate::util;
use alloc::format;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use std::mem::size_of;

/// Maximum transfer size for one I2C transaction supported by the chip
pub const MAX_I2C_CHUNK: usize = 128;

#[repr(C, packed)]
pub struct EcParamsI2cPassthruMsg {
    /// Slave address and flags
    addr_and_flags: u16,
    transfer_len: u16,
}

#[repr(C, packed)]
pub struct EcParamsI2cPassthru {
    port: u8,
    /// How many messages
    messages: u8,
    msg: [EcParamsI2cPassthruMsg; 0],
}

#[repr(C, packed)]
struct _EcI2cPassthruResponse {
    i2c_status: u8,
    /// How many messages
    messages: u8,
    data: [u8; 0],
}

#[derive(Debug)]
pub struct EcI2cPassthruResponse {
    pub i2c_status: u8, // TODO: Can probably use enum
    pub data: Vec<u8>,
}

impl EcI2cPassthruResponse {
    pub fn is_successful(&self) -> EcResult<()> {
        if self.i2c_status & 1 > 0 {
            return Err(EcError::DeviceError(
                "I2C Transfer not acknowledged".to_string(),
            ));
        }
        if self.i2c_status & (1 << 1) > 0 {
            return Err(EcError::DeviceError("I2C Transfer timeout".to_string()));
        }
        // Check for any other unknown error bits
        if self.i2c_status != 0 {
            return Err(EcError::DeviceError(format!(
                "I2C Transfer failed with unknown status: 0x{:02X}",
                self.i2c_status
            )));
        }
        Ok(())
    }
}

/// Indicate that it's a read, not a write
const I2C_READ_FLAG: u16 = 1 << 15;

pub fn i2c_read(
    ec: &CrosEc,
    i2c_port: u8,
    i2c_addr: u16,
    addr: u16,
    len: u16,
) -> EcResult<EcI2cPassthruResponse> {
    trace!(
        "i2c_read(i2c_port: 0x{:X}, i2c_addr: 0x{:X}, addr: 0x{:X}, len: 0x{:X})",
        i2c_port,
        i2c_addr,
        addr,
        len
    );
    if usize::from(len) > MAX_I2C_CHUNK {
        return EcResult::Err(EcError::DeviceError(format!(
            "i2c_read too long. Must be <128, is: {}",
            len
        )));
    }
    let addr_bytes = if addr < 0xFF {
        vec![addr as u8]
    } else {
        u16::to_le_bytes(addr).to_vec()
    };
    let messages = vec![
        EcParamsI2cPassthruMsg {
            addr_and_flags: i2c_addr,
            transfer_len: addr_bytes.len() as u16,
        },
        EcParamsI2cPassthruMsg {
            addr_and_flags: i2c_addr + I2C_READ_FLAG,
            transfer_len: len, // How much to read
        },
    ];
    let msgs_len = size_of::<EcParamsI2cPassthruMsg>() * messages.len();
    let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) };

    let params = EcParamsI2cPassthru {
        port: i2c_port,
        messages: messages.len() as u8,
        msg: [], // Messages are copied right after this struct
    };
    let params_len = size_of::<EcParamsI2cPassthru>();
    let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(&params) };

    let mut buffer: Vec<u8> = vec![0; params_len + msgs_len + addr_bytes.len()];
    buffer[0..params_len].copy_from_slice(params_buffer);
    buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer);
    buffer[params_len + msgs_len..].copy_from_slice(&addr_bytes);

    let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?;
    let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) };
    let res_data = &data[size_of::<_EcI2cPassthruResponse>()..];
    // TODO: Seems to be either one, non-deterministically
    debug_assert!(res.messages as usize == messages.len() || res.messages == 0);
    Ok(EcI2cPassthruResponse {
        i2c_status: res.i2c_status,
        data: res_data.to_vec(),
    })
}

/// I2C read with 16-bit addressing (for larger EEPROMs like 24C32+)
/// Always sends a 2-byte address, even for addr=0
pub fn i2c_read_16bit_addr(
    ec: &CrosEc,
    i2c_port: u8,
    i2c_addr: u16,
    addr: u16,
    len: u16,
) -> EcResult<EcI2cPassthruResponse> {
    trace!(
        "i2c_read_16bit_addr(i2c_port: 0x{:X}, i2c_addr: 0x{:X}, addr: 0x{:X}, len: 0x{:X})",
        i2c_port,
        i2c_addr,
        addr,
        len
    );
    if usize::from(len) > MAX_I2C_CHUNK {
        return EcResult::Err(EcError::DeviceError(format!(
            "i2c_read too long. Must be <128, is: {}",
            len
        )));
    }
    // Always use 16-bit addressing (big-endian for EEPROM)
    let addr_bytes = u16::to_be_bytes(addr).to_vec();
    let messages = vec![
        EcParamsI2cPassthruMsg {
            addr_and_flags: i2c_addr,
            transfer_len: addr_bytes.len() as u16,
        },
        EcParamsI2cPassthruMsg {
            addr_and_flags: i2c_addr + I2C_READ_FLAG,
            transfer_len: len, // How much to read
        },
    ];
    let msgs_len = size_of::<EcParamsI2cPassthruMsg>() * messages.len();
    let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) };

    let params = EcParamsI2cPassthru {
        port: i2c_port,
        messages: messages.len() as u8,
        msg: [], // Messages are copied right after this struct
    };
    let params_len = size_of::<EcParamsI2cPassthru>();
    let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(&params) };

    let mut buffer: Vec<u8> = vec![0; params_len + msgs_len + addr_bytes.len()];
    buffer[0..params_len].copy_from_slice(params_buffer);
    buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer);
    buffer[params_len + msgs_len..].copy_from_slice(&addr_bytes);

    let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?;
    let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) };
    let header_len: usize = size_of::<_EcI2cPassthruResponse>();
    /* Note on windows, you can get extra bytes back, so truncate this to the requested size */
    let res_data = &data[header_len..(len as usize + header_len)];
    debug_assert!(res.messages as usize == messages.len() || res.messages == 0);
    trace!(
        "  i2c_read_16bit_addr response (len: {}, data: {:#04X?})",
        res_data.len(),
        res_data.to_vec()
    );
    Ok(EcI2cPassthruResponse {
        i2c_status: res.i2c_status,
        data: res_data.to_vec(),
    })
}

/* Write address and bytes in a single I2C transfer */
pub fn i2c_write_block(
    ec: &CrosEc,
    i2c_port: u8,
    i2c_addr: u16,
    addr: u8,
    data: &[u8],
) -> EcResult<EcI2cPassthruResponse> {
    trace!(
        "  i2c_write(addr: {}, len: {}, data: {:?})",
        addr,
        data.len(),
        data
    );
    let addr_bytes = [addr];
    let messages = vec![EcParamsI2cPassthruMsg {
        addr_and_flags: i2c_addr,
        transfer_len: (addr_bytes.len() + data.len()) as u16,
    }];
    let msgs_len = size_of::<EcParamsI2cPassthruMsg>() * messages.len();
    let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) };

    let params = EcParamsI2cPassthru {
        port: i2c_port,
        messages: messages.len() as u8,
        msg: [], // Messages are copied right after this struct
    };
    let params_len = size_of::<EcParamsI2cPassthru>();
    let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(&params) };

    let mut buffer: Vec<u8> = vec![0; params_len + msgs_len + addr_bytes.len() + data.len()];
    buffer[0..params_len].copy_from_slice(params_buffer);
    buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer);
    buffer[params_len + msgs_len..params_len + msgs_len + addr_bytes.len()]
        .copy_from_slice(&addr_bytes);
    buffer[params_len + msgs_len + addr_bytes.len()..].copy_from_slice(data);

    let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?;
    let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) };
    util::assert_win_len(data.len(), size_of::<_EcI2cPassthruResponse>()); // No extra data other than the header

    // Only assert message count if the transfer was successful
    // On error (NAK/timeout), the EC may return 0 messages processed
    if res.i2c_status == 0 {
        debug_assert_eq!(res.messages as usize, messages.len());
    }

    Ok(EcI2cPassthruResponse {
        i2c_status: res.i2c_status,
        data: vec![], // Writing doesn't return any data
    })
}

pub fn i2c_write(
    ec: &CrosEc,
    i2c_port: u8,
    i2c_addr: u16,
    addr: u16,
    data: &[u8],
) -> EcResult<EcI2cPassthruResponse> {
    trace!(
        "  i2c_write(addr: {}, len: {}, data: {:?})",
        addr,
        data.len(),
        data
    );
    let addr_bytes = [addr as u8, (addr >> 8) as u8];
    let messages = vec![EcParamsI2cPassthruMsg {
        addr_and_flags: i2c_addr,
        transfer_len: (addr_bytes.len() + data.len()) as u16,
    }];
    let msgs_len = size_of::<EcParamsI2cPassthruMsg>() * messages.len();
    let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) };

    let params = EcParamsI2cPassthru {
        port: i2c_port,
        messages: messages.len() as u8,
        msg: [], // Messages are copied right after this struct
    };
    let params_len = size_of::<EcParamsI2cPassthru>();
    let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(&params) };

    let mut buffer: Vec<u8> = vec![0; params_len + msgs_len + addr_bytes.len() + data.len()];
    buffer[0..params_len].copy_from_slice(params_buffer);
    buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer);
    buffer[params_len + msgs_len..params_len + msgs_len + addr_bytes.len()]
        .copy_from_slice(&addr_bytes);
    buffer[params_len + msgs_len + addr_bytes.len()..].copy_from_slice(data);

    let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?;
    let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) };
    util::assert_win_len(data.len(), size_of::<_EcI2cPassthruResponse>()); // No extra data other than the header

    // Only assert message count if the transfer was successful
    // On error (NAK/timeout), the EC may return 0 messages processed
    if res.i2c_status == 0 {
        debug_assert_eq!(res.messages as usize, messages.len());
    }

    Ok(EcI2cPassthruResponse {
        i2c_status: res.i2c_status,
        data: vec![], // Writing doesn't return any data
    })
}
