#ifdef LIBUSBP_DROP_IN
#include "libusbp.h"
#else
#pragma once
#include <libusbp_config.h>
#include <libusbp.h>
#if BUILD_SYSTEM_LIBUSBP_VERSION_MAJOR != LIBUSBP_VERSION_MAJOR
#error Major version in libusbp.h disagrees with build system.
#endif
#endif

// Don't warn about zero-length format strings, which we sometimes use when
// constructing error objects.
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wformat-zero-length"
#endif

// Silence some warnings from the Microsoft C Compiler.
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#define strdup _strdup
#endif

#include <assert.h>
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#ifdef _WIN32
#include <windows.h>
#include <devpropdef.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <usbiodef.h>
#include <usbioctl.h>
#include <stringapiset.h>
#include <winusb.h>
#include <ntddmodm.h>
#endif

#ifdef __linux__
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <libudev.h>
#include <linux/usbdevice_fs.h>
#include <linux/usb/ch9.h>
#include <sys/ioctl.h>
#endif

#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <mach/error.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFBundle.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/serial/ioss.h>
#endif

// For debug builds (NDEBUG undefined), functions and variables declared with
// LIBUSBP_TEST_API will be exported in the library so that they can be unit
// tested, but they are not part of the public API and they might change at any
// time.  Those symbols can cause name collisions with symbols defined by the
// user, so this type of build is not suitable for making a general release.
#ifdef NDEBUG
#define LIBUSBP_TEST_API
#else
#define LIBUSBP_TEST_API LIBUSBP_API
#endif

#ifdef _MSC_VER
#define LIBUSBP_PRINTF(f, a)
#else
#define LIBUSBP_PRINTF(f, a) __attribute__((format(printf, f, a)))
#endif

// Suppresses unused parameter warnings.
#define LIBUSBP_UNUSED(param_name) (void)param_name;

#define MAX_ENDPOINT_NUMBER 15

typedef struct libusbp_setup_packet
{
    uint8_t bmRequestType;
    uint8_t bRequest;
    uint16_t wValue;
    uint16_t wIndex;
    uint16_t wLength;
} libusbp_setup_packet;

LIBUSBP_TEST_API extern libusbp_error error_no_memory;

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error* error_create(
    const char* format, ...) LIBUSBP_PRINTF(1, 2);

LIBUSBP_WARN_UNUSED libusbp_error* error_add_v(
    libusbp_error* error, const char* format, va_list ap);

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error* error_add(
    libusbp_error* error, const char* format, ...) LIBUSBP_PRINTF(2, 3);

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error* error_add_code(
    libusbp_error* error, uint32_t code);

LIBUSBP_WARN_UNUSED libusbp_error* string_copy(
    const char* input_string, char** output_string);

LIBUSBP_WARN_UNUSED
libusbp_error* check_pipe_id(uint8_t pipe_id);

LIBUSBP_WARN_UNUSED
libusbp_error* check_pipe_id_in(uint8_t pipe_id);

LIBUSBP_WARN_UNUSED
libusbp_error* check_pipe_id_out(uint8_t pipe_id);

LIBUSBP_WARN_UNUSED
libusbp_error* device_list_create(libusbp_device*** device_list);

LIBUSBP_WARN_UNUSED
libusbp_error* device_list_append(libusbp_device*** device_list,
    size_t* device_count,
    libusbp_device* device);

void free_devices_and_list(libusbp_device** device_list);

typedef struct async_in_transfer async_in_transfer;

void async_in_transfer_handle_completion(async_in_transfer*);

void async_in_transfer_free(async_in_transfer* transfer);

libusbp_error* async_in_transfer_create(libusbp_generic_handle* handle,
    uint8_t pipe_id,
    size_t transfer_size,
    async_in_transfer** transfer);

LIBUSBP_WARN_UNUSED
libusbp_error* async_in_pipe_create(libusbp_generic_handle* handle,
    uint8_t pipe_id,
    libusbp_async_in_pipe** pipe);

LIBUSBP_WARN_UNUSED
libusbp_error* async_in_pipe_setup(libusbp_generic_handle* gh, uint8_t pipe_id);

void async_in_transfer_submit(async_in_transfer* transfer);

LIBUSBP_WARN_UNUSED
libusbp_error* async_in_transfer_get_results(async_in_transfer* transfer,
    void* buffer,
    size_t* transferred,
    libusbp_error** transfer_error);

LIBUSBP_WARN_UNUSED
libusbp_error* async_in_transfer_cancel(async_in_transfer* transfer);

bool async_in_transfer_pending(async_in_transfer* transfer);

LIBUSBP_WARN_UNUSED
libusbp_error* generic_handle_events(libusbp_generic_handle* handle);

#ifdef _WIN32

LIBUSBP_WARN_UNUSED libusbp_error* create_device(
    HDEVINFO list, PSP_DEVINFO_DATA info, libusbp_device** device);

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(
    1, 2) libusbp_error* error_create_winapi(const char* format, ...);

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(
    1, 2) libusbp_error* error_create_overlapped(const char* format, ...);

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(
    2, 3) libusbp_error* error_create_cr(CONFIGRET, const char* format, ...);

LIBUSBP_WARN_UNUSED libusbp_error* get_interface(const char* device_instance_id,
    uint8_t interface_number,
    bool composite,
    HDEVINFO* list,
    PSP_DEVINFO_DATA info);

LIBUSBP_WARN_UNUSED libusbp_error* create_id_string(
    HDEVINFO list, PSP_DEVINFO_DATA info, char** id);

LIBUSBP_WARN_UNUSED libusbp_error* get_filename_from_devinst_and_guid(
    DEVINST devinst, const GUID* guid, char** filename);

#endif

#ifdef __linux__

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(
    1, 2) libusbp_error* error_create_errno(const char* format, ...);

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3)
    libusbp_error* error_create_udev(int error_code, const char* format, ...);

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error* error_from_urb_status(
    struct usbdevfs_urb* urb);

LIBUSBP_WARN_UNUSED
libusbp_error* device_create(struct udev_device* dev, libusbp_device** device);

LIBUSBP_WARN_UNUSED
libusbp_error* generic_interface_get_device_copy(
    const libusbp_generic_interface* gi, libusbp_device** device);

/** udevw
 * **********************************************************************/

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_create_context(struct udev**);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_create_usb_list(
    struct udev* context, struct udev_enumerate** list);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_device_from_syspath(
    struct udev*, const char* syspath, struct udev_device** dev);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_device_type(struct udev_device*, const char** devtype);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_sysattr_uint8(
    struct udev_device* dev, const char* name, uint8_t* value);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_sysattr_uint16(
    struct udev_device* dev, const char* name, uint16_t* value);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_sysattr_if_exists_copy(
    struct udev_device* dev, const char* name, char** value);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_interface(struct udev* udev,
    const char* device_syspath,
    uint8_t interface_number,
    struct udev_device** device);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_tty(
    struct udev* udev, struct udev_device* parent, struct udev_device** device);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_syspath(
    struct udev_device* device, const char** syspath);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_syspath_copy(
    struct udev_device* device, char** devnode);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_devnode(
    struct udev_device* device, const char** devnode);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_devnode_copy(
    struct udev_device* device, char** devnode);

LIBUSBP_WARN_UNUSED
libusbp_error* udevw_get_devnode_copy_from_syspath(
    const char* syspath, char** devnode);

/** usbfd
 * **********************************************************************/

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED libusbp_error* usbfd_check_existence(
    const char* filename);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_open(const char* path, int* fd);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_get_device_descriptor(
    int fd, struct usb_device_descriptor* desc);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_control_transfer(int fd,
    libusbp_setup_packet setup,
    uint32_t timeout,
    void* data,
    size_t* transferred);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_control_transfer_async(int fd,
    void* combined_buffer,
    size_t size,
    uint32_t timeout,
    void* user_context);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_bulk_or_interrupt_transfer(int fd,
    uint8_t pipe,
    uint32_t timeout,
    void* buffer,
    size_t size,
    size_t* transferred);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_submit_urb(int fd, struct usbdevfs_urb* urb);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_reap_urb(int fd, struct usbdevfs_urb** urb);

LIBUSBP_WARN_UNUSED
libusbp_error* usbfd_discard_urb(int fd, struct usbdevfs_urb* urb);

#endif

#ifdef __APPLE__

LIBUSBP_TEST_API
    LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(2, 3) libusbp_error* error_create_mach(
        kern_return_t error_code, const char* format, ...);

LIBUSBP_WARN_UNUSED
libusbp_error* create_device(io_service_t service, libusbp_device** device);

uint64_t device_get_id(const libusbp_device* device);

bool generic_interface_get_interface_id(
    const libusbp_generic_interface* gi, uint64_t* id);
uint64_t generic_interface_get_device_id(const libusbp_generic_interface* gi);
uint8_t generic_interface_get_interface_number(
    const libusbp_generic_interface* gi);

IOUSBInterfaceInterface182** generic_handle_get_ioh(
    const libusbp_generic_handle* handle);

uint8_t generic_handle_get_pipe_index(
    const libusbp_generic_handle* handle, uint8_t pipe_id);

LIBUSBP_WARN_UNUSED
libusbp_error* iokit_id_to_string(uint64_t id, char** str);

LIBUSBP_WARN_UNUSED
libusbp_error* service_get_from_id(uint64_t id, io_service_t*);

LIBUSBP_WARN_UNUSED
libusbp_error* service_get_usb_interface(io_service_t service,
    uint8_t interface_number,
    io_service_t* interface_service);

LIBUSBP_WARN_UNUSED
libusbp_error* service_get_child_by_class(io_service_t service,
    const char* class_name,
    io_service_t* interface_service);

LIBUSBP_WARN_UNUSED
libusbp_error* service_to_interface(io_service_t,
    CFUUIDRef pluginType,
    REFIID rid,
    void**,
    IOCFPlugInInterface***);

LIBUSBP_WARN_UNUSED
libusbp_error* get_id(io_registry_entry_t entry, uint64_t* id);

LIBUSBP_WARN_UNUSED
libusbp_error* get_string(
    io_registry_entry_t entry, CFStringRef name, char** value);

LIBUSBP_WARN_UNUSED
libusbp_error* get_int32(
    io_registry_entry_t entry, CFStringRef name, int32_t* value);

LIBUSBP_WARN_UNUSED
libusbp_error* get_uint16(
    io_registry_entry_t entry, CFStringRef name, uint16_t* value);

#endif

#if defined(_WIN32) || defined(__APPLE__)

LIBUSBP_TEST_API LIBUSBP_WARN_UNUSED LIBUSBP_PRINTF(
    2, 3) libusbp_error* error_create_hr(HRESULT, const char* format, ...);

#endif
