///|
pub(all) suberror QueryError {
  InvalidTerm(String)
  Syntax(Int32, String)
  Unsupported(Int32, String)
  Resource(String)
  Evaluation(String)
  InvalidInput(String)
} derive(@debug.Debug)

///|
pub fn QueryError::message(self : QueryError) -> String {
  match self {
    InvalidTerm(s) => "invalid RDF term: " + s
    Syntax(p, s) => "syntax at " + p.to_string() + ": " + s
    Unsupported(p, s) => "unsupported at " + p.to_string() + ": " + s
    Resource(s) => "resource limit: " + s
    Evaluation(s) => "evaluation: " + s
    InvalidInput(s) => "input: " + s
  }
}

///|
pub(all) struct Limits {
  max_triples : Int32
  max_query_chars : Int32
  max_tokens : Int32
  max_depth : Int32
  max_steps : Int32
  max_bindings : Int32
  max_results : Int32
  max_path_pairs : Int32
  max_numeric_digits : Int32
} derive(Eq, @debug.Debug)

///|
pub fn Limits::default() -> Limits {
  {
    max_triples: 100000,
    max_query_chars: 262144,
    max_tokens: 32768,
    max_depth: 64,
    max_steps: 5000000,
    max_bindings: 100000,
    max_results: 100000,
    max_path_pairs: 100000,
    max_numeric_digits: 256,
  }
}

///|
fn Limits::validate(self : Limits) -> Unit raise QueryError {
  let values = [
    self.max_triples,
    self.max_query_chars,
    self.max_tokens,
    self.max_depth,
    self.max_steps,
    self.max_bindings,
    self.max_results,
    self.max_path_pairs,
    self.max_numeric_digits,
  ]
  for n in values {
    if n <= 0 {
      raise InvalidInput("limits must be positive")
    }
  }
  if self.max_depth > 256 {
    raise InvalidInput("max_depth exceeds 256")
  }
  if self.max_numeric_digits > 4096 {
    raise InvalidInput("numeric digit budget exceeds 4096")
  }
}

///|
priv struct Budget {
  limits : Limits
  mut steps : Int32
  mut peak_bindings : Int32
  mut path_pairs : Int32
}

///|
fn Budget::new(limits : Limits) -> Budget raise QueryError {
  limits.validate()
  { limits, steps: 0, peak_bindings: 0, path_pairs: 0, }
}

///|
fn Budget::tick(self : Budget, n~ : Int32 = 1) -> Unit raise QueryError {
  if n < 0 || n > self.limits.max_steps - self.steps {
    raise Resource("execution steps")
  }
  self.steps += n
}

///|
fn Budget::rows(self : Budget, n : Int32) -> Unit raise QueryError {
  if n > self.limits.max_bindings {
    raise Resource("intermediate bindings")
  }
  if n > self.peak_bindings {
    self.peak_bindings = n
  }
}

///|
fn Budget::path(self : Budget) -> Unit raise QueryError {
  if self.path_pairs >= self.limits.max_path_pairs {
    raise Resource("property path pairs")
  }
  self.path_pairs += 1
}

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

///|
pub extend Limits with Eq::{equal, not_equal}

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