///|
priv struct Budget {
  mut args : Int32
  mut nodes : Int32
}

///|
fn Budget::argument(self : Budget, limits : Limits) -> Unit raise OscError {
  if self.args >= limits.arguments {
    raise Limit("total arguments")
  }
  self.args += 1
}

///|
fn read_arguments(
  r : Reader,
  tags : String,
  cursor : Ref[Int32],
  depth : Int32,
  limits : Limits,
  budget : Budget,
  nested : Bool,
) -> Array[Argument] raise OscError {
  if depth > limits.depth {
    raise Limit("argument array depth")
  }
  let out : Array[Argument] = []
  while cursor.val < tags.length() {
    let tag = tags[cursor.val].to_int()
    cursor.val += 1
    if tag == 93 {
      if !nested {
        raise Invalid("unmatched array close", cursor.val - 1)
      }
      return out
    }
    budget.argument(limits)
    let arg = match tag {
      105 => I32(r.word().reinterpret_as_int())
      102 => F32(r.word())
      115 => Text(r.text(limits))
      98 => Blob(r.blob(limits))
      104 => I64(r.wide().reinterpret_as_int64())
      100 => F64(r.wide())
      116 => Time({ seconds: r.word(), fraction: r.word(), })
      83 => Symbol(r.text(limits))
      99 => {
        let c = r.word()
        if c > 127U {
          raise Invalid("non-ASCII character argument", r.pos - 4)
        }
        Character(c.reinterpret_as_int())
      }
      114 => Rgba(r.word())
      109 => Midi(r.word())
      84 => True
      70 => False
      78 => Nil
      73 => Impulse
      91 =>
        Array(read_arguments(r, tags, cursor, depth + 1, limits, budget, true))
      _ => raise Invalid("unsupported type tag", cursor.val - 1)
    }
    out.push(arg)
  }
  if nested {
    raise Invalid("unclosed argument array", cursor.val)
  }
  out
}

///|
fn collect_tags(
  args : Array[Argument],
  tags : Array[Char],
  depth : Int32,
  limits : Limits,
  budget : Budget,
) -> Unit raise OscError {
  if depth > limits.depth {
    raise Limit("argument array depth")
  }
  for arg in args {
    budget.argument(limits)
    match arg {
      Array(values) => {
        tags.push('[')
        collect_tags(values, tags, depth + 1, limits, budget)
        tags.push(']')
      }
      _ => tags.push(argument_tag(arg))
    }
    if tags.length() > limits.string_bytes {
      raise Limit("type-tag bytes")
    }
  }
}

///|
fn write_arguments(w : Writer, args : Array[Argument]) -> Unit raise OscError {
  for arg in args {
    match arg {
      Array(values) => write_arguments(w, values)
      _ => write_argument(w, arg)
    }
  }
}