///|
const MAX_FRAME_BYTES : Int = 64 * 1024

///|
fn fixture_identity() -> @madk.AoaIdentity raise @madk.AoaError {
  @madk.AoaIdentity::new(
    manufacturer="wasee-moon",
    model="wasee-android-host",
    description="wasee Android WASI AOA guest",
    version="0.1.0",
    uri="https://example.invalid/wasee-moon",
    serial="wasee-guest",
  )
}

///|
fn frame(body : String) -> Bytes {
  let payload = @utf8.encode(body, bom=false)
  let output = @buffer.Buffer::Buffer(size_hint=4 + payload.length())
  output.write_int_be(payload.length())
  output.write_bytes(payload[:])
  output.to_bytes()
}

///|
fn read_frame(
  session : @madk.AoaSession,
  timeout_ms : Int,
) -> String raise @madk.AoaError {
  let header = session.read_bulk_exact(4, timeout_ms)
  let length = header[0].to_int() * 16_777_216 +
    header[1].to_int() * 65_536 +
    header[2].to_int() * 256 +
    header[3].to_int()
  if length <= 0 || length > MAX_FRAME_BYTES {
    raise @madk.InvalidState("invalid AOA frame length: \{length}")
  }
  @utf8.decode_lossy(session.read_bulk_exact(length, timeout_ms))
}

///|
fn exchange(
  session : @madk.AoaSession,
  body : String,
  expected : String,
) -> Unit raise @madk.AoaError {
  session.write_bulk(frame(body), 5_000)
  let actual = read_frame(session, 5_000)
  if actual != expected {
    raise @madk.InvalidState(
      "AOA frame mismatch: expected '\{expected}', got '\{actual}'",
    )
  }
  println("wasee guest: frame ok=" + body)
}

///|
fn run_aoa(
  transport : @bridge.AndroidUsbTransport,
) -> Unit raise @madk.AoaError {
  let session = @madk.AoaSession::new(transport as &@transport.UsbTransport)
  if session.state() == @madk.BulkReady {
    println("wasee guest: AOA accessory already enumerated")
  } else {
    let protocol = session.probe()
    println("wasee guest: AOA protocol=\{Repr(protocol)}")
    session.configure(fixture_identity())
    session.start()
    println("wasee guest: AOA start requested; waiting for re-enumeration")
    session.refresh_bulk_endpoints()
  }
  let info = session.device_info()
  println(
    "wasee guest: AOA accessory vid=" +
    info.vendor_id().to_string() +
    " pid=" +
    info.product_id().to_string(),
  )
  exchange(session, "status", "status:connected;protocol=host-negotiated")
  exchange(session, "echo-from-wasee", "echo:echo-from-wasee")
  session.close()
}

///|
fn run_descriptor_probe(
  transport : @bridge.AndroidUsbTransport,
) -> Unit raise @transport.TransportError {
  let request = @transport.ControlTransfer::in_request(
    request_type=0x80,
    request=0x06,
    value=0x0100,
    index=0,
    length=64,
  )
  let response = transport.control(request, 1_000)
  if response.length() >= 12 && response[1] == b'\x01' {
    let vendor_id = response[8].to_int() + response[9].to_int() * 256
    let product_id = response[10].to_int() + response[11].to_int() * 256
    println(
      "wasee guest: usb descriptor length=" + response.length().to_string(),
    )
    println(
      "wasee guest: usb descriptor vid=" +
      vendor_id.to_string() +
      " pid=" +
      product_id.to_string(),
    )
  } else {
    raise @transport.Failure("USB device descriptor response is invalid")
  }
  transport.close()
  println("wasee guest: usb open/close=0")
}

///|
fn main {
  let count = @bridge.list_devices_count()
  println("wasee guest: allowlisted USB devices=" + count.to_string())
  if count <= 0 {
    return
  }
  let device_id = @bridge.first_device_id()
  let handle = @bridge.open_device(device_id)
  if handle < 0 {
    println("wasee guest: usb open failed=" + handle.to_string())
    return
  }
  let transport = @bridge.AndroidUsbTransport::new(handle)
  let info = transport.info() catch {
    error => {
      println("wasee guest: usb info failed=\{Repr(error)}")
      return
    }
  }
  if info.vendor_id() == @madk.aoa_vendor_id {
    run_aoa(transport) catch {
      error => println("wasee guest: AOA failed=\{Repr(error)}")
    }
  } else {
    run_descriptor_probe(transport) catch {
      error => println("wasee guest: descriptor failed=\{Repr(error)}")
    }
  }
}