///|
pub(all) struct Route {
  network : Int32
  address : Bytes
} derive(Debug)

///|
pub(all) struct Npdu {
  destination : Route?
  source : Route?
  hop_count : Int32
  expecting_reply : Bool
  priority : Int32
  payload : Bytes
} derive(Debug)

///|
fn route_valid(route : Route, source : Bool) -> Unit raise BacnetError {
  require_range(
    route.network,
    1,
    if source {
      65534
    } else {
      65535
    },
    "network number",
  )
  if route.address.length() > 255 ||
    (source && route.address.is_empty()) ||
    (route.network == 65535 && !route.address.is_empty()) {
    raise Invalid("route address", 0)
  }
}

///|
fn write_route(
  w : Writer,
  route : Route,
  source : Bool,
) -> Unit raise BacnetError {
  route_valid(route, source)
  w.uint(route.network.reinterpret_as_uint(), 2)
  w.u8(route.address.length())
  w.put(route.address)
}

///|
fn read_route(r : Reader, source : Bool) -> Route raise BacnetError {
  let network = r.uint(2).reinterpret_as_int()
  let address = r.take(r.u8())
  let result : Route = { network, address, }
  route_valid(result, source)
  result
}

///|
pub fn Npdu::for_local(
  payload : Bytes,
  expecting_reply~ : Bool = false,
) -> Npdu {
  {
    destination: None,
    source: None,
    hop_count: 255,
    expecting_reply,
    priority: 0,
    payload,
  }
}

///|
pub fn encode_npdu(
  npdu : Npdu,
  limits~ : Limits = Limits::default(),
) -> Bytes raise BacnetError {
  limits.validate()
  require_range(npdu.priority, 0, 3, "network priority")
  require_range(npdu.hop_count, 0, 255, "hop count")
  let mut control = npdu.priority | (if npdu.expecting_reply { 4 } else { 0 })
  if npdu.destination is Some(_) {
    control = control | 0x20
  }
  if npdu.source is Some(_) {
    control = control | 8
  }
  let w = Writer::new(limits.max_bytes)
  w.u8(1)
  w.u8(control)
  if npdu.destination is Some(route) {
    write_route(w, route, false)
  }
  if npdu.source is Some(route) {
    write_route(w, route, true)
  }
  if npdu.destination is Some(_) {
    w.u8(npdu.hop_count)
  }
  if npdu.payload.is_empty() {
    raise Invalid("missing APDU", 0)
  }
  w.put(npdu.payload)
  w.finish()
}

///|
pub fn decode_npdu(
  data : Bytes,
  limits~ : Limits = Limits::default(),
) -> Npdu raise BacnetError {
  let r = Reader::new(data, limits)
  if r.u8() != 1 {
    raise Unsupported("NPDU version")
  }
  let control = r.u8()
  if (control & 0x50) != 0 {
    raise Invalid("NPDU reserved flags", 1)
  }
  if (control & 0x80) != 0 {
    raise Unsupported("network-layer message")
  }
  let destination = if (control & 0x20) != 0 {
    Some(read_route(r, false))
  } else {
    None
  }
  let source = if (control & 8) != 0 { Some(read_route(r, true)) } else { None }
  let hop_count = if destination is Some(_) { r.u8() } else { 255 }
  let payload = r.take(r.remaining())
  if payload.is_empty() {
    raise Invalid("missing APDU", r.pos)
  }
  {
    destination,
    source,
    hop_count,
    expecting_reply: (control & 4) != 0,
    priority: control & 3,
    payload,
  }
}

///|
pub extend Route with @debug.Debug::{to_repr}

///|
pub extend Npdu with @debug.Debug::{to_repr}