///|
pub fn tariff_candidate(
  id : String,
  priority : Int,
  effective_from : Int,
  effective_to : Int,
  predicates : Array[TariffPredicate],
  charge : Money,
) -> TariffCandidate {
  { id, priority, effective_from, effective_to, predicates, charge }
}

///|
fn predicate_matches(
  predicate : TariffPredicate,
  parcel : ParcelDim,
  zone : String,
  declared_value : Money,
) -> Bool {
  match predicate {
    WeightAtLeast(value) => parcel.weight_g >= value
    WeightAtMost(value) => parcel.weight_g <= value
    LongestSideAtMost(value) => parcel.longest_side_cm() <= value
    ZoneIs(expected) => zone == expected
    DeclaredValueAtLeast(value) => declared_value.cents >= value.cents
  }
}

///|
fn candidate_matches(
  candidate : TariffCandidate,
  parcel : ParcelDim,
  zone : String,
  declared_value : Money,
  day : Int,
) -> Bool {
  if day < candidate.effective_from || day > candidate.effective_to {
    false
  } else {
    let mut matches = true
    for predicate in candidate.predicates {
      if !predicate_matches(predicate, parcel, zone, declared_value) {
        matches = false
      }
    }
    matches
  }
}

///|
pub fn choose_tariff(
  candidates : Array[TariffCandidate],
  parcel : ParcelDim,
  zone : String,
  declared_value : Money,
  day : Int,
) -> DecisionTrace {
  let mut selected = ""
  let mut selected_priority = -1
  let mut considered = 0
  let rejected : Array[String] = []
  let reasons : Array[String] = []
  for candidate in candidates {
    considered += 1
    if candidate_matches(candidate, parcel, zone, declared_value, day) {
      if candidate.priority > selected_priority {
        if selected.length() > 0 {
          rejected.push(selected)
        }
        selected = candidate.id
        selected_priority = candidate.priority
      } else {
        rejected.push(candidate.id)
      }
    } else {
      rejected.push(candidate.id)
    }
  }
  if selected.length() == 0 {
    reasons.push("no applicable candidate")
  } else {
    reasons.push("highest priority applicable candidate")
  }
  { selected, considered, rejected, reasons }
}

///|
pub fn TariffCandidate::is_well_formed(self : TariffCandidate) -> Bool {
  self.id.length() > 0 &&
  self.priority >= 0 &&
  self.effective_to >= self.effective_from &&
  self.charge.cents >= 0
}

///|
pub fn TariffCandidate::active_on(self : TariffCandidate, day : Int) -> Bool {
  day >= self.effective_from && day <= self.effective_to
}

///|
pub fn DecisionTrace::has_selection(self : DecisionTrace) -> Bool {
  self.selected.length() > 0
}

///|
pub fn DecisionTrace::rejection_count(self : DecisionTrace) -> Int {
  self.rejected.length()
}

///|
pub fn DecisionTrace::explanation(self : DecisionTrace) -> String {
  if self.selected.length() == 0 {
    "no tariff selected"
  } else {
    "selected=" + self.selected + " reason=" + self.reasons[0]
  }
}