use crate::hardware::error::WrappingError;
use anyhow::anyhow;
use embedded_hal::i2c::{ErrorType, I2c, Operation, SevenBitAddress};
use log::trace;
use std::collections::HashMap;
use crate::CRC_8_CCITT;
use log::warn;

const CRC: crc::Crc<u8> = crc::Crc::<u8>::new(&CRC_8_CCITT);

#[derive(Debug)]
pub struct SimMct8316a {
    data: HashMap<u32, u32>,
}

impl SimMct8316a {
    pub fn new() -> Self {
        trace!("SimMct8316a::new()");
        Self {
            data: HashMap::new(),
        }
    }
}

impl ErrorType for SimMct8316a {
    type Error = WrappingError<anyhow::Error>;
}

impl I2c for SimMct8316a {
    fn transaction(
        &mut self,
        i2c_addr: SevenBitAddress,
        operations: &mut [Operation<'_>],
    ) -> Result<(), Self::Error> {
        trace!(
            "SimMct8316a::transaction(self: {self:?}, i2c_addr: {i2c_addr}, operations: {operations:?})"
        );
        let mut do_read_operation = false;
        let mut include_crc = false;
        let mut data_length = 2;
        let mut address: u32 = 0;
        let mut crc = CRC.digest();
        for operation in operations {
            match operation {
                Operation::Write(write_buffer) => {
                    // Ignore incoming write operations
                    if write_buffer.len() < 3 {
                        return Err(WrappingError(anyhow!("Not enough data in write")));
                    }
                    // We don't care about the first byte
                    do_read_operation = write_buffer[0] & 0x80 > 0;
                    include_crc = write_buffer[0] & 0x40 > 0;
                    if write_buffer[0] & 0x10 > 0 {
                        data_length = 4;
                    } else if write_buffer[0] & 0x20 > 0 {
                        data_length = 8;
                    }
                    address |= u32::from(write_buffer[0] & 0xF) << 16;
                    address |= u32::from(write_buffer[1]) << 8;
                    address |= u32::from(write_buffer[2]);
                    if !do_read_operation {
                        if write_buffer.len() != 3 + data_length + 1 {
                            return Err(WrappingError(anyhow!(
                                "Write for write has wrong length {}. {} expected",
                                write_buffer.len(),
                                3 + data_length + 1
                            )));
                        }
                        if data_length == 2 {
                            todo!("Unimplemented");
                        } else {
                            let written_value = u32::from_le_bytes([
                                write_buffer[3],
                                write_buffer[4],
                                write_buffer[5],
                                write_buffer[6],
                            ]);
                            self.data.insert(address, written_value);
                            if data_length == 8 {
                                todo!("Unimplemented");
                            }
                        }
                    } else if write_buffer.len() != 3 {
                        return Err(WrappingError(anyhow!(
                            "Write for read has wrong length {}. {} expected",
                            write_buffer.len(),
                            3
                        )));
                    }
                    crc.update(&[(i2c_addr << 1) | 0b0]);
                    crc.update(write_buffer);
                }
                Operation::Read(read_buffer) => {
                    if !do_read_operation {
                        return Err(WrappingError(anyhow!("Unexpected Read Operation")));
                    }
                    let expected_length = data_length + usize::from(include_crc);
                    if read_buffer.len() != expected_length {
                        return Err(WrappingError(anyhow!(
                            "Unexpected length in read buffer {}. {} expected",
                            read_buffer.len(),
                            expected_length
                        )));
                    }
                    crc.update(&[(i2c_addr << 1) | 0b1]);
                    if data_length == 2 {
                        todo!("Unimplemented");
                    } else if data_length == 4 {
                        let value = *self.data.get(&address).unwrap_or(&0);
                        read_buffer[0..4].copy_from_slice(&value.to_le_bytes());
                    } else {
                        todo!("Unimplemented");
                    }
                    crc.update(&read_buffer[0..data_length]);
                    if do_read_operation {
                        read_buffer[data_length] = crc.clone().finalize();
                    }
                }
            }
        }
        Ok(())
    }
}
