use bytemuck::{Pod, Zeroable};

use crate::{
    communication::UsbDirection,
    device::UsbSpeed,
    error::UsbError,
    info::{UsbDeviceClass, UsbDeviceProtocol, UsbEndpointType, UsbVersion},
};

#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct UsbDeviceDescriptor {
    pub length: u8,
    pub ty: u8,
    pub bcd_usb: u16,
    pub device_class: u8,
    pub device_subclass: u8,
    pub device_protocol: u8,
    pub max_packet_size_0: u8,
    pub id_vendor: u16,
    pub id_product: u16,
    pub bcd_device: u16,
    pub manufacturer_str: u8,
    pub product_str: u8,
    pub serial_number_str: u8,
    pub num_configurations: u8,
}

#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct UsbConfigurationDescriptor {
    pub length: u8,
    pub ty: u8,
    pub total_length: u16,
    pub num_interfaces: u8,
    pub config_val: u8,
    pub config_str: u8,
    pub attributes: u8,
    pub max_power: u8,
}

#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct UsbInterfaceDescriptor {
    pub length: u8,
    pub ty: u8,
    pub interface_number: u8,
    pub alternate_setting: u8,
    pub num_endpoints: u8,
    pub interface_class: u8,
    pub interface_subclass: u8,
    pub interface_protocol: u8,
    pub interface_str: u8,
}

#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct UsbEndpointDescriptor {
    pub length: u8,
    pub ty: u8,
    pub endpoint_address: u8,
    pub attributes: u8,
    pub max_packet_size: u16,
    pub interval: u8,
}

#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct UsbDeviceQualifier {
    pub length: u8,
    pub ty: u8,
    pub bcd_usb: u16,
    pub device_class: u8,
    pub device_subclass: u8,
    pub device_protocol: u8,
    pub max_packet_size_0: u8,
    pub num_configurations: u8,
    pub _reserved: u8,
}

#[derive(Clone, Copy, Debug, Default, Pod, Zeroable)]
#[repr(C, packed)]
pub struct UsbOtherSpeedConfiguration {
    pub length: u8,
    pub ty: u8,
    pub total_length: u16,
    pub num_interfaces: u8,
    pub config_val: u8,
    pub config_str: u8,
    pub attributes: u8,
    pub max_power: u8,
}

impl UsbInterfaceDescriptor {
    pub fn class(&self) -> UsbDeviceClass {
        UsbDeviceClass::try_from(self.interface_class).unwrap_or(UsbDeviceClass::Unknown)
    }

    pub fn protocol(&self) -> UsbDeviceProtocol {
        UsbDeviceProtocol::try_from(self.interface_protocol).unwrap_or(UsbDeviceProtocol::Unknown)
    }
}

impl UsbEndpointDescriptor {
    pub fn direction(&self) -> UsbDirection {
        match self.endpoint_address >> 7 {
            1 => UsbDirection::In,
            0 => UsbDirection::Out,
            _ => unreachable!(),
        }
    }

    pub fn number(&self) -> u8 {
        assert_ne!(self.endpoint_address & 0xF, 0);
        self.endpoint_address & 0xF
    }

    pub fn transfer_type(&self) -> UsbEndpointType {
        match self.attributes & 0x3 {
            0 => UsbEndpointType::Control,
            1 => UsbEndpointType::Isochronous,
            2 => UsbEndpointType::Bulk,
            3 => UsbEndpointType::Interrupt,
            _ => unreachable!(),
        }
    }
}

impl UsbDeviceDescriptor {
    pub fn class(&self) -> UsbDeviceClass {
        UsbDeviceClass::try_from(self.device_class).unwrap_or(UsbDeviceClass::Unknown)
    }

    pub fn protocol(&self) -> UsbDeviceProtocol {
        UsbDeviceProtocol::try_from(self.device_protocol).unwrap_or(UsbDeviceProtocol::Unknown)
    }

    pub fn max_packet_size(&self, version: UsbVersion, speed: UsbSpeed) -> Result<usize, UsbError> {
        match (version.is_version_3(), speed, self.max_packet_size_0) {
            (true, UsbSpeed::Super, 9) => Ok(1 << 9),
            (true, _, _) => todo!("Non-GenX speed USB3+ maxpacketsize0"),
            (false, _, 8) => Ok(8),
            (false, _, 16) => Ok(16),
            (false, _, 32) => Ok(32),
            (false, _, 64) => Ok(64),
            _ => Err(UsbError::InvalidDescriptorField),
        }
    }
}
