///|
/// A positive number of simultaneously open nested operations.
pub struct Budget {
  priv units : Int32
} derive(Debug, Eq)

///|
/// Create a positive fuel budget, or raise `DataModel` for zero or less.
pub fn Budget::Budget(units : Int32) -> Budget raise IonError {
  guard units > 0 else {
    raise DataModel("a fuel budget must contain at least one unit")
  }
  { units, }
}

///|
/// The number of units in this budget.
pub fn Budget::units(self : Budget) -> Int32 {
  self.units
}

///|
/// Optional scoped fuel. Nesting is unrestricted unless the caller supplies
/// `Limited(Budget(...))`. Siblings reuse their enclosing operation's fuel.
/// Fuel bounds open operations, not total work, memory, or elapsed time.
pub(all) enum Fuel {
  Unlimited
  Limited(Budget)
} derive(Debug, Eq)

///|
/// The operation that tried to enter a nested scope after fuel ran out.
pub(all) enum FuelOperation {
  TextRead
  BinaryRead
  CstRead
  JsonRead
  SchemaLoad
  SchemaImport
  SchemaValidate
  RegexParse
  RegexCompile
} derive(Debug, Eq)

///|
/// Where fuel ran out. Text offsets count UTF-16 code units; binary offsets
/// count octets. Value paths use the schema validator's `$` notation.
pub(all) enum FuelLocation {
  TextOffset(Int32)
  /// Code-point offset within a regular expression.
  CharacterOffset(Int32)
  /// Index in a compiled regular expression's instruction program.
  RegexInstruction(Int32)
  BinaryOffset(Int32)
  ValuePath(String)
  SchemaId(String)
} derive(Debug, Eq)

///|
/// An immutable scoped fuel cursor used by the library's implementations.
#doc(hidden)
pub struct FuelState {
  priv fuel : Fuel
  priv level : Int32
}

///|
#doc(hidden)
pub fn Fuel::start(self : Fuel) -> FuelState {
  { fuel: self, level: 0, }
}

///|
/// Enter a scope. Keep the outer state to restore fuel on success or error.
#doc(hidden)
pub fn FuelState::enter(
  self : FuelState,
  operation : FuelOperation,
  location : FuelLocation,
) -> FuelState raise IonError {
  match self.fuel {
    Unlimited => self
    Limited(budget) => {
      if self.level >= budget.units {
        raise FuelExhausted(budget~, operation~, location~)
      }
      { fuel: self.fuel, level: self.level + 1, }
    }
  }
}

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

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

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

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

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

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

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

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