use libusb1_sys as ffi;

#[test]
fn test_version() {
    use std::{ffi::CStr, str};
    let version = unsafe { &*ffi::libusb_get_version() };
    let rc = str::from_utf8(unsafe { CStr::from_ptr(version.rc) }.to_bytes()).unwrap_or("");
    let describe =
        str::from_utf8(unsafe { CStr::from_ptr(version.describe) }.to_bytes()).unwrap_or("");

    assert_eq!(version.major, 1);
    assert_eq!(version.minor, 0);
    println!(
        "libusb v{}.{}.{}.{}{} {}",
        version.major, version.minor, version.micro, version.nano, rc, describe
    );
}

#[test]
fn test_init_and_exit() {
    let mut context: *mut ffi::libusb_context = std::ptr::null_mut();
    for i in 0..=100 {
        match unsafe { ffi::libusb_init(&mut context) } {
            0 => (),
            err => panic!("Failed to init libusb on iteration {}: {}", i, err),
        }
        unsafe {
            ffi::libusb_exit(context);
        }
        #[cfg(target_os = "macos")]
        std::thread::sleep(std::time::Duration::from_millis(1));
        context = std::ptr::null_mut();
    }
}

#[test]
fn test_get_device_list() {
    let mut context = std::mem::MaybeUninit::<*mut ffi::libusb_context>::uninit();
    match unsafe { ffi::libusb_init(context.as_mut_ptr()) } {
        0 => (),
        err => panic!("Failed to init libusb {}", err),
    }
    let mut list = std::mem::MaybeUninit::<*const *mut ffi::libusb_device>::uninit();
    let list_size =
        unsafe { ffi::libusb_get_device_list(context.assume_init(), list.as_mut_ptr()) };
    if list_size < 0 || unsafe { list.assume_init().is_null() } {
        panic!("Failed to get device list {} {:p}", -list_size, unsafe {
            list.assume_init()
        });
    }
    unsafe {
        ffi::libusb_free_device_list(list.assume_init(), 1);
    }
    unsafe {
        ffi::libusb_exit(context.assume_init());
    }
}

#[test]
fn test_fill_control_setup() {
    let mut buf = [0u8; ffi::constants::LIBUSB_CONTROL_SETUP_SIZE + 1];
    unsafe {
        ffi::libusb_fill_control_setup(
            buf.as_mut_ptr(),
            ffi::constants::LIBUSB_REQUEST_TYPE_VENDOR | ffi::constants::LIBUSB_ENDPOINT_OUT,
            0x04,
            0x4e,
            0,
            (buf.len() - ffi::constants::LIBUSB_CONTROL_SETUP_SIZE) as u16,
        );
    }
    buf[ffi::constants::LIBUSB_CONTROL_SETUP_SIZE] = 0x01;
    let setup: *mut ffi::libusb_control_setup = buf.as_mut_ptr() as *mut _;

    assert_eq!(
        unsafe { (*setup).bmRequestType },
        ffi::constants::LIBUSB_REQUEST_TYPE_VENDOR | ffi::constants::LIBUSB_ENDPOINT_OUT
    );
    assert_eq!(unsafe { (*setup).bRequest }, 0x04);
    assert_eq!(unsafe { u16::from_le((*setup).wValue) }, 0x4e);
    assert_eq!(unsafe { u16::from_le((*setup).wIndex) }, 0);
    assert_eq!(unsafe { u16::from_le((*setup).wLength) }, 1);
}

#[test]
fn test_fill_control_transfer() {
    extern "system" fn callback(_transfer: *mut ffi::libusb_transfer) {}

    let mut buf = [0u8; ffi::constants::LIBUSB_CONTROL_SETUP_SIZE + 1];
    unsafe {
        ffi::libusb_fill_control_setup(
            buf.as_mut_ptr(),
            ffi::constants::LIBUSB_REQUEST_TYPE_VENDOR | ffi::constants::LIBUSB_ENDPOINT_OUT,
            0x04,
            0x4e,
            0,
            (buf.len() - ffi::constants::LIBUSB_CONTROL_SETUP_SIZE) as u16,
        );
    }
    buf[ffi::constants::LIBUSB_CONTROL_SETUP_SIZE] = 0x05;

    let mut transfer = std::mem::MaybeUninit::<ffi::libusb_transfer>::uninit();

    unsafe {
        ffi::libusb_fill_control_transfer(
            transfer.as_mut_ptr(),
            std::ptr::null_mut(),
            buf.as_mut_ptr(),
            callback,
            std::ptr::null_mut(),
            1000,
        );
    }
    let transfer = unsafe { &mut transfer.assume_init() };
    assert_eq!(transfer.endpoint, 0);
    assert_eq!(
        transfer.length as usize,
        ffi::constants::LIBUSB_CONTROL_SETUP_SIZE + 1
    );
    assert_eq!(transfer.timeout, 1000);
    assert_eq!(
        transfer.transfer_type,
        ffi::constants::LIBUSB_TRANSFER_TYPE_CONTROL
    );
    assert_eq!(transfer.buffer, buf.as_mut_ptr());

    let data = unsafe {
        std::slice::from_raw_parts(ffi::libusb_control_transfer_get_data(transfer as *mut _), 1)
    };
    assert_eq!(data[0], 0x05);
}

#[test]
fn test_fill_bulk_transfer() {
    extern "system" fn callback(_transfer: *mut ffi::libusb_transfer) {}

    let mut transfer = std::mem::MaybeUninit::<ffi::libusb_transfer>::uninit();
    let mut buf = [5u8; 64];
    unsafe {
        ffi::libusb_fill_bulk_transfer(
            transfer.as_mut_ptr(),
            std::ptr::null_mut(),
            0x80,
            buf.as_mut_ptr(),
            buf.len() as libc::c_int,
            callback,
            std::ptr::null_mut(),
            1000,
        );
    }
    let transfer = unsafe { &transfer.assume_init() };
    assert_eq!(transfer.endpoint, 0x80);
    assert_eq!(transfer.timeout, 1000);
    assert_eq!(
        transfer.transfer_type,
        ffi::constants::LIBUSB_TRANSFER_TYPE_BULK
    );
    assert_eq!(transfer.buffer, buf.as_mut_ptr());
    assert_eq!(transfer.length, buf.len() as libc::c_int);
}
