use core::{fmt, num::NonZeroU8};

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

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,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PortString(u64);

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PortStringIter(u64);

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

    pub interface_class: u8,
    pub interface_subclass: u8,
    pub interface_protocol: 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: Option<String>,
    pub config_value: u8,
    pub interfaces: Vec<UsbInterfaceInfo>,
    pub endpoints: Vec<UsbEndpointInfo>,
}

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))
    }
}

impl PortString {
    pub const fn new_root_port(root_hub_port_number: NonZeroU8) -> Self {
        Self(root_hub_port_number.get() as u64)
    }

    pub const fn root_hub_port_number(&self) -> NonZeroU8 {
        unsafe { NonZeroU8::new_unchecked((self.0 & 0xF) as u8) }
    }

    pub fn append(mut self, port: NonZeroU8) -> Self {
        for i in 0..16 {
            let pos = i * 4;
            if (self.0 >> pos) & 0xF == 0 {
                self.0 |= (port.get() as u64) << pos;
                return self;
            }
        }
        panic!("Port route string too long");
    }

    pub fn parent_hub_port_number(&self) -> Option<NonZeroU8> {
        self.iter().last()
    }

    pub const fn raw(&self) -> u64 {
        self.0
    }

    pub fn iter(&self) -> PortStringIter {
        PortStringIter(self.0)
    }
}

impl fmt::Display for PortString {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<port ")?;
        for (i, port) in self.iter().enumerate() {
            if i != 0 {
                write!(f, ".")?;
            }
            write!(f, "{port}")?;
        }
        write!(f, ">")
    }
}

impl Iterator for PortStringIter {
    type Item = NonZeroU8;

    fn next(&mut self) -> Option<Self::Item> {
        let port = NonZeroU8::new((self.0 & 0xF) as u8)?;
        self.0 >>= 4;
        Some(port)
    }
}
