///|
pub(all) suberror TransportError {
  InvalidDeviceInfo(String)
  InvalidControlRequest(String)
  InvalidEndpoint(Int)
  InvalidLength(Int)
  Timeout
  Disconnected
  Closed
  ShortTransfer(Int, Int)
  UnsupportedRequest(Int)
  Failure(String)
} derive(Eq, Debug)

///|
fn valid_u8(value : Int) -> Bool {
  value >= 0 && value <= 255
}

///|
fn valid_u16(value : Int) -> Bool {
  value >= 0 && value <= 65535
}

///|
pub struct DeviceInfo {
  priv vendor_id : Int
  priv product_id : Int
  priv bulk_in_endpoint : Int?
  priv bulk_out_endpoint : Int?
} derive(Eq, Debug)

///|
pub fn DeviceInfo::new(
  vendor_id~ : Int,
  product_id~ : Int,
  bulk_in_endpoint~ : Int?,
  bulk_out_endpoint~ : Int?,
) -> DeviceInfo raise TransportError {
  if !valid_u16(vendor_id) || !valid_u16(product_id) {
    raise InvalidDeviceInfo("USB vendor and product IDs must fit in 16 bits")
  }
  match bulk_in_endpoint {
    Some(endpoint) if !valid_u8(endpoint) => raise InvalidEndpoint(endpoint)
    _ => ()
  }
  match bulk_out_endpoint {
    Some(endpoint) if !valid_u8(endpoint) => raise InvalidEndpoint(endpoint)
    _ => ()
  }
  { vendor_id, product_id, bulk_in_endpoint, bulk_out_endpoint }
}

///|
pub fn DeviceInfo::vendor_id(self : DeviceInfo) -> Int {
  self.vendor_id
}

///|
pub fn DeviceInfo::product_id(self : DeviceInfo) -> Int {
  self.product_id
}

///|
pub fn DeviceInfo::bulk_in_endpoint(self : DeviceInfo) -> Int? {
  self.bulk_in_endpoint
}

///|
pub fn DeviceInfo::bulk_out_endpoint(self : DeviceInfo) -> Int? {
  self.bulk_out_endpoint
}

///|
pub fn DeviceInfo::has_bulk_endpoints(self : DeviceInfo) -> Bool {
  self.bulk_in_endpoint is Some(_) && self.bulk_out_endpoint is Some(_)
}

///|
pub struct ControlTransfer {
  priv request_type : Int
  priv request : Int
  priv value : Int
  priv index : Int
  priv length : Int
  priv data : Bytes
} derive(Eq, Debug)

///|
pub fn ControlTransfer::in_request(
  request_type~ : Int,
  request~ : Int,
  value~ : Int,
  index~ : Int,
  length~ : Int,
) -> ControlTransfer raise TransportError {
  validate_control_header(request_type, request, value, index)
  if length < 0 || length > 65535 {
    raise InvalidLength(length)
  }
  { request_type, request, value, index, length, data: b"" }
}

///|
pub fn ControlTransfer::out_request(
  request_type~ : Int,
  request~ : Int,
  value~ : Int,
  index~ : Int,
  data~ : Bytes,
) -> ControlTransfer raise TransportError {
  validate_control_header(request_type, request, value, index)
  if data.length() > 65535 {
    raise InvalidLength(data.length())
  }
  { request_type, request, value, index, length: data.length(), data }
}

///|
fn validate_control_header(
  request_type : Int,
  request : Int,
  value : Int,
  index : Int,
) -> Unit raise TransportError {
  if !valid_u8(request_type) {
    raise InvalidControlRequest("request type must fit in one byte")
  }
  if !valid_u8(request) {
    raise InvalidControlRequest("request must fit in one byte")
  }
  if !valid_u16(value) || !valid_u16(index) {
    raise InvalidControlRequest("value and index must fit in 16 bits")
  }
}

///|
pub fn ControlTransfer::request_type(self : ControlTransfer) -> Int {
  self.request_type
}

///|
pub fn ControlTransfer::request(self : ControlTransfer) -> Int {
  self.request
}

///|
pub fn ControlTransfer::value(self : ControlTransfer) -> Int {
  self.value
}

///|
pub fn ControlTransfer::index(self : ControlTransfer) -> Int {
  self.index
}

///|
pub fn ControlTransfer::length(self : ControlTransfer) -> Int {
  self.length
}

///|
pub fn ControlTransfer::data(self : ControlTransfer) -> Bytes {
  self.data
}

///|
pub fn ControlTransfer::is_in(self : ControlTransfer) -> Bool {
  self.request_type >= 128
}

///|
pub(open) trait UsbTransport {
  fn info(Self) -> DeviceInfo raise TransportError
  fn control(Self, ControlTransfer, Int) -> Bytes raise TransportError
  fn bulk_write(Self, Int, Bytes, Int) -> Int raise TransportError
  fn bulk_read(Self, Int, Int, Int) -> Bytes raise TransportError
  fn close(Self) -> Unit raise TransportError
}