///|
pub(all) struct SeedRun {
  seed : UInt64
  summary : ModelSummary
}

///|
pub(all) struct SeedMatrix {
  name : String
  runs : Array[SeedRun]
}

///|
pub(all) struct SeedMatrixComparison {
  left_name : String
  right_name : String
  same_digest : Bool
  same_count : Bool
  left_digest : UInt64
  right_digest : UInt64
  left_count : Int
  right_count : Int
}

///|
pub fn seed_run(seed : UInt64, summary : ModelSummary) -> SeedRun {
  { seed, summary }
}

///|
pub fn SeedMatrix::new(name : String) -> SeedMatrix {
  { name, runs: [] }
}

///|
pub fn SeedMatrix::add(self : SeedMatrix, run : SeedRun) -> SeedMatrix {
  self.runs.push(run)
  self
}

///|
pub fn SeedMatrix::count(self : SeedMatrix) -> Int {
  self.runs.length()
}

///|
pub fn SeedMatrix::is_empty(self : SeedMatrix) -> Bool {
  self.runs.length() == 0
}

///|
pub fn SeedMatrix::has_seed(self : SeedMatrix, seed : UInt64) -> Bool {
  for run in self.runs {
    if run.seed == seed {
      return true
    }
  }
  false
}

///|
pub fn SeedMatrix::combined_digest(self : SeedMatrix) -> UInt64 {
  let entries : Array[TraceEntry] = []
  for i in 0.. Int {
  let mut max = 0
  for run in self.runs {
    if run.summary.final_tick > max {
      max = run.summary.final_tick
    }
  }
  max
}

///|
pub fn SeedMatrix::total_events(self : SeedMatrix) -> Int {
  let mut total = 0
  for run in self.runs {
    total += run.summary.events
  }
  total
}

///|
pub fn SeedMatrix::slowest(self : SeedMatrix) -> SeedRun? {
  if self.runs.length() == 0 {
    None
  } else {
    let mut best = self.runs[0]
    for run in self.runs {
      if run.summary.final_tick > best.summary.final_tick {
        best = run
      }
    }
    Some(best)
  }
}

///|
pub fn SeedMatrix::fastest(self : SeedMatrix) -> SeedRun? {
  if self.runs.length() == 0 {
    None
  } else {
    let mut best = self.runs[0]
    for run in self.runs {
      if run.summary.final_tick < best.summary.final_tick {
        best = run
      }
    }
    Some(best)
  }
}

///|
pub fn SeedMatrix::unique_digests(self : SeedMatrix) -> Int {
  let digests : Array[UInt64] = []
  for run in self.runs {
    if !uint64_array_contains(digests, run.summary.digest) {
      digests.push(run.summary.digest)
    }
  }
  digests.length()
}

///|
fn uint64_array_contains(items : Array[UInt64], value : UInt64) -> Bool {
  for item in items {
    if item == value {
      return true
    }
  }
  false
}

///|
pub fn SeedMatrix::tick_span(self : SeedMatrix) -> Int {
  match (self.fastest(), self.slowest()) {
    (Some(fast), Some(slow)) =>
      slow.summary.final_tick - fast.summary.final_tick
    _ => 0
  }
}

///|
pub fn SeedMatrix::summary_line(self : SeedMatrix) -> String {
  self.name +
  " runs=" +
  self.count().to_string() +
  " unique_digests=" +
  self.unique_digests().to_string() +
  " max_tick=" +
  self.max_final_tick().to_string() +
  " tick_span=" +
  self.tick_span().to_string()
}

///|
pub fn SeedMatrix::lines(self : SeedMatrix) -> Array[String] {
  let lines : Array[String] = []
  for run in self.runs {
    lines.push("seed=" + run.seed.to_string() + " " + run.summary.line())
  }
  lines
}

///|
pub fn compare_seed_matrices(
  left : SeedMatrix,
  right : SeedMatrix,
) -> SeedMatrixComparison {
  {
    left_name: left.name,
    right_name: right.name,
    same_digest: left.combined_digest() == right.combined_digest(),
    same_count: left.count() == right.count(),
    left_digest: left.combined_digest(),
    right_digest: right.combined_digest(),
    left_count: left.count(),
    right_count: right.count(),
  }
}

///|
pub fn SeedMatrixComparison::matched(self : SeedMatrixComparison) -> Bool {
  self.same_digest && self.same_count
}

///|
pub fn SeedMatrixComparison::is_stable(self : SeedMatrixComparison) -> Bool {
  self.matched()
}

///|
pub fn SeedMatrixComparison::digest_changed(
  self : SeedMatrixComparison,
) -> Bool {
  !self.same_digest
}

///|
pub fn SeedMatrixComparison::count_changed(self : SeedMatrixComparison) -> Bool {
  !self.same_count
}

///|
pub fn SeedMatrixComparison::fully_changed(self : SeedMatrixComparison) -> Bool {
  self.digest_changed() && self.count_changed()
}

///|
pub fn SeedMatrixComparison::summary(self : SeedMatrixComparison) -> String {
  let status = if self.matched() { "matched" } else { "mismatch" }
  status +
  " left=" +
  self.left_name +
  " right=" +
  self.right_name +
  " left_count=" +
  self.left_count.to_string() +
  " right_count=" +
  self.right_count.to_string() +
  " left_digest=" +
  self.left_digest.to_string() +
  " right_digest=" +
  self.right_digest.to_string()
}

///|
pub fn SeedMatrix::render(self : SeedMatrix) -> String {
  let buf = StringBuilder::new()
  buf.write_string("# Seed Matrix " + self.name + "\n")
  buf.write_string(self.summary_line() + "\n")
  buf.write_string("events=" + self.total_events().to_string() + "\n")
  buf.write_string("digest=" + self.combined_digest().to_string() + "\n")
  for line in self.lines() {
    buf.write_string("- " + line + "\n")
  }
  match self.fastest() {
    None => ()
    Some(run) => buf.write_string("fastest_seed=" + run.seed.to_string() + "\n")
  }
  match self.slowest() {
    None => ()
    Some(run) => buf.write_string("slowest_seed=" + run.seed.to_string() + "\n")
  }
  buf.to_string()
}

///|
pub fn retry_seed_matrix(seeds : Array[UInt64]) -> SeedMatrix {
  let matrix = SeedMatrix::new("retry")
  for seed in seeds {
    ignore(
      matrix.add(
        seed_run(seed, retry_summary(run_retry_model(retry_config(seed~)))),
      ),
    )
  }
  matrix
}

///|
pub fn network_seed_matrix(seeds : Array[UInt64]) -> SeedMatrix {
  let matrix = SeedMatrix::new("network")
  for seed in seeds {
    ignore(
      matrix.add(
        seed_run(
          seed,
          network_summary(run_network_model(network_config(seed~))),
        ),
      ),
    )
  }
  matrix
}

///|
pub fn load_balancer_seed_matrix(
  seeds : Array[UInt64],
  strategy? : String = "least_queue",
) -> SeedMatrix {
  let matrix = SeedMatrix::new("load_balancer:" + strategy)
  for seed in seeds {
    ignore(
      matrix.add(
        seed_run(
          seed,
          load_balancer_summary(
            run_load_balancer_model(load_balancer_config(seed~, strategy~)),
          ),
        ),
      ),
    )
  }
  matrix
}