///|
pub(all) struct ScenarioReport {
  rankings : Array[Ranking]
  rank_counts : Array[Array[Int32]]
  winner_counts : Array[Int32]
  mean_ranks : Array[Double]
  best_ranks : Array[Int32]
  worst_ranks : Array[Int32]
  universally_best : Array[String]
  pairwise_wins : Array[Array[Int32]]
  pairwise_ties : Array[Array[Int32]]
} derive(@debug.Debug)

///|
/// Frequencies describe only the supplied finite scenario grid, not probabilities.
pub fn weight_scenarios(
  m : DecisionMatrix,
  scenarios : Array[Array[Double]],
  algorithm : Method,
  tolerance~ : Double = 1.0e-10,
) -> ScenarioReport raise ChoiceError {
  let (n, p) = m.shape()
  let count = scenarios.length()
  if count == 0 || count > 64 {
    raise Budget("scenario count must be 1..64")
  }
  if n * n * p * count > 16777216 {
    raise Budget("scenario work exceeds 16777216 cell comparisons")
  }
  let rankings = scenarios.map(w => evaluate(m, w, algorithm, tolerance~))
  let rank_counts : Array[Array[Int32]] = [
    for _ in 0.. Array::make(n, 0)
  ]
  let winner_counts = Array::make(n, 0)
  let mean_ranks = Array::make(n, 0.0)
  let best_ranks = Array::make(n, n)
  let worst_ranks = Array::make(n, 1)
  let pairwise_wins : Array[Array[Int32]] = [
    for _ in 0.. Array::make(n, 0)
  ]
  let pairwise_ties : Array[Array[Int32]] = [
    for _ in 0.. Array::make(n, 0)
  ]
  for ranking in rankings {
    for i in 0.. worst_ranks[i] {
        worst_ranks[i] = rank
      }
      for j in 0.. m.names[i]
  ]
  {
    rankings,
    rank_counts,
    winner_counts,
    mean_ranks,
    best_ranks,
    worst_ranks,
    universally_best,
    pairwise_wins,
    pairwise_ties,
  }
}

///|
/// Vary one weight, redistributing the rest proportionally to baseline weights.
pub fn weight_grid(
  weights : Array[Double],
  criterion : Int32,
  values : Array[Double],
) -> Array[Array[Double]] raise ChoiceError {
  let base = normalize_weights(weights)
  if criterion < 0 || criterion >= base.length() {
    raise Invalid("criterion index out of range")
  }
  if values.length() == 0 || values.length() > 64 {
    raise Budget("grid size must be 1..64")
  }
  let rest = sum([ for j in 0.. base[j] ])
  let result : Array[Array[Double]] = []
  for value in values {
    probability(value)
    let row = Array::make(base.length(), 0.0)
    if base.length() == 1 && value != 1.0 {
      raise Invalid("single criterion weight must be one")
    }
    row[criterion] = value
    for j in 0.. 0.0 {
        (1.0 - value) * base[j] / rest
      } else {
        (1.0 - value) / (base.length() - 1).to_double()
      }
    }
    result.push(row)
  }
  result
}

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