use core::fmt;

use alloc::{string::String, vec::Vec};
use yggdrasil_abi::primitive_enum;

use crate::communication::UsbDirection;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UsbEndpointType {
    Control,
    Isochronous,
    Bulk,
    Interrupt,
}

#[derive(Debug, Clone, Copy)]
pub enum UsbSyncType {
    NoSync,
    Async,
    Adaptive,
    Sync,
}

#[derive(Debug)]
pub enum UsbUsageType {
    Data,
    Feedback,
    ImplicitFeedbackData,
    Reserved,
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum UsbVersion {
    Usb11,
    Usb20,
    Usb21,
    Usb30,
    Usb31,
    Usb32,
}

primitive_enum! {
    pub enum UsbDeviceClass: u8 {
        FromInterface = 0x00,
        Hid = 0x03,
        MassStorage = 0x08,
        Unknown = 0xFF,
    }
}

primitive_enum! {
    pub enum UsbDeviceProtocol: u8 {
        FromInterface = 0x00,
        Unknown = 0xFF,
    }
}

#[derive(Debug, Clone)]
pub struct UsbInterfaceInfo {
    pub name: String,
    pub number: u8,

    pub interface_class: UsbDeviceClass,
    pub interface_subclass: u8,
    pub interface_protocol: UsbDeviceProtocol,
    pub interface_protocol_number: u8,
}

#[derive(Debug, Clone)]
pub struct UsbEndpointInfo {
    pub number: u8,
    pub direction: UsbDirection,
    pub max_packet_size: usize,
    pub ty: UsbEndpointType,
}

#[derive(Debug, Clone)]
pub struct UsbConfigurationInfo {
    pub name: String,
    pub config_value: u8,
    pub interfaces: Vec<UsbInterfaceInfo>,
    pub endpoints: Vec<UsbEndpointInfo>,
}

#[derive(Debug, Clone)]
pub struct UsbDeviceInfo {
    pub manufacturer: String,
    pub product: String,

    pub usb_version: UsbVersion,

    pub id_vendor: u16,
    pub id_product: u16,

    pub device_class: UsbDeviceClass,
    pub device_subclass: u8,
    pub device_protocol: UsbDeviceProtocol,
    pub device_protocol_number: u8,

    /// Max packet size for endpoint zero
    pub max_packet_size: usize,

    pub num_configurations: u8,
}

impl UsbVersion {
    pub fn is_version_3(&self) -> bool {
        matches!(self, Self::Usb30 | Self::Usb31 | Self::Usb32)
    }

    pub fn from_bcd_usb(value: u16) -> Option<Self> {
        match value {
            0x110 => Some(UsbVersion::Usb11),
            0x200..=0x20F => Some(UsbVersion::Usb20),
            0x210..=0x21F => Some(UsbVersion::Usb21),
            0x300 => Some(UsbVersion::Usb30),
            0x310 => Some(UsbVersion::Usb31),
            0x320 => Some(UsbVersion::Usb32),
            _ => None,
        }
    }
}

impl fmt::Display for UsbVersion {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let string = match self {
            Self::Usb11 => "USB1.1",
            Self::Usb20 => "USB2.0",
            Self::Usb21 => "USB2.1",
            Self::Usb30 => "USB3.0",
            Self::Usb31 => "USB3.1",
            Self::Usb32 => "USB3.2",
        };
        f.write_str(string)
    }
}

impl UsbEndpointInfo {
    pub fn is(&self, ty: UsbEndpointType, dir: UsbDirection) -> bool {
        self.ty == ty && self.direction == dir
    }
}

impl UsbConfigurationInfo {
    pub fn find_endpoint<P: Fn(&UsbEndpointInfo) -> bool>(
        &self,
        predicate: P,
    ) -> Option<(u8, &UsbEndpointInfo)> {
        let index = self.endpoints.iter().position(predicate)?;
        let info = &self.endpoints[index];
        Some((index as u8 + 1, info))
    }
}
