///|
/// Start a cross-calendar deadline comparison.
pub fn DeadlineMatrix::new(
  start~ : Date,
  rule~ : DateRule,
  start_rule~ : StartDateRule,
  extension~ : ExtensionPolicy,
) -> DeadlineMatrix {
  { start, rule, start_rule, extension, scenarios: [] }
}

///|
/// Add a named calendar scenario.
pub fn DeadlineMatrix::add_calendar(
  self : DeadlineMatrix,
  name : String,
  calendar : Calendar,
) -> Result[DeadlineMatrix, MatrixError] {
  for scenario in self.scenarios {
    if scenario.name == name {
      return Err(MatrixDuplicate(name))
    }
  }
  let scenarios = self.scenarios.copy()
  scenarios.push({ name, calendar })
  Ok({ ..self, scenarios, })
}

///|
/// Calculate scenarios in insertion order.
pub fn DeadlineMatrix::calculate(
  self : DeadlineMatrix,
) -> Result[MatrixResult, MatrixError] {
  if self.scenarios.is_empty() {
    return Err(MatrixEmpty)
  }
  let entries : Array[MatrixEntry] = Array::new()
  for scenario in self.scenarios {
    let deadline = Deadline::new(
      start_date=self.start,
      rule=self.rule,
      start_rule=self.start_rule,
      calendar=scenario.calendar,
      extension=self.extension,
    )
    match deadline.calculate() {
      Ok(result) => entries.push({ name: scenario.name, result })
      Err(error) => return Err(MatrixDeadlineFailure(error))
    }
  }
  Ok({ entries, })
}

///|
fn matrix_index(entries : Array[MatrixEntry], name : String) -> Int {
  let mut index = 0
  while index < entries.length() {
    if entries[index].name == name {
      return index
    }
    index = index + 1
  }
  -1
}

///|
/// Return scenario names in calculation order.
pub fn MatrixResult::ordered(self : MatrixResult) -> Array[String] {
  [
    for entry in self.entries => entry.name
  ]
}

///|
/// Look up one scenario result.
pub fn MatrixResult::get(self : MatrixResult, name : String) -> DeadlineResult? {
  let index = matrix_index(self.entries, name)
  if index < 0 {
    None
  } else {
    Some(self.entries[index].result)
  }
}

///|
/// Difference in adjusted calendar days between two scenarios.
pub fn MatrixResult::delta(
  self : MatrixResult,
  left : String,
  right : String,
) -> Result[Int, MatrixError] {
  let left_index = matrix_index(self.entries, left)
  let right_index = matrix_index(self.entries, right)
  if left_index < 0 {
    return Err(MatrixMissing(left))
  }
  if right_index < 0 {
    return Err(MatrixMissing(right))
  }
  let left_date = self.entries[left_index].result.adjusted_date()
  let right_date = self.entries[right_index].result.adjusted_date()
  Ok(
    days_from_civil(right_date.year, right_date.month, right_date.day) -
    days_from_civil(left_date.year, left_date.month, left_date.day),
  )
}