package libusb

import "core:c"

// =============================================================================
// Foreign import — platform-selected linking
// =============================================================================

// Set to false to statically link libusb on Windows.
// On Linux/macOS, the system linker decides static vs dynamic based on what's
// available; use pkg-config or linker flags to control it externally.
LIBUSB_SHARED :: #config(LIBUSB_SHARED, true)

when ODIN_OS == .Windows {
	when ODIN_ARCH == .amd64 {
		when LIBUSB_SHARED {
			foreign import lib "windows-x64/libusb-1.0.lib"
		} else {
			foreign import lib {
				"windows-x64/libusb-1.0-static.lib",
				"system:Advapi32.lib",
				"system:Ole32.lib",
				"system:Setupapi.lib",
			}
		}
	} else when ODIN_ARCH == .i386 {
		when LIBUSB_SHARED {
			foreign import lib "windows-x86/libusb-1.0.lib"
		} else {
			foreign import lib {
				"windows-x86/libusb-1.0-static.lib",
				"system:Advapi32.lib",
				"system:Ole32.lib",
				"system:Setupapi.lib",
			}
		}
	} else {
		#panic("Unsupported Windows architecture for libusb. Only amd64 and i386 are supported.")
	}
} else {
	// Linux, macOS, BSD — link against system-installed libusb.
	// Install via: apt install libusb-1.0-0-dev / brew install libusb / etc.
	foreign import lib "system:usb-1.0"
}

// libusb API version (1.0.30).
API_VERSION :: 0x0100010C

// Descriptor sizes per descriptor type.
DT_DEVICE_SIZE :: 18
DT_CONFIG_SIZE :: 9
DT_INTERFACE_SIZE :: 9
DT_INTERFACE_ASSOCIATION_SIZE :: 8
DT_ENDPOINT_SIZE :: 7
DT_ENDPOINT_AUDIO_SIZE :: 9
DT_HUB_NONVAR_SIZE :: 7
DT_SS_ENDPOINT_COMPANION_SIZE :: 6
DT_BOS_SIZE :: 5
DT_DEVICE_CAPABILITY_SIZE :: 3

// BOS descriptor sizes.
BT_USB_2_0_EXTENSION_SIZE :: 7
BT_SS_USB_DEVICE_CAPABILITY_SIZE :: 10
BT_SSPLUS_USB_DEVICE_CAPABILITY_SIZE :: 12
BT_CONTAINER_ID_SIZE :: 20
BT_PLATFORM_DESCRIPTOR_MIN_SIZE :: 20

// Maximum BOS descriptor size (sum of all sub-descriptors).
DT_BOS_MAX_SIZE ::
	DT_BOS_SIZE + BT_USB_2_0_EXTENSION_SIZE + BT_SS_USB_DEVICE_CAPABILITY_SIZE + BT_CONTAINER_ID_SIZE

// Endpoint address / direction masks.
ENDPOINT_ADDRESS_MASK :: 0x0f
ENDPOINT_DIR_MASK :: 0x80

// Transfer type mask in bmAttributes.
TRANSFER_TYPE_MASK :: 0x03

// Iso sync type mask in bmAttributes.
ISO_SYNC_TYPE_MASK :: 0x0c

// Iso usage type mask in bmAttributes.
ISO_USAGE_TYPE_MASK :: 0x30

// Control setup packet size (8 bytes).
CONTROL_SETUP_SIZE :: 8

// Total number of error codes in the Error enum.
ERROR_COUNT :: 14

// Hotplug convenience constants.
HOTPLUG_NO_FLAGS :: Hotplug_Flags{}
HOTPLUG_MATCH_ANY :: c.int(-1)

// Maximum length for a device string descriptor in UTF-8 (libusb 1.0.30+).
DEVICE_STRING_BYTES_MAX :: 384

// Poll event constants matching <poll.h> values.
POLLIN :: c.short(0x0001)
POLLPRI :: c.short(0x0002)
POLLOUT :: c.short(0x0004)
POLLERR :: c.short(0x0008)
POLLHUP :: c.short(0x0010)

// Device and/or Interface Class codes.
Class_Code :: enum c.int {
	PER_INTERFACE       = 0x00,
	AUDIO               = 0x01,
	COMM                = 0x02,
	HID                 = 0x03,
	PHYSICAL            = 0x05,
	IMAGE               = 0x06,
	PRINTER             = 0x07,
	MASS_STORAGE        = 0x08,
	HUB                 = 0x09,
	DATA                = 0x0a,
	SMART_CARD          = 0x0b,
	CONTENT_SECURITY    = 0x0d,
	VIDEO               = 0x0e,
	PERSONAL_HEALTHCARE = 0x0f,
	AUDIO_VIDEO         = 0x10,
	BILLBOARD           = 0x11,
	TYPE_C_BRIDGE       = 0x12,
	BULK_DISPLAY        = 0x13,
	MCTP                = 0x14,
	I3C                 = 0x3c,
	DIAGNOSTIC_DEVICE   = 0xdc,
	WIRELESS            = 0xe0,
	MISCELLANEOUS       = 0xef,
	APPLICATION         = 0xfe,
	VENDOR_SPEC         = 0xff,
}

// Legacy alias from libusb-0.1.
CLASS_PTP :: Class_Code.IMAGE

// Descriptor types as defined by the USB specification.
Descriptor_Type :: enum c.int {
	DEVICE                = 0x01,
	CONFIG                = 0x02,
	STRING                = 0x03,
	INTERFACE             = 0x04,
	ENDPOINT              = 0x05,
	INTERFACE_ASSOCIATION = 0x0b,
	BOS                   = 0x0f,
	DEVICE_CAPABILITY     = 0x10,
	HID                   = 0x21,
	REPORT                = 0x22,
	PHYSICAL              = 0x23,
	HUB                   = 0x29,
	SUPERSPEED_HUB        = 0x2a,
	SS_ENDPOINT_COMPANION = 0x30,
}

// Endpoint direction. Values for bit 7 of the endpoint address.
Endpoint_Direction :: enum c.int {
	OUT = 0x00,
	IN  = 0x80,
}

// Endpoint transfer type. Values for bits 0:1 of bmAttributes.
Endpoint_Transfer_Type :: enum c.int {
	CONTROL     = 0x0,
	ISOCHRONOUS = 0x1,
	BULK        = 0x2,
	INTERRUPT   = 0x3,
}

// Standard requests, as defined in table 9-5 of the USB 3.0 specifications.
Standard_Request :: enum c.int {
	GET_STATUS        = 0x00,
	CLEAR_FEATURE     = 0x01,
	SET_FEATURE       = 0x03,
	SET_ADDRESS       = 0x05,
	GET_DESCRIPTOR    = 0x06,
	SET_DESCRIPTOR    = 0x07,
	GET_CONFIGURATION = 0x08,
	SET_CONFIGURATION = 0x09,
	GET_INTERFACE     = 0x0a,
	SET_INTERFACE     = 0x0b,
	SYNCH_FRAME       = 0x0c,
	SET_SEL           = 0x30,
	SET_ISOCH_DELAY   = 0x31,
}

// Request type bits of the bmRequestType field in control transfers.
Request_Type :: enum c.int {
	STANDARD = 0x00 << 5,
	CLASS    = 0x01 << 5,
	VENDOR   = 0x02 << 5,
	RESERVED = 0x03 << 5,
}

// Recipient bits of the bmRequestType field in control transfers.
// Values 4 through 31 are reserved.
Request_Recipient :: enum c.int {
	DEVICE    = 0x00,
	INTERFACE = 0x01,
	ENDPOINT  = 0x02,
	OTHER     = 0x03,
}

// Synchronization type for isochronous endpoints.
// Values for bits 2:3 of bmAttributes.
Iso_Sync_Type :: enum c.int {
	NONE     = 0x0,
	ASYNC    = 0x1,
	ADAPTIVE = 0x2,
	SYNC     = 0x3,
}

// Usage type for isochronous endpoints.
// Values for bits 4:5 of bmAttributes.
Iso_Usage_Type :: enum c.int {
	DATA     = 0x0,
	FEEDBACK = 0x1,
	IMPLICIT = 0x2,
}

// Supported speeds (wSpeedSupported) — bitmask flags.
Supported_Speed_Bit :: enum u16 {
	LOW   = 0,
	FULL  = 1,
	HIGH  = 2,
	SUPER = 3,
}
Supported_Speeds :: bit_set[Supported_Speed_Bit;u16]

// USB 2.0 Extension bmAttributes flags.
Usb2_Ext_Attribute_Bit :: enum u32 {
	LPM_SUPPORT = 1,
}
Usb2_Ext_Attributes :: bit_set[Usb2_Ext_Attribute_Bit;u32]

// SuperSpeed USB Device Capability bmAttributes flags.
Ss_Dev_Cap_Attribute_Bit :: enum u8 {
	LTM_SUPPORT = 1,
}
Ss_Dev_Cap_Attributes :: bit_set[Ss_Dev_Cap_Attribute_Bit;u8]

// USB capability types (BOS).
Bos_Type :: enum c.int {
	WIRELESS_USB_DEVICE_CAPABILITY = 0x01,
	USB_2_0_EXTENSION              = 0x02,
	SS_USB_DEVICE_CAPABILITY       = 0x03,
	CONTAINER_ID                   = 0x04,
	PLATFORM_DESCRIPTOR            = 0x05,
	SUPERSPEED_PLUS_CAPABILITY     = 0x0A,
}

// Speed codes. Indicates the speed at which the device is operating.
Speed :: enum c.int {
	UNKNOWN       = 0,
	LOW           = 1,
	FULL          = 2,
	HIGH          = 3,
	SUPER         = 4,
	SUPER_PLUS    = 5,
	SUPER_PLUS_X2 = 6,
}

// Error codes. Most libusb functions return 0 on success or one of these
// codes on failure. Use `error_name()` for a string representation or
// `strerror()` for an end-user description.
Error :: enum c.int {
	SUCCESS       = 0,
	IO            = -1,
	INVALID_PARAM = -2,
	ACCESS        = -3,
	NO_DEVICE     = -4,
	NOT_FOUND     = -5,
	BUSY          = -6,
	TIMEOUT       = -7,
	OVERFLOW      = -8,
	PIPE          = -9,
	INTERRUPTED   = -10,
	NO_MEM        = -11,
	NOT_SUPPORTED = -12,
	OTHER         = -99,
}

// Transfer type (for the Transfer struct's `type` field).
// Stored as u8 to match libusb_transfer.type which is `unsigned char`.
Transfer_Type :: enum u8 {
	CONTROL     = 0,
	ISOCHRONOUS = 1,
	BULK        = 2,
	INTERRUPT   = 3,
	BULK_STREAM = 4,
}

// Transfer status codes.
Transfer_Status :: enum c.int {
	COMPLETED,
	ERROR,
	TIMED_OUT,
	CANCELLED,
	STALL,
	NO_DEVICE,
	OVERFLOW,
}

// Transfer flag bits for use in a `Transfer_Flags` bit_set.
Transfer_Flag_Bit :: enum u8 {
	// Report short frames as errors.
	SHORT_NOT_OK,
	// Automatically free() transfer buffer during `free_transfer()`.
	// Do not use with buffers from `dev_mem_alloc()`.
	FREE_BUFFER,
	// Automatically call `free_transfer()` after callback returns.
	// Do not call `free_transfer()` from your callback if this is set.
	FREE_TRANSFER,
	// Terminate transfers that are a multiple of the endpoint's
	// wMaxPacketSize with an extra zero length packet. Currently
	// only supported on Linux. Available since libusb-1.0.9.
	ADD_ZERO_PACKET,
}
Transfer_Flags :: bit_set[Transfer_Flag_Bit;u8]

// Capabilities supported by this instance of libusb.
// Test with `has_capability()`.
Capability :: enum u32 {
	HAS_CAPABILITY                = 0x0000,
	HAS_HOTPLUG                   = 0x0001,
	HAS_HID_ACCESS                = 0x0100,
	SUPPORTS_DETACH_KERNEL_DRIVER = 0x0101,
}

// Log message levels.
Log_Level :: enum c.int {
	NONE    = 0,
	ERROR   = 1,
	WARNING = 2,
	INFO    = 3,
	DEBUG   = 4,
}

// Log callback mode bits. Since version 1.0.23.
Log_Cb_Mode_Bit :: enum c.int {
	GLOBAL  = 0,
	CONTEXT = 1,
}
Log_Cb_Mode :: bit_set[Log_Cb_Mode_Bit;c.int]

// Available option values for `set_option()` and `init_context()`.
Option :: enum c.int {
	// Set the log message verbosity. Argument: `Log_Level` (as c.int).
	LOG_LEVEL           = 0,
	// Use the UsbDk backend (Windows only, ignored elsewhere).
	USE_USBDK           = 1,
	// Do not scan for devices in `init_context()` (Linux only).
	// Alias: WEAK_AUTHORITY.
	NO_DEVICE_DISCOVERY = 2,
	// Set the context log callback. Argument: `Log_Cb`.
	LOG_CB              = 3,
	MAX                 = 4,
}

// Alias for NO_DEVICE_DISCOVERY.
OPTION_WEAK_AUTHORITY :: Option.NO_DEVICE_DISCOVERY

// Hotplug event bits. Since version 1.0.16.
Hotplug_Event_Bit :: enum c.int {
	// A device has been plugged in and is ready to use.
	DEVICE_ARRIVED = 0,
	// A device has left and is no longer available. It is the user's
	// responsibility to call `close()` on any associated handle.
	DEVICE_LEFT    = 1,
}
Hotplug_Events :: bit_set[Hotplug_Event_Bit;c.int]

// Hotplug flag bits. Since version 1.0.16.
Hotplug_Flag_Bit :: enum c.int {
	// Arm the callback and fire it for all matching currently attached devices.
	ENUMERATE = 0,
}
Hotplug_Flags :: bit_set[Hotplug_Flag_Bit;c.int]

// Device string type (libusb 1.0.30+).
Device_String_Type :: enum c.int {
	MANUFACTURER,
	PRODUCT,
	SERIAL_NUMBER,
	COUNT,
}

// SuperSpeedPlus sublink attribute enums (libusb 1.0.28+).
Ssplus_Sublink_Type :: enum c.int {
	SYM  = 0,
	ASYM = 1,
}

Ssplus_Sublink_Direction :: enum c.int {
	RX = 0,
	TX = 1,
}

Ssplus_Sublink_Exponent :: enum c.int {
	BPS = 0,
	KBS = 1,
	MBS = 2,
	GBS = 3,
}

Ssplus_Sublink_Protocol :: enum c.int {
	SS     = 0,
	SSPLUS = 1,
}

// Setup packet for control transfers (8 bytes, packed).
// Use `fill_control_setup()` to populate. The 16-bit fields
// are stored in little-endian byte order on the wire.
Control_Setup :: struct #packed {
	bmRequestType: u8,
	bRequest:      u8,
	wValue:        u16,
	wIndex:        u16,
	wLength:       u16,
}

// Standard USB device descriptor (USB 3.0, section 9.6.1).
// All multi-byte fields in host-endian format.
Device_Descriptor :: struct {
	// Size of this descriptor (in bytes).
	bLength:            u8,
	// Descriptor type (`Descriptor_Type.DEVICE`).
	bDescriptorType:    u8,
	// USB spec release number in BCD. 0x0200 = USB 2.0, etc.
	bcdUSB:             u16,
	// USB-IF class code for the device. See `Class_Code`.
	bDeviceClass:       u8,
	// USB-IF subclass code, qualified by bDeviceClass.
	bDeviceSubClass:    u8,
	// USB-IF protocol code, qualified by class and subclass.
	bDeviceProtocol:    u8,
	// Maximum packet size for endpoint 0.
	bMaxPacketSize0:    u8,
	// USB-IF vendor ID.
	idVendor:           u16,
	// USB-IF product ID.
	idProduct:          u16,
	// Device release number in BCD.
	bcdDevice:          u16,
	// Index of manufacturer string descriptor.
	iManufacturer:      u8,
	// Index of product string descriptor.
	iProduct:           u8,
	// Index of serial number string descriptor.
	iSerialNumber:      u8,
	// Number of possible configurations.
	bNumConfigurations: u8,
}

// Standard USB endpoint descriptor (USB 3.0, section 9.6.6).
// All multi-byte fields in host-endian format.
Endpoint_Descriptor :: struct {
	// Size of this descriptor (in bytes).
	bLength:          u8,
	// Descriptor type (`Descriptor_Type.ENDPOINT`).
	bDescriptorType:  u8,
	// Endpoint address. Bits 0:3 = endpoint number, bit 7 = direction.
	bEndpointAddress: u8,
	// Endpoint attributes. Bits 0:1 = transfer type, 2:3 = iso sync type,
	// 4:5 = iso usage type.
	bmAttributes:     u8,
	// Maximum packet size this endpoint can send/receive.
	wMaxPacketSize:   u16,
	// Polling interval for data transfers.
	bInterval:        u8,
	// Audio devices only: rate of synchronization feedback.
	bRefresh:         u8,
	// Audio devices only: address of the synch endpoint.
	bSynchAddress:    u8,
	// Extra descriptors. Stored here if libusb encounters unknown descriptors.
	extra:            [^]u8,
	// Length of extra descriptors in bytes.
	extra_length:     c.int,
}

// Standard USB interface association descriptor (USB 3.0, section 9.6.4).
Interface_Association_Descriptor :: struct {
	bLength:           u8,
	bDescriptorType:   u8,
	bFirstInterface:   u8,
	bInterfaceCount:   u8,
	bFunctionClass:    u8,
	bFunctionSubClass: u8,
	bFunctionProtocol: u8,
	iFunction:         u8,
}

// Array of 0 or more interface association descriptors.
Interface_Association_Descriptor_Array :: struct {
	iad:    [^]Interface_Association_Descriptor,
	length: c.int,
}

// Standard USB interface descriptor (USB 3.0, section 9.6.5).
Interface_Descriptor :: struct {
	bLength:            u8,
	bDescriptorType:    u8,
	bInterfaceNumber:   u8,
	bAlternateSetting:  u8,
	bNumEndpoints:      u8,
	bInterfaceClass:    u8,
	bInterfaceSubClass: u8,
	bInterfaceProtocol: u8,
	iInterface:         u8,
	// Array of endpoint descriptors (length = bNumEndpoints).
	endpoint:           [^]Endpoint_Descriptor,
	extra:              [^]u8,
	extra_length:       c.int,
}

// A collection of alternate settings for a particular USB interface.
Interface :: struct {
	// Array of interface descriptors (length = num_altsetting).
	altsetting:     [^]Interface_Descriptor,
	num_altsetting: c.int,
}

// Standard USB configuration descriptor (USB 3.0, section 9.6.3).
Config_Descriptor :: struct {
	bLength:             u8,
	bDescriptorType:     u8,
	wTotalLength:        u16,
	bNumInterfaces:      u8,
	bConfigurationValue: u8,
	iConfiguration:      u8,
	bmAttributes:        u8,
	// Max power consumption. Units of 2 mA (high-speed) or 8 mA (super-speed).
	MaxPower:            u8,
	// Array of interfaces (length = bNumInterfaces).
	interface:           [^]Interface,
	extra:               [^]u8,
	extra_length:        c.int,
}

// SuperSpeed endpoint companion descriptor (USB 3.0, section 9.6.7).
Ss_Endpoint_Companion_Descriptor :: struct {
	bLength:           u8,
	bDescriptorType:   u8,
	bMaxBurst:         u8,
	bmAttributes:      u8,
	wBytesPerInterval: u16,
}

// Generic BOS Device Capability descriptor. Check bDevCapabilityType and
// call the matching get_*_descriptor function for a fully-typed struct.
Bos_Dev_Capability_Descriptor :: struct {
	bLength:             u8,
	bDescriptorType:     u8,
	bDevCapabilityType:  u8,
	// Device Capability data (bLength - 3 bytes). Flexible array member.
	dev_capability_data: [0]u8,
}

// Binary Device Object Store (BOS) descriptor (USB 3.0, section 9.6.2).
Bos_Descriptor :: struct {
	bLength:         u8,
	bDescriptorType: u8,
	wTotalLength:    u16,
	bNumDeviceCaps:  u8,
	// Flexible array of device capability descriptor pointers.
	dev_capability:  [0]^Bos_Dev_Capability_Descriptor,
}

// USB 2.0 Extension descriptor (USB 3.0, section 9.6.2.1).
Usb2_Extension_Descriptor :: struct {
	bLength:            u8,
	bDescriptorType:    u8,
	bDevCapabilityType: u8,
	// Bitmap of supported device level features.
	// See `Usb2_Ext_Attributes`.
	bmAttributes:       u32,
}

// SuperSpeed USB Device Capability descriptor (USB 3.0, section 9.6.2.2).
Ss_Usb_Device_Capability_Descriptor :: struct {
	bLength:               u8,
	bDescriptorType:       u8,
	bDevCapabilityType:    u8,
	// See `Ss_Dev_Cap_Attributes`.
	bmAttributes:          u8,
	// Bitmap of supported speeds. See `Supported_Speeds`.
	wSpeedSupported:       u16,
	// Lowest speed at which full functionality is available.
	bFunctionalitySupport: u8,
	bU1DevExitLat:         u8,
	bU2DevExitLat:         u16,
}

// Container ID descriptor (USB 3.0, section 9.6.2.3).
Container_Id_Descriptor :: struct {
	bLength:            u8,
	bDescriptorType:    u8,
	bDevCapabilityType: u8,
	bReserved:          u8,
	// 128-bit UUID as a byte array.
	ContainerID:        [16]u8,
}

// Platform descriptor (USB 3.2, section 9.6.2.4).
// Contains a flexible array member — all memory must be managed manually.
Platform_Descriptor :: struct {
	bLength:                u8,
	bDescriptorType:        u8,
	bDevCapabilityType:     u8,
	bReserved:              u8,
	// 128-bit UUID as a byte array.
	PlatformCapabilityUUID: [16]u8,
	// Capability data (bLength - 20 bytes). Flexible array member.
	CapabilityData:         [0]u8,
}

// SuperSpeedPlus sublink attribute (libusb 1.0.28+, USB 3.1, section 9.6.2.5).
Ssplus_Sublink_Attribute :: struct {
	ssid:      u8,
	exponent:  Ssplus_Sublink_Exponent,
	type:      Ssplus_Sublink_Type,
	direction: Ssplus_Sublink_Direction,
	protocol:  Ssplus_Sublink_Protocol,
	mantissa:  u16,
}

// SuperSpeedPlus USB Device Capability descriptor (libusb 1.0.28+).
// This is a parsed/computed representation, not the raw USB descriptor.
Ssplus_Usb_Device_Capability_Descriptor :: struct {
	numSublinkSpeedAttributes: u8,
	numSublinkSpeedIDs:        u8,
	ssid:                      u8,
	minRxLaneCount:            u8,
	minTxLaneCount:            u8,
	// Flexible array of sublink attributes (length = numSublinkSpeedAttributes).
	sublinkSpeedAttributes:    [0]Ssplus_Sublink_Attribute,
}

// Version of the libusb runtime.
Version :: struct {
	major:    u16,
	minor:    u16,
	micro:    u16,
	nano:     u16,
	// Release candidate suffix, e.g. "-rc4".
	rc:       cstring,
	// For ABI compatibility only.
	describe: cstring,
}

// Isochronous packet descriptor.
Iso_Packet_Descriptor :: struct {
	length:        c.uint,
	actual_length: c.uint,
	status:        Transfer_Status,
}

// Generic USB transfer structure.
Transfer :: struct {
	// Handle of the device that this transfer will be submitted to.
	dev_handle:      Device_Handle,
	// Bitwise OR of `Transfer_Flags`.
	flags:           Transfer_Flags,
	// Address of the endpoint where this transfer will be sent.
	endpoint:        u8,
	// Type of the transfer from `Transfer_Type`.
	type:            Transfer_Type,
	// Timeout for this transfer in milliseconds. 0 = no timeout.
	timeout:         c.uint,
	// Status of the transfer. Read-only, valid only within the callback.
	status:          Transfer_Status,
	// Length of the data buffer.
	length:          c.int,
	// Actual length of data transferred. Read-only, valid only within callback.
	actual_length:   c.int,
	// Callback invoked when the transfer completes, fails, or is cancelled.
	callback:        Transfer_Cb,
	// User context data accessible from within the callback.
	user_data:       rawptr,
	// Data buffer.
	buffer:          [^]u8,
	// Number of isochronous packets. Only for isochronous endpoints.
	num_iso_packets: c.int,
	// Isochronous packet descriptors. Flexible array member.
	iso_packet_desc: [0]Iso_Packet_Descriptor,
}

// Platform-specific timeval struct matching the system's struct timeval.
// libusb uses this for event-loop timeouts.
when ODIN_OS == .Darwin {
	Timeval :: struct {
		tv_sec:  c.long,
		tv_usec: i32,
	}
} else {
	Timeval :: struct {
		tv_sec:  c.long,
		tv_usec: c.long,
	}
}

// File descriptor for polling.
Poll_Fd :: struct {
	fd:     c.int,
	// Event flags from <poll.h>. POLLIN for read-ready, POLLOUT for write-ready.
	events: c.short,
}

// Option value union for `Init_Option`.
Init_Option_Value :: struct #raw_union {
	ival:   c.int,
	log_cb: Log_Cb,
}

// Option structure for `init_context()`.
Init_Option :: struct {
	option: Option,
	value:  Init_Option_Value,
}

//----- Opaque handles ----------------------------------

Context :: distinct rawptr
Device :: distinct rawptr
Device_Handle :: distinct rawptr

// Hotplug callback handle, returned by `hotplug_register_callback()`.
Callback_Handle :: distinct c.int

//----- Callback types ----------------------------------

// Asynchronous transfer completion callback.
Transfer_Cb :: #type proc "c" (transfer: ^Transfer)

// Log message callback. Since version 1.0.23.
Log_Cb :: #type proc "c" (ctx: Context, level: Log_Level, str: cstring)

// Hotplug event callback. Return 1 to deregister this callback.
Hotplug_Callback_Fn :: #type proc "c" (
	ctx: Context,
	device: Device,
	event: Hotplug_Events,
	user_data: rawptr,
) -> c.int

// Callback invoked when a new file descriptor should be monitored for events.
Pollfd_Added_Cb :: #type proc "c" (fd: c.int, events: c.short, user_data: rawptr)

// Callback invoked when a file descriptor should no longer be monitored.
Pollfd_Removed_Cb :: #type proc "c" (fd: c.int, user_data: rawptr)

@(default_calling_convention = "c", link_prefix = "libusb_")
foreign lib {

	// ---- Library initialization/deinitialization ----

	set_log_cb :: proc(ctx: Context, cb: Log_Cb, mode: Log_Cb_Mode) ---
	set_option :: proc(ctx: Context, option: Option, #c_vararg args: ..any) -> Error ---
	init :: proc(ctx: ^Context) -> Error ---
	init_context :: proc(ctx: ^Context, options: [^]Init_Option, num_options: c.int) -> Error ---
	exit :: proc(ctx: Context) ---

	// Deprecated: use `set_option(.LOG_LEVEL, level)` instead.
	set_debug :: proc(ctx: Context, level: c.int) ---

	// ---- Device handling and enumeration ----

	// Returns device count (positive) or a negative Error code.
	get_device_list :: proc(ctx: Context, list: ^[^]Device) -> int ---
	free_device_list :: proc(list: [^]Device, unref_devices: c.int) ---
	get_bus_number :: proc(dev: Device) -> u8 ---
	get_port_number :: proc(dev: Device) -> u8 ---
	// Returns number of elements filled or negative Error code.
	get_port_numbers :: proc(dev: Device, port_numbers: [^]u8, port_numbers_len: c.int) -> c.int ---
	// Deprecated: use `get_port_numbers()` instead.
	get_port_path :: proc(ctx: Context, dev: Device, path: [^]u8, path_length: u8) -> c.int ---
	get_parent :: proc(dev: Device) -> Device ---
	get_device_address :: proc(dev: Device) -> u8 ---
	get_device_speed :: proc(dev: Device) -> Speed ---
	get_session_data :: proc(dev: Device) -> c.ulong ---
	// Returns max packet size or negative Error code.
	get_max_packet_size :: proc(dev: Device, endpoint: u8) -> c.int ---
	// Returns max iso packet size or negative Error code.
	get_max_iso_packet_size :: proc(dev: Device, endpoint: u8) -> c.int ---
	get_max_alt_packet_size :: proc(dev: Device, interface_number: c.int, alternate_setting: c.int, endpoint: u8) -> c.int ---
	ref_device :: proc(dev: Device) -> Device ---
	unref_device :: proc(dev: Device) ---
	wrap_sys_device :: proc(ctx: Context, sys_dev: c.intptr_t, dev_handle: ^Device_Handle) -> Error ---
	open :: proc(dev: Device, dev_handle: ^Device_Handle) -> Error ---
	open_device_with_vid_pid :: proc(ctx: Context, vendor_id: u16, product_id: u16) -> Device_Handle ---
	close :: proc(dev_handle: Device_Handle) ---
	get_device :: proc(dev_handle: Device_Handle) -> Device ---
	get_configuration :: proc(dev_handle: Device_Handle, config: ^c.int) -> Error ---
	set_configuration :: proc(dev_handle: Device_Handle, configuration: c.int) -> Error ---
	claim_interface :: proc(dev_handle: Device_Handle, interface_number: c.int) -> Error ---
	release_interface :: proc(dev_handle: Device_Handle, interface_number: c.int) -> Error ---
	set_interface_alt_setting :: proc(dev_handle: Device_Handle, interface_number: c.int, alternate_setting: c.int) -> Error ---
	clear_halt :: proc(dev_handle: Device_Handle, endpoint: u8) -> Error ---
	reset_device :: proc(dev_handle: Device_Handle) -> Error ---
	// Returns 0 if no kernel driver active, 1 if active, or negative Error code.
	kernel_driver_active :: proc(dev_handle: Device_Handle, interface_number: c.int) -> c.int ---
	detach_kernel_driver :: proc(dev_handle: Device_Handle, interface_number: c.int) -> Error ---
	attach_kernel_driver :: proc(dev_handle: Device_Handle, interface_number: c.int) -> Error ---
	set_auto_detach_kernel_driver :: proc(dev_handle: Device_Handle, enable: c.int) -> Error ---

	// ---- Miscellaneous ----

	has_capability :: proc(capability: Capability) -> c.int ---
	error_name :: proc(errcode: Error) -> cstring ---
	get_version :: proc() -> ^Version ---
	setlocale :: proc(locale: cstring) -> Error ---
	strerror :: proc(errcode: Error) -> cstring ---

	// ---- USB descriptors ----

	get_device_descriptor :: proc(dev: Device, desc: ^Device_Descriptor) -> Error ---
	get_active_config_descriptor :: proc(dev: Device, config: ^^Config_Descriptor) -> Error ---
	get_config_descriptor :: proc(dev: Device, config_index: u8, config: ^^Config_Descriptor) -> Error ---
	get_config_descriptor_by_value :: proc(dev: Device, bConfigurationValue: u8, config: ^^Config_Descriptor) -> Error ---
	free_config_descriptor :: proc(config: ^Config_Descriptor) ---
	get_ss_endpoint_companion_descriptor :: proc(ctx: Context, endpoint: ^Endpoint_Descriptor, ep_comp: ^^Ss_Endpoint_Companion_Descriptor) -> Error ---
	free_ss_endpoint_companion_descriptor :: proc(ep_comp: ^Ss_Endpoint_Companion_Descriptor) ---
	get_bos_descriptor :: proc(dev_handle: Device_Handle, bos: ^^Bos_Descriptor) -> Error ---
	free_bos_descriptor :: proc(bos: ^Bos_Descriptor) ---
	get_usb2_extension_descriptor :: proc(ctx: Context, dev_cap: ^Bos_Dev_Capability_Descriptor, usb2_extension: ^^Usb2_Extension_Descriptor) -> Error ---
	free_usb2_extension_descriptor :: proc(usb2_extension: ^Usb2_Extension_Descriptor) ---
	get_ss_usb_device_capability_descriptor :: proc(ctx: Context, dev_cap: ^Bos_Dev_Capability_Descriptor, ss_usb_device_cap: ^^Ss_Usb_Device_Capability_Descriptor) -> Error ---
	free_ss_usb_device_capability_descriptor :: proc(ss_usb_device_cap: ^Ss_Usb_Device_Capability_Descriptor) ---
	get_ssplus_usb_device_capability_descriptor :: proc(ctx: Context, dev_cap: ^Bos_Dev_Capability_Descriptor, ssplus_usb_device_cap: ^^Ssplus_Usb_Device_Capability_Descriptor) -> Error ---
	free_ssplus_usb_device_capability_descriptor :: proc(ssplus_usb_device_cap: ^Ssplus_Usb_Device_Capability_Descriptor) ---
	get_container_id_descriptor :: proc(ctx: Context, dev_cap: ^Bos_Dev_Capability_Descriptor, container_id: ^^Container_Id_Descriptor) -> Error ---
	free_container_id_descriptor :: proc(container_id: ^Container_Id_Descriptor) ---
	get_platform_descriptor :: proc(ctx: Context, dev_cap: ^Bos_Dev_Capability_Descriptor, platform_descriptor: ^^Platform_Descriptor) -> Error ---
	free_platform_descriptor :: proc(platform_descriptor: ^Platform_Descriptor) ---
	// Returns number of bytes written or negative Error code. `data` is an output buffer.
	get_string_descriptor_ascii :: proc(dev_handle: Device_Handle, desc_index: u8, data: [^]u8, length: c.int) -> c.int ---
	get_interface_association_descriptors :: proc(dev: Device, config_index: u8, iad_array: ^^Interface_Association_Descriptor_Array) -> Error ---
	get_active_interface_association_descriptors :: proc(dev: Device, iad_array: ^^Interface_Association_Descriptor_Array) -> Error ---
	free_interface_association_descriptors :: proc(iad_array: ^Interface_Association_Descriptor_Array) ---

	// ---- Device string (libusb 1.0.30+) ----

	// Retrieve a device string descriptor as UTF-8.
	// Returns number of bytes written or negative Error code.
	get_device_string :: proc(dev: Device, string_type: Device_String_Type, data: [^]u8, length: c.int) -> c.int ---

	// ---- Raw I/O (libusb 1.0.30+) ----

	// Returns 1 if supported, 0 if not, or negative Error code.
	endpoint_supports_raw_io :: proc(dev_handle: Device_Handle, endpoint: u8) -> c.int ---
	endpoint_set_raw_io :: proc(dev_handle: Device_Handle, endpoint: u8, enable: c.int) -> c.int ---
	get_max_raw_io_transfer_size :: proc(dev_handle: Device_Handle, endpoint: u8) -> c.int ---

	// ---- Device hotplug event notification ----

	hotplug_register_callback :: proc(ctx: Context, events: Hotplug_Events, flags: Hotplug_Flags, vendor_id: c.int, product_id: c.int, dev_class: c.int, cb_fn: Hotplug_Callback_Fn, user_data: rawptr, callback_handle: ^Callback_Handle) -> Error ---
	hotplug_deregister_callback :: proc(ctx: Context, callback_handle: Callback_Handle) ---
	hotplug_get_user_data :: proc(ctx: Context, callback_handle: Callback_Handle) -> rawptr ---

	// ---- Asynchronous device I/O ----

	// Returns number of streams allocated or negative Error code.
	alloc_streams :: proc(dev_handle: Device_Handle, num_streams: u32, endpoints: [^]u8, num_endpoints: c.int) -> c.int ---
	free_streams :: proc(dev_handle: Device_Handle, endpoints: [^]u8, num_endpoints: c.int) -> Error ---
	dev_mem_alloc :: proc(dev_handle: Device_Handle, length: c.size_t) -> [^]u8 ---
	dev_mem_free :: proc(dev_handle: Device_Handle, buffer: [^]u8, length: c.size_t) -> Error ---
	alloc_transfer :: proc(iso_packets: c.int) -> ^Transfer ---
	free_transfer :: proc(transfer: ^Transfer) ---
	submit_transfer :: proc(transfer: ^Transfer) -> Error ---
	cancel_transfer :: proc(transfer: ^Transfer) -> Error ---
	transfer_set_stream_id :: proc(transfer: ^Transfer, stream_id: u32) ---
	transfer_get_stream_id :: proc(transfer: ^Transfer) -> u32 ---

	// ---- Polling and timing ----

	try_lock_events :: proc(ctx: Context) -> c.int ---
	lock_events :: proc(ctx: Context) ---
	unlock_events :: proc(ctx: Context) ---
	event_handling_ok :: proc(ctx: Context) -> c.int ---
	event_handler_active :: proc(ctx: Context) -> c.int ---
	interrupt_event_handler :: proc(ctx: Context) ---
	lock_event_waiters :: proc(ctx: Context) ---
	unlock_event_waiters :: proc(ctx: Context) ---
	wait_for_event :: proc(ctx: Context, tv: ^Timeval) -> c.int ---
	handle_events :: proc(ctx: Context) -> Error ---
	handle_events_timeout :: proc(ctx: Context, tv: ^Timeval) -> Error ---
	handle_events_timeout_completed :: proc(ctx: Context, tv: ^Timeval, completed: ^c.int) -> Error ---
	handle_events_completed :: proc(ctx: Context, completed: ^c.int) -> Error ---
	handle_events_locked :: proc(ctx: Context, tv: ^Timeval) -> Error ---
	pollfds_handle_timeouts :: proc(ctx: Context) -> c.int ---
	// Returns 0 if no timeout, 1 if a timeout was returned, or negative Error code.
	get_next_timeout :: proc(ctx: Context, tv: ^Timeval) -> c.int ---
	set_pollfd_notifiers :: proc(ctx: Context, added_cb: Pollfd_Added_Cb, removed_cb: Pollfd_Removed_Cb, user_data: rawptr) ---
	// Returns a NULL-terminated list of `Poll_Fd` pointers. Free with `free_pollfds()`.
	get_pollfds :: proc(ctx: Context) -> [^]^Poll_Fd ---
	free_pollfds :: proc(pollfds: [^]^Poll_Fd) ---

	// ---- Synchronous device I/O ----

	// Returns number of bytes transferred or negative Error code.
	control_transfer :: proc(dev_handle: Device_Handle, bmRequestType: u8, bRequest: u8, wValue: u16, wIndex: u16, data: [^]u8, wLength: u16, timeout: c.uint) -> c.int ---
	bulk_transfer :: proc(dev_handle: Device_Handle, endpoint: u8, data: [^]u8, length: c.int, transferred: ^c.int, timeout: c.uint) -> Error ---
	interrupt_transfer :: proc(dev_handle: Device_Handle, endpoint: u8, data: [^]u8, length: c.int, transferred: ^c.int, timeout: c.uint) -> Error ---
}

// ---------------------------------------------------------------------------------------------------------------------
// ----- Helper Procedures (from libusb static inline in libusb.h) ------------------------
// ---------------------------------------------------------------------------------------------------------------------

// Convert a 16-bit value from host-endian to little-endian format.
cpu_to_le16 :: #force_inline proc "contextless" (x: u16) -> u16 {
	return transmute(u16)u16le(x)
}

// Convert a 16-bit value from little-endian to host-endian format.
// Identical to `cpu_to_le16` since the conversion is its own inverse.
le16_to_cpu :: cpu_to_le16

// Get the data section of a control transfer. The data starts 8 bytes into
// the buffer, after the setup packet.
control_transfer_get_data :: #force_inline proc "contextless" (transfer: ^Transfer) -> [^]u8 {
	return ([^]u8)(&transfer.buffer[CONTROL_SETUP_SIZE])
}

// Get the control setup packet from the start of a transfer's data buffer.
control_transfer_get_setup :: #force_inline proc "contextless" (transfer: ^Transfer) -> ^Control_Setup {
	return (^Control_Setup)(transfer.buffer)
}

// Populate the setup packet (first 8 bytes of the data buffer) for a control
// transfer. The wValue, wIndex, and wLength values should be given in
// host-endian byte order; they will be converted to little-endian.
fill_control_setup :: proc "contextless" (
	buffer: [^]u8,
	bmRequestType: u8,
	bRequest: u8,
	wValue: u16,
	wIndex: u16,
	wLength: u16,
) {
	setup := (^Control_Setup)(buffer)
	setup.bmRequestType = bmRequestType
	setup.bRequest = bRequest
	setup.wValue = cpu_to_le16(wValue)
	setup.wIndex = cpu_to_le16(wIndex)
	setup.wLength = cpu_to_le16(wLength)
}

// Populate the required fields for a control transfer. If buffer is non-nil,
// the first 8 bytes are interpreted as a setup packet and the transfer length
// is inferred from the setup's wLength field.
fill_control_transfer :: proc "contextless" (
	transfer: ^Transfer,
	dev_handle: Device_Handle,
	buffer: [^]u8,
	callback: Transfer_Cb,
	user_data: rawptr,
	timeout: c.uint,
) {
	transfer.dev_handle = dev_handle
	transfer.endpoint = 0
	transfer.type = .CONTROL
	transfer.timeout = timeout
	transfer.buffer = buffer
	if buffer != nil {
		setup := (^Control_Setup)(buffer)
		transfer.length = c.int(CONTROL_SETUP_SIZE) + c.int(le16_to_cpu(setup.wLength))
	}
	transfer.user_data = user_data
	transfer.callback = callback
}

// Populate the required fields for a bulk transfer.
fill_bulk_transfer :: proc "contextless" (
	transfer: ^Transfer,
	dev_handle: Device_Handle,
	endpoint: u8,
	buffer: [^]u8,
	length: c.int,
	callback: Transfer_Cb,
	user_data: rawptr,
	timeout: c.uint,
) {
	transfer.dev_handle = dev_handle
	transfer.endpoint = endpoint
	transfer.type = .BULK
	transfer.timeout = timeout
	transfer.buffer = buffer
	transfer.length = length
	transfer.user_data = user_data
	transfer.callback = callback
}

// Populate the required fields for a bulk stream transfer.
// Since version 1.0.19.
fill_bulk_stream_transfer :: proc "contextless" (
	transfer: ^Transfer,
	dev_handle: Device_Handle,
	endpoint: u8,
	stream_id: u32,
	buffer: [^]u8,
	length: c.int,
	callback: Transfer_Cb,
	user_data: rawptr,
	timeout: c.uint,
) {
	fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length, callback, user_data, timeout)
	transfer.type = .BULK_STREAM
	transfer_set_stream_id(transfer, stream_id)
}

// Populate the required fields for an interrupt transfer.
fill_interrupt_transfer :: proc "contextless" (
	transfer: ^Transfer,
	dev_handle: Device_Handle,
	endpoint: u8,
	buffer: [^]u8,
	length: c.int,
	callback: Transfer_Cb,
	user_data: rawptr,
	timeout: c.uint,
) {
	transfer.dev_handle = dev_handle
	transfer.endpoint = endpoint
	transfer.type = .INTERRUPT
	transfer.timeout = timeout
	transfer.buffer = buffer
	transfer.length = length
	transfer.user_data = user_data
	transfer.callback = callback
}

// Populate the required fields for an isochronous transfer.
fill_iso_transfer :: proc "contextless" (
	transfer: ^Transfer,
	dev_handle: Device_Handle,
	endpoint: u8,
	buffer: [^]u8,
	length: c.int,
	num_iso_packets: c.int,
	callback: Transfer_Cb,
	user_data: rawptr,
	timeout: c.uint,
) {
	transfer.dev_handle = dev_handle
	transfer.endpoint = endpoint
	transfer.type = .ISOCHRONOUS
	transfer.timeout = timeout
	transfer.buffer = buffer
	transfer.length = length
	transfer.num_iso_packets = num_iso_packets
	transfer.user_data = user_data
	transfer.callback = callback
}

// Set the length of all packets in an isochronous transfer to the same value.
set_iso_packet_lengths :: proc "contextless" (transfer: ^Transfer, length: c.uint) {
	descs := ([^]Iso_Packet_Descriptor)(&transfer.iso_packet_desc)
	for i in 0 ..< int(transfer.num_iso_packets) {
		descs[i].length = length
	}
}

// Locate the position of an isochronous packet within the buffer by
// accumulating lengths of all preceding packets.
get_iso_packet_buffer :: proc "contextless" (transfer: ^Transfer, packet: c.uint) -> [^]u8 {
	if c.int(packet) >= transfer.num_iso_packets do return nil
	descs := ([^]Iso_Packet_Descriptor)(&transfer.iso_packet_desc)
	offset: uint = 0
	for i in 0 ..< int(packet) {
		offset += uint(descs[i].length)
	}
	return ([^]u8)(&transfer.buffer[offset])
}

// Locate the position of an isochronous packet within the buffer, assuming
// all packets are the same size as the first packet.
get_iso_packet_buffer_simple :: proc "contextless" (transfer: ^Transfer, packet: c.uint) -> [^]u8 {
	if c.int(packet) >= transfer.num_iso_packets do return nil
	descs := ([^]Iso_Packet_Descriptor)(&transfer.iso_packet_desc)
	offset := uint(descs[0].length) * uint(packet)
	return ([^]u8)(&transfer.buffer[offset])
}

// Retrieve a descriptor from the default control pipe. Convenience wrapper
// around `control_transfer()`.
get_descriptor :: proc(
	dev_handle: Device_Handle,
	desc_type: u8,
	desc_index: u8,
	data: [^]u8,
	length: c.int,
) -> c.int {
	return control_transfer(
		dev_handle,
		u8(Endpoint_Direction.IN),
		u8(Standard_Request.GET_DESCRIPTOR),
		u16(desc_type) << 8 | u16(desc_index),
		0,
		data,
		u16(length),
		1000,
	)
}

// Retrieve a string descriptor from a device. The string returned is Unicode
// (UTF-16LE) as per the USB spec. Convenience wrapper around `control_transfer()`.
get_string_descriptor :: proc(
	dev_handle: Device_Handle,
	desc_index: u8,
	langid: u16,
	data: [^]u8,
	length: c.int,
) -> c.int {
	return control_transfer(
		dev_handle,
		u8(Endpoint_Direction.IN),
		u8(Standard_Request.GET_DESCRIPTOR),
		u16(u16(Descriptor_Type.STRING) << 8 | u16(desc_index)),
		langid,
		data,
		u16(length),
		1000,
	)
}

// =============================================================================
// Tests
// =============================================================================

import "core:testing"

@(test)
init_test :: proc(t: ^testing.T) {
	result := init(nil)
	testing.expect_value(t, result, Error.SUCCESS)
	if result == .SUCCESS {
		exit(nil)
	}
}
