pub use std::ptr;

macro_rules! merge {
    ($default:expr => $($field:ident : $value:expr),*) => {
        {
            let mut x = $default;
            $( x.$field = $value; )*

            x
        }
    }
}

#[macro_export]
macro_rules! endpoint_descriptor {
    ($($key:ident : $value:expr),*) => {
        merge!(
            libusb1_sys::libusb_endpoint_descriptor {
                bLength:          7,
                bDescriptorType:  0x05,
                bEndpointAddress: 0x00,
                bmAttributes:     0x00,
                wMaxPacketSize:   16,
                bInterval:        1,
                bRefresh:         1,
                bSynchAddress:    0,
                extra:            $crate::test_helpers::ptr::null(),
                extra_length:     0
            } => $($key: $value),*
        )
    }
}

#[macro_export]
macro_rules! interface_descriptor {
    ($($key:ident : $value:expr),*) => {
        merge!(
            libusb1_sys::libusb_interface_descriptor {
                bLength:            9,
                bDescriptorType:    0x04,
                bInterfaceNumber:   0,
                bAlternateSetting:  0,
                bNumEndpoints:      0,
                bInterfaceClass:    0,
                bInterfaceSubClass: 0,
                bInterfaceProtocol: 0,
                iInterface:         0,
                endpoint:           $crate::test_helpers::ptr::null(),
                extra:              $crate::test_helpers::ptr::null(),
                extra_length:       0
            } => $($key: $value),*
        )
    };
    ($($endpoint:expr),+) => {
        {
            let endpoints = vec![$($endpoint),+];

            let r = libusb1_sys::libusb_interface_descriptor {
                bLength:            9,
                bDescriptorType:    0x04,
                bInterfaceNumber:   0,
                bAlternateSetting:  0,
                bNumEndpoints:      endpoints.len() as u8,
                bInterfaceClass:    0,
                bInterfaceSubClass: 0,
                bInterfaceProtocol: 0,
                iInterface:         0,
                endpoint:           (&endpoints[..]).as_ptr(),
                extra:              $crate::test_helpers::ptr::null(),
                extra_length:       0
            };

            // leak the Vec so the returned pointer remains valid
            ::std::mem::forget(endpoints);
            r
        }
    }
}

#[macro_export]
macro_rules! interface {
    ($($descriptor:expr),*) => {
        {
            let descriptors = vec![$($descriptor),*];

            let r = libusb1_sys::libusb_interface {
                altsetting:     descriptors.as_ptr(),
                num_altsetting: descriptors.len() as ::libc::c_int
            };

            // leak the Vec so the returned pointer remains valid
            ::std::mem::forget(descriptors);
            r
        }
    }
}

#[macro_export]
macro_rules! config_descriptor {
    ($($key:ident : $value:expr),*) => {
        merge!(
            libusb1_sys::libusb_config_descriptor {
                bLength:             9,
                bDescriptorType:     0x02,
                wTotalLength:        9,
                bNumInterfaces:      0,
                bConfigurationValue: 0,
                iConfiguration:      0,
                bmAttributes:        0x00,
                bMaxPower:           10,
                interface:           $crate::test_helpers::ptr::null(),
                extra:               $crate::test_helpers::ptr::null(),
                extra_length:        0
            } => $($key: $value),*
        )
    };
    ($($interface:expr),+) => {
        {
            let interfaces = vec![$($interface),+];

            let r = libusb1_sys::libusb_config_descriptor {
                bLength:             9,
                bDescriptorType:     0x02,
                wTotalLength:        9,
                bNumInterfaces:      interfaces.len() as u8,
                bConfigurationValue: 0,
                iConfiguration:      0,
                bmAttributes:        0x00,
                bMaxPower:           10,
                interface:           (&interfaces[..]).as_ptr(),
                extra:               $crate::test_helpers::ptr::null(),
                extra_length:        0
            };

            // leak the Vec so the returned pointer remains valid
            ::std::mem::forget(interfaces);
            r
        }
    }
}

#[macro_export]
macro_rules! device_descriptor {
    ($($key:ident : $value:expr),*) => {
        merge!(
            libusb1_sys::libusb_device_descriptor {
                bLength:            18,
                bDescriptorType:    0x01,
                bcdUSB:             0x0110,
                bDeviceClass:       0,
                bDeviceSubClass:    0,
                bDeviceProtocol:    0,
                bMaxPacketSize0:    16,
                idVendor:           0x1234,
                idProduct:          0x5678,
                bcdDevice:          0x0123,
                iManufacturer:      0,
                iProduct:           0,
                iSerialNumber:      0,
                bNumConfigurations: 1
            } => $($key: $value),*
        )
    }
}
