///|
pub fn effect_request(effect : EffectScope, budget : Budget) -> EffectRequest {
  { effect, budget, }
}

///|
pub fn EffectRequest::effect(self : EffectRequest) -> EffectScope {
  self.effect
}

///|
pub fn EffectRequest::budget(self : EffectRequest) -> Budget {
  self.budget
}

///|
fn EffectRequest::canonical(self : EffectRequest) -> String {
  self.effect.canonical() + "|" + self.budget.canonical()
}

///|
fn merge_exact_requests(
  requests : Array[EffectRequest],
) -> Array[EffectRequest] {
  let merged : Array[EffectRequest] = []
  for request in requests {
    let mut duplicate : Int? = None
    for index, current in merged {
      if current.effect == request.effect {
        duplicate = Some(index)
        break
      }
    }
    match duplicate {
      Some(index) =>
        merged[index] = {
          effect: merged[index].effect,
          budget: budget_union(merged[index].budget, request.budget),
        }
      None => merged.push(request)
    }
  }
  merged
}

///|
pub fn compile_plan(
  permit_id : String,
  requests : Array[EffectRequest],
) -> Permit raise PermitError {
  let id = permit_id.trim().to_owned()
  if id is "" {
    raise PermitError::EmptyPermitId
  }
  if requests.is_empty() {
    raise PermitError::EmptyPlan
  }
  let merged = merge_exact_requests(requests)
  merged.sort_by((left, right) => left.canonical().compare(right.canonical()))
  let grants : Array[Grant] = []
  for index, request in merged {
    grants.push({
      id: "grant-\{index + 1}",
      effect: request.effect,
      budget: request.budget,
    })
  }
  { id, grants, }
}

///|
pub fn Permit::id(self : Permit) -> String {
  self.id
}

///|
pub fn Permit::grants(self : Permit) -> Array[Grant] {
  self.grants.copy()
}

///|
pub fn Permit::grant_count(self : Permit) -> Int {
  self.grants.length()
}

///|
pub fn Grant::id(self : Grant) -> String {
  self.id
}

///|
pub fn Grant::effect(self : Grant) -> EffectScope {
  self.effect
}

///|
pub fn Grant::budget(self : Grant) -> Budget {
  self.budget
}

///|
fn Grant::canonical(self : Grant) -> String {
  self.id + "|" + self.effect.canonical() + "|" + self.budget.canonical()
}

///|
pub fn Permit::canonical(self : Permit) -> String {
  let lines = self.grants.map(grant => grant.canonical())
  "permit:" + self.id + "\n" + lines.join("\n")
}

///|
pub fn grant_contains(grant : Grant, requested : Grant) -> Bool {
  effect_contains(grant.effect, requested.effect) is Containment::Contained &&
  budget_contains(grant.budget, requested.budget)
}

///|
pub fn permit_contains(grant : Permit, requested : Permit) -> Bool {
  for requested_grant in requested.grants {
    let mut covered = false
    for available_grant in grant.grants {
      if grant_contains(available_grant, requested_grant) {
        covered = true
        break
      }
    }
    if !covered {
      return false
    }
  }
  true
}