use std::{fmt, slice};

use libusb1_sys::*;

use crate::interface_descriptor::{self, Interface};

/// Describes a configuration.
pub struct ConfigDescriptor {
    descriptor: *const libusb_config_descriptor,
}

impl Drop for ConfigDescriptor {
    fn drop(&mut self) {
        unsafe {
            libusb_free_config_descriptor(self.descriptor);
        }
    }
}

unsafe impl Sync for ConfigDescriptor {}
unsafe impl Send for ConfigDescriptor {}

impl ConfigDescriptor {
    /// Returns the size of the descriptor in bytes
    pub fn length(&self) -> u8 {
        unsafe { (*self.descriptor).bLength }
    }

    /// Returns the total length in bytes of data returned for this configuration: all interfaces and endpoints
    pub fn total_length(&self) -> u16 {
        unsafe { (*self.descriptor).wTotalLength }
    }

    /// Returns the descriptor type
    pub fn descriptor_type(&self) -> u8 {
        unsafe { (*self.descriptor).bDescriptorType }
    }

    /// Returns the configuration number.
    pub fn number(&self) -> u8 {
        unsafe { (*self.descriptor).bConfigurationValue }
    }

    /// Returns the device's maximum power consumption (in milliamps) in this configuration.
    pub fn max_power(&self) -> u16 {
        unsafe { u16::from((*self.descriptor).bMaxPower) * 2 }
    }

    /// Indicates if the device is self-powered in this configuration.
    pub fn self_powered(&self) -> bool {
        unsafe { (*self.descriptor).bmAttributes & 0x40 != 0 }
    }

    /// Indicates if the device has remote wakeup capability in this configuration.
    pub fn remote_wakeup(&self) -> bool {
        unsafe { (*self.descriptor).bmAttributes & 0x20 != 0 }
    }

    /// Returns the index of the string descriptor that describes the configuration.
    pub fn description_string_index(&self) -> Option<u8> {
        unsafe {
            match (*self.descriptor).iConfiguration {
                0 => None,
                n => Some(n),
            }
        }
    }

    /// Returns the number of interfaces for this configuration.
    pub fn num_interfaces(&self) -> u8 {
        unsafe { (*self.descriptor).bNumInterfaces }
    }

    /// Returns a collection of the configuration's interfaces.
    pub fn interfaces(&self) -> Interfaces {
        let interfaces = unsafe {
            slice::from_raw_parts(
                (*self.descriptor).interface,
                (*self.descriptor).bNumInterfaces as usize,
            )
        };

        Interfaces {
            iter: interfaces.iter(),
        }
    }

    /// Returns the unknown 'extra' bytes that libusb does not understand.
    pub fn extra(&self) -> &[u8] {
        unsafe {
            match (*self.descriptor).extra_length {
                len if len > 0 => slice::from_raw_parts((*self.descriptor).extra, len as usize),
                _ => &[],
            }
        }
    }
}

impl fmt::Debug for ConfigDescriptor {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        let mut debug = fmt.debug_struct("ConfigDescriptor");

        let descriptor: &libusb_config_descriptor = unsafe { &*self.descriptor };

        debug.field("bLength", &descriptor.bLength);
        debug.field("bDescriptorType", &descriptor.bDescriptorType);
        debug.field("wTotalLength", &descriptor.wTotalLength);
        debug.field("bNumInterfaces", &descriptor.bNumInterfaces);
        debug.field("bConfigurationValue", &descriptor.bConfigurationValue);
        debug.field("iConfiguration", &descriptor.iConfiguration);
        debug.field("bmAttributes", &descriptor.bmAttributes);
        debug.field("bMaxPower", &descriptor.bMaxPower);
        debug.field("extra", &self.extra());

        debug.finish()
    }
}

/// Iterator over a configuration's interfaces.
pub struct Interfaces<'a> {
    iter: slice::Iter<'a, libusb_interface>,
}

impl<'a> Iterator for Interfaces<'a> {
    type Item = Interface<'a>;

    fn next(&mut self) -> Option<Interface<'a>> {
        self.iter
            .next()
            .map(|interface| unsafe { interface_descriptor::from_libusb(interface) })
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        self.iter.size_hint()
    }
}

#[doc(hidden)]
pub(crate) unsafe fn from_libusb(config: *const libusb_config_descriptor) -> ConfigDescriptor {
    ConfigDescriptor { descriptor: config }
}

#[cfg(test)]
mod test {
    use std::mem::ManuallyDrop;

    // The Drop trait impl calls libusb_free_config_descriptor(), which would attempt to free
    // unallocated memory for a stack-allocated config descriptor. Allocating a config descriptor
    // is not a simple malloc()/free() inside libusb. Mimicking libusb's allocation would be
    // error-prone, difficult to maintain, and provide little benefit for the tests. It's easier to
    // use mem::forget() to prevent the Drop trait impl from running. The config descriptor passed
    // as `$config` should be stack-allocated to prevent memory leaks in the test suite.
    macro_rules! with_config {
        ($name:ident : $config:expr => $body:block) => {{
            let config = $config;
            let $name = ManuallyDrop::new(unsafe { super::from_libusb(&config) });
            $body;
        }};
    }

    #[test]
    fn it_has_number() {
        with_config!(config: config_descriptor!(bConfigurationValue: 42) => {
            assert_eq!(42, config.number());
        });
    }

    #[test]
    fn it_has_max_power() {
        with_config!(config: config_descriptor!(bMaxPower: 21) => {
            assert_eq!(42, config.max_power());
        });
    }

    #[test]
    fn it_interprets_self_powered_bit_in_attributes() {
        with_config!(config: config_descriptor!(bmAttributes: 0b0000_0000) => {
            assert_eq!(false, config.self_powered());
        });

        with_config!(config: config_descriptor!(bmAttributes: 0b0100_0000) => {
            assert_eq!(true, config.self_powered());
        });
    }

    #[test]
    fn it_interprets_remote_wakeup_bit_in_attributes() {
        with_config!(config: config_descriptor!(bmAttributes: 0b0000_0000) => {
            assert_eq!(false, config.remote_wakeup());
        });

        with_config!(config: config_descriptor!(bmAttributes: 0b0010_0000) => {
            assert_eq!(true, config.remote_wakeup());
        });
    }

    #[test]
    fn it_has_description_string_index() {
        with_config!(config: config_descriptor!(iConfiguration: 42) => {
            assert_eq!(Some(42), config.description_string_index());
        });
    }

    #[test]
    fn it_handles_missing_description_string_index() {
        with_config!(config: config_descriptor!(iConfiguration: 0) => {
            assert_eq!(None, config.description_string_index());
        });
    }

    #[test]
    fn it_has_num_interfaces() {
        let interface1 = interface!(interface_descriptor!(bInterfaceNumber: 1));
        let interface2 = interface!(interface_descriptor!(bInterfaceNumber: 2));

        with_config!(config: config_descriptor!(interface1, interface2) => {
            assert_eq!(2, config.num_interfaces());
        });
    }

    #[test]
    fn it_has_interfaces() {
        let interface = interface!(interface_descriptor!(bInterfaceNumber: 1));

        with_config!(config: config_descriptor!(interface) => {
            let interface_numbers = config.interfaces().map(|interface| {
                interface.number()
            }).collect::<Vec<_>>();

            assert_eq!(vec![1], interface_numbers);
        });
    }

    // Successful compilation shows that the lifetime of the endpoint descriptor(s) is the same
    // as the lifetime of the config descriptor.
    #[test]
    fn it_had_interfaces_with_endpoints() {
        let endpoint1 = endpoint_descriptor!(bEndpointAddress: 0x81);
        let endpoint2 = endpoint_descriptor!(bEndpointAddress: 0x01);
        let endpoint3 = endpoint_descriptor!(bEndpointAddress: 0x02);
        let interface1 = interface!(interface_descriptor!(endpoint1, endpoint2));
        let interface2 = interface!(interface_descriptor!(endpoint3));

        with_config!(config: config_descriptor!(interface1, interface2) => {
            // Exists only to name config's lifetime.
            fn named_lifetime<'a>(config: &'a super::ConfigDescriptor) {
                let addresses: Vec<_> = config.interfaces().flat_map(|intf| intf.descriptors()).flat_map(|desc| desc.endpoint_descriptors()).map(|ep| ep.address()).collect();
                assert_eq!(addresses, &[0x81, 0x01, 0x02]);
                let desc: crate::InterfaceDescriptor<'a> = config.interfaces().flat_map(|intf| intf.descriptors()).next().expect("There's one interface");
                let _: crate::EndpointDescriptor<'a> = desc.endpoint_descriptors().next().expect("There's one endpoint");
            }
            named_lifetime(&*config);
        })
    }
}
