use libc::c_int;
use libusb1_sys::constants::*;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Device speeds. Indicates the speed at which a device is operating.
/// - [libusb_supported_speed](http://libusb.sourceforge.net/api-1.0/group__libusb__dev.html#ga1454797ecc0de4d084c1619c420014f6)
/// - [USB release versions](https://en.wikipedia.org/wiki/USB#Release_versions)
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum Speed {
    /// The operating system doesn't know the device speed.
    Unknown,

    /// The device is operating at low speed (1.5 Mbps).
    Low,

    /// The device is operating at full speed (12 Mbps).
    Full,

    /// The device is operating at high speed (480 Mbps).
    High,

    /// The device is operating at super speed (5 Gbps).
    Super,

    /// The device is operating at super speed (10 Gbps).
    SuperPlus,
}

#[doc(hidden)]
pub(crate) fn speed_from_libusb(n: c_int) -> Speed {
    match n {
        LIBUSB_SPEED_SUPER_PLUS => Speed::SuperPlus,
        LIBUSB_SPEED_SUPER => Speed::Super,
        LIBUSB_SPEED_HIGH => Speed::High,
        LIBUSB_SPEED_FULL => Speed::Full,
        LIBUSB_SPEED_LOW => Speed::Low,

        LIBUSB_SPEED_UNKNOWN | _ => Speed::Unknown,
    }
}

/// Transfer and endpoint directions.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Direction {
    /// Direction for read (device to host) transfers.
    In,

    /// Direction for write (host to device) transfers.
    Out,
}

/// An endpoint's transfer type.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TransferType {
    /// Control endpoint.
    Control,

    /// Isochronous endpoint.
    Isochronous,

    /// Bulk endpoint.
    Bulk,

    /// Interrupt endpoint.
    Interrupt,
}

/// Isochronous synchronization mode.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SyncType {
    /// No synchronisation.
    NoSync,

    /// Asynchronous.
    Asynchronous,

    /// Adaptive.
    Adaptive,

    /// Synchronous.
    Synchronous,
}

/// Isochronous usage type.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum UsageType {
    /// Data endpoint.
    Data,

    /// Feedback endpoint.
    Feedback,

    /// Explicit feedback data endpoint.
    FeedbackData,

    /// Reserved.
    Reserved,
}

/// Types of control transfers.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum RequestType {
    /// Requests that are defined by the USB standard.
    Standard,

    /// Requests that are defined by a device class, e.g., HID.
    Class,

    /// Vendor-specific requests.
    Vendor,

    /// Reserved for future use.
    Reserved,
}

/// Recipients of control transfers.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Recipient {
    /// The recipient is a device.
    Device,

    /// The recipient is an interface.
    Interface,

    /// The recipient is an endpoint.
    Endpoint,

    /// Other.
    Other,
}

/// A three-part version consisting of major, minor, and sub minor components.
///
/// This can be used to represent versions of the format `J.M.N`, where `J` is the major version,
/// `M` is the minor version, and `N` is the sub minor version. A version is constructed by
/// providing the fields in the same order to the tuple. For example:
///
/// ```
/// rusb::Version(0, 2, 1);
/// ```
///
/// represents the version 0.2.1.
///
/// The intended use case of `Version` is to extract meaning from the version fields in USB
/// descriptors, such as `bcdUSB` and `bcdDevice` in device descriptors.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Version(pub u8, pub u8, pub u8);

impl Version {
    /// Extracts a version from a binary coded decimal (BCD) field. BCD fields exist in USB
    /// descriptors as 16-bit integers encoding a version as `0xJJMN`, where `JJ` is the major
    /// version, `M` is the minor version, and `N` is the sub minor version. For example, 2.0 is
    /// encoded as `0x0200` and 1.1 is encoded as `0x0110`.
    pub fn from_bcd(mut raw: u16) -> Self {
        let sub_minor: u8 = (raw & 0x000F) as u8;
        raw >>= 4;

        let minor: u8 = (raw & 0x000F) as u8;
        raw >>= 4;

        let mut major: u8 = (raw & 0x000F) as u8;
        raw >>= 4;

        major += (10 * raw) as u8;

        Version(major, minor, sub_minor)
    }

    /// Returns the major version.
    pub fn major(self) -> u8 {
        let Version(major, _, _) = self;
        major
    }

    /// Returns the minor version.
    pub fn minor(self) -> u8 {
        let Version(_, minor, _) = self;
        minor
    }

    /// Returns the sub minor version.
    pub fn sub_minor(self) -> u8 {
        let Version(_, _, sub_minor) = self;
        sub_minor
    }
}

impl std::fmt::Display for Version {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}.{}.{}", self.major(), self.minor(), self.sub_minor())
    }
}

/// Builds a value for the `bmRequestType` field of a control transfer setup packet.
///
/// The `bmRequestType` field of a USB control transfer setup packet is a bit field specifying
/// three parameters, which are given to this function by corresponding enum values.
///
/// ## Examples
///
/// The following example returns a `bmRequestType` value for a standard inbound transfer from the
/// device, which could be used for reading a device's descriptors:
///
/// ```no_run
/// use rusb::{Direction,RequestType,Recipient};
///
/// rusb::request_type(Direction::In, RequestType::Standard, Recipient::Device);
/// ```
pub const fn request_type(
    direction: Direction,
    request_type: RequestType,
    recipient: Recipient,
) -> u8 {
    let mut value: u8 = match direction {
        Direction::Out => LIBUSB_ENDPOINT_OUT,
        Direction::In => LIBUSB_ENDPOINT_IN,
    };

    value |= match request_type {
        RequestType::Standard => LIBUSB_REQUEST_TYPE_STANDARD,
        RequestType::Class => LIBUSB_REQUEST_TYPE_CLASS,
        RequestType::Vendor => LIBUSB_REQUEST_TYPE_VENDOR,
        RequestType::Reserved => LIBUSB_REQUEST_TYPE_RESERVED,
    };

    value |= match recipient {
        Recipient::Device => LIBUSB_RECIPIENT_DEVICE,
        Recipient::Interface => LIBUSB_RECIPIENT_INTERFACE,
        Recipient::Endpoint => LIBUSB_RECIPIENT_ENDPOINT,
        Recipient::Other => LIBUSB_RECIPIENT_OTHER,
    };

    value
}

#[cfg(test)]
mod test {
    use super::*;

    // Version

    #[test]
    fn version_returns_major_version() {
        assert_eq!(1, Version(1, 0, 0).major());
        assert_eq!(2, Version(2, 0, 0).major());
    }

    #[test]
    fn version_returns_minor_version() {
        assert_eq!(1, Version(0, 1, 0).minor());
        assert_eq!(2, Version(0, 2, 0).minor());
    }

    #[test]
    fn version_returns_sub_minor_version() {
        assert_eq!(1, Version(0, 0, 1).sub_minor());
        assert_eq!(2, Version(0, 0, 2).sub_minor());
    }

    #[test]
    fn version_parses_major_version() {
        assert_eq!(3, Version::from_bcd(0x0300).major());
    }

    #[test]
    fn version_parses_long_major_version() {
        assert_eq!(12, Version::from_bcd(0x1200).major());
    }

    #[test]
    fn version_parses_minor_version() {
        assert_eq!(1, Version::from_bcd(0x0010).minor());
        assert_eq!(2, Version::from_bcd(0x0020).minor());
    }

    #[test]
    fn version_parses_sub_minor_version() {
        assert_eq!(1, Version::from_bcd(0x0001).sub_minor());
        assert_eq!(2, Version::from_bcd(0x0002).sub_minor());
    }

    #[test]
    fn version_parses_full_version() {
        assert_eq!(Version(12, 3, 4), Version::from_bcd(0x1234));
    }

    #[test]
    fn version_display() {
        assert_eq!(Version(2, 45, 13).to_string(), "2.45.13");
    }

    #[test]
    fn version_ord() {
        assert!(Version(0, 0, 0) < Version(1, 2, 3));
        assert!(Version(1, 0, 0) < Version(1, 2, 3));
        assert!(Version(1, 2, 0) < Version(1, 2, 3));
        assert!(Version(1, 2, 0) < Version(1, 3, 0));
        assert!(Version(255, 255, 255) > Version(254, 0, 0));
        assert!(Version(0, 255, 0) > Version(0, 254, 255));
    }

    // request_type for direction

    #[test]
    fn request_type_builds_value_for_out_direction() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Standard, Recipient::Device) & 0x80,
            0x00
        );
    }

    #[test]
    fn request_type_builds_value_for_in_direction() {
        assert_eq!(
            request_type(Direction::In, RequestType::Standard, Recipient::Device) & 0x80,
            0x80
        );
    }

    // request_type for request type

    #[test]
    fn request_type_builds_value_for_standard_request() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Standard, Recipient::Device) & 0x60,
            0x00
        );
    }

    #[test]
    fn request_type_builds_value_for_class_request() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Class, Recipient::Device) & 0x60,
            0x20
        );
    }

    #[test]
    fn request_type_builds_value_for_vendor_request() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Vendor, Recipient::Device) & 0x60,
            0x40
        );
    }

    #[test]
    fn request_type_builds_value_for_reserved_request() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Reserved, Recipient::Device) & 0x60,
            0x60
        );
    }

    // request_type for recipient

    #[test]
    fn request_type_builds_value_for_device_recipient() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Standard, Recipient::Device) & 0x0F,
            0x00
        );
    }

    #[test]
    fn request_type_builds_value_for_interface_recipient() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Standard, Recipient::Interface) & 0x0F,
            0x01
        );
    }

    #[test]
    fn request_type_builds_value_for_endpoint_recipient() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Standard, Recipient::Endpoint) & 0x0F,
            0x02
        );
    }

    #[test]
    fn request_type_builds_value_for_other_recipient() {
        assert_eq!(
            request_type(Direction::Out, RequestType::Standard, Recipient::Other) & 0x0F,
            0x03
        );
    }
}
