///|
fn Budget::node(self : Budget, limits : Limits) -> Unit raise OscError {
  if self.nodes >= limits.elements {
    raise Limit("total packet nodes")
  }
  self.nodes += 1
}

///|
fn read_packet(
  r : Reader,
  limits : Limits,
  budget : Budget,
  depth : Int32,
) -> Packet raise OscError {
  if depth > limits.depth {
    raise Limit("bundle depth")
  }
  budget.node(limits)
  r.need(4)
  if r.data[r.pos] == 47 {
    return Message(decode_message_reader(r, limits, budget))
  }
  let start = r.pos
  if r.text(limits) != "#bundle" {
    raise Invalid("unknown packet prefix", start)
  }
  let time = TimeTag::{ seconds: r.word(), fraction: r.word(), }
  let children : Array[Packet] = []
  while r.pos < r.end {
    let n = r.word().reinterpret_as_int()
    if n <= 0 || n % 4 != 0 {
      raise Invalid("invalid bundle element size", r.pos - 4)
    }
    r.need(n)
    let child = Reader::{ data: r.data, pos: r.pos, end: r.pos + n, }
    children.push(read_packet(child, limits, budget, depth + 1))
    if child.pos != child.end {
      raise Invalid("unconsumed bundle child", child.pos)
    }
    r.pos += n
  }
  Bundle(time, children)
}

///|
pub fn decode(
  bytes : Bytes,
  limits~ : Limits = Limits::default(),
) -> Packet raise OscError {
  limits.validate()
  if bytes.length() > limits.packet_bytes {
    raise Limit("packet bytes")
  }
  if bytes.length() % 4 != 0 {
    raise Invalid("packet not 4-byte aligned", bytes.length())
  }
  read_packet(
    Reader::{ data: bytes, pos: 0, end: bytes.length(), },
    limits,
    Budget::{ args: 0, nodes: 0, },
    1,
  )
}

///|
fn write_packet(
  packet : Packet,
  limits : Limits,
  budget : Budget,
  depth : Int32,
) -> Bytes raise OscError {
  if depth > limits.depth {
    raise Limit("bundle depth")
  }
  budget.node(limits)
  match packet {
    Message(m) => encode_message_budget(m, limits, budget)
    Bundle(time, children) => {
      let w = Writer::{ out: [], limits, }
      w.text("#bundle")
      w.word(time.seconds)
      w.word(time.fraction)
      for child in children {
        let encoded = write_packet(child, limits, budget, depth + 1)
        w.room(4 + encoded.length())
        w.word(encoded.length().reinterpret_as_uint())
        for b in encoded {
          w.out.push(b)
        }
      }
      Bytes::from_array(w.out)
    }
  }
}

///|
pub fn encode(
  packet : Packet,
  limits~ : Limits = Limits::default(),
) -> Bytes raise OscError {
  limits.validate()
  write_packet(packet, limits, Budget::{ args: 0, nodes: 0, }, 1)
}