///|
/// Limits bound indexing allocations. The caller still owns the input buffer.
pub struct ParseLimits {
  max_file_bytes : Int
  max_objects : Int
  max_classes : Int
  max_strings : Int
  max_text_bytes : Int
  max_roots : Int
  max_records : Int
  max_trace_frames : Int
}

///|
pub fn ParseLimits::new(
  max_file_bytes? : Int = 536_870_912,
  max_objects? : Int = 2_000_000,
  max_classes? : Int = 200_000,
  max_strings? : Int = 2_000_000,
  max_text_bytes? : Int = 1_048_576,
  max_roots? : Int = 4_000_000,
  max_records? : Int = 8_000_000,
  max_trace_frames? : Int = 100_000,
) -> ParseLimits raise ParseError {
  let limits = {
    max_file_bytes,
    max_objects,
    max_classes,
    max_strings,
    max_text_bytes,
    max_roots,
    max_records,
    max_trace_frames,
  }
  limits.validate()
  limits
}

///|
pub fn ParseLimits::validate(self : ParseLimits) -> Unit raise ParseError {
  if self.max_file_bytes < 31 ||
    self.max_objects < 0 ||
    self.max_classes < 0 ||
    self.max_strings < 0 ||
    self.max_text_bytes < 0 ||
    self.max_roots < 0 ||
    self.max_records < 0 ||
    self.max_trace_frames < 0 {
    raise Invalid(0, "parse limits must be non-negative and allow a header")
  }
}

///|
fn check_limit(
  offset : Int,
  name : String,
  count : Int,
  maximum : Int,
) -> Unit raise ParseError {
  if count >= maximum {
    raise Invalid(offset, "resource limit exceeded: \{name}")
  }
}

///|
/// Parses and indexes a dump using explicit caller-specified resource limits.
pub fn parse_with_limits(
  data : Bytes,
  limits : ParseLimits,
) -> Summary raise ParseError {
  parse(data, limits~)
}