///|
pub(all) struct ScenarioExpectation {
  kind : String
  name : String
  expected : String
}

///|
pub(all) struct ScenarioFailure {
  kind : String
  name : String
  expected : String
  actual : String
}

///|
pub(all) struct ScenarioReport {
  name : String
  mut passed : Bool
  steps : Int
  final_tick : Int
  digest : UInt64
  mut failures : Array[ScenarioFailure]
}

///|
pub(all) struct Scenario {
  name : String
  seed : UInt64
  max_steps : Int
  mut expectations : Array[ScenarioExpectation]
}

///|
pub(all) struct ScenarioCase {
  scenario : Scenario
  sim : Sim
}

///|
pub(all) struct ScenarioSuite {
  name : String
  mut cases : Array[ScenarioCase]
}

///|
pub(all) struct ScenarioSuiteReport {
  name : String
  reports : Array[ScenarioReport]
}

///|
pub fn Scenario::new(
  name : String,
  seed? : UInt64 = 1UL,
  max_steps? : Int = 100000,
) -> Scenario {
  { name, seed, max_steps, expectations: [] }
}

///|
pub fn Scenario::expect_counter(
  self : Scenario,
  name : String,
  value : Int,
) -> Scenario {
  self.expectations.push({ kind: "counter", name, expected: value.to_string() })
  self
}

///|
pub fn Scenario::expect_counter_range(
  self : Scenario,
  name : String,
  min : Int,
  max : Int,
) -> Scenario {
  self.expectations.push({
    kind: "counter_range",
    name,
    expected: min.to_string() + ".." + max.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_gauge(
  self : Scenario,
  name : String,
  value : Int,
) -> Scenario {
  self.expectations.push({ kind: "gauge", name, expected: value.to_string() })
  self
}

///|
pub fn Scenario::expect_final_tick(self : Scenario, value : Int) -> Scenario {
  self.expectations.push({
    kind: "final_tick",
    name: "time",
    expected: value.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_final_tick_range(
  self : Scenario,
  min : Int,
  max : Int,
) -> Scenario {
  self.expectations.push({
    kind: "final_tick_range",
    name: "time",
    expected: min.to_string() + ".." + max.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_pending(self : Scenario, value : Int) -> Scenario {
  self.expectations.push({
    kind: "pending",
    name: "events",
    expected: value.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_digest(self : Scenario, value : UInt64) -> Scenario {
  self.expectations.push({
    kind: "digest",
    name: "trace",
    expected: value.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_trace_contains(
  self : Scenario,
  text : String,
) -> Scenario {
  self.expectations.push({
    kind: "trace_contains",
    name: "trace",
    expected: text,
  })
  self
}

///|
pub fn Scenario::expect_trace_kind_count(
  self : Scenario,
  kind : String,
  count : Int,
) -> Scenario {
  self.expectations.push({
    kind: "trace_kind_count",
    name: kind,
    expected: count.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_sample_count(
  self : Scenario,
  name : String,
  value : Int,
) -> Scenario {
  self.expectations.push({
    kind: "sample_count",
    name,
    expected: value.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_summary_min(
  self : Scenario,
  name : String,
  value : Int,
) -> Scenario {
  self.expectations.push({
    kind: "summary_min",
    name,
    expected: value.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_summary_max(
  self : Scenario,
  name : String,
  value : Int,
) -> Scenario {
  self.expectations.push({
    kind: "summary_max",
    name,
    expected: value.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_summary_average_range(
  self : Scenario,
  name : String,
  min : Int,
  max : Int,
) -> Scenario {
  self.expectations.push({
    kind: "summary_average_range",
    name,
    expected: min.to_string() + ".." + max.to_string(),
  })
  self
}

///|
pub fn Scenario::expect_no_validation_issues(self : Scenario) -> Scenario {
  self.expectations.push({ kind: "validation", name: "sim", expected: "PASS" })
  self
}

///|
pub fn Scenario::create_sim(self : Scenario) -> Sim {
  Sim::new(seed=self.seed)
}

///|
pub fn Scenario::verify(self : Scenario, sim : Sim) -> ScenarioReport {
  let steps = sim.run_until_idle(max_steps=self.max_steps)
  let report = {
    name: self.name,
    passed: true,
    steps,
    final_tick: sim.time(),
    digest: sim.digest(),
    failures: [],
  }
  for expectation in self.expectations {
    self.check_expectation(expectation, sim, report)
  }
  if report.failures.length() > 0 {
    report.passed = false
  }
  report
}

///|
fn Scenario::check_expectation(
  _self : Scenario,
  expectation : ScenarioExpectation,
  sim : Sim,
  report : ScenarioReport,
) -> Unit {
  let actual = match expectation.kind {
    "counter" => sim.metrics().counter(expectation.name).to_string()
    "counter_range" =>
      range_actual(
        sim.metrics().counter(expectation.name),
        expectation.expected,
      )
    "gauge" => sim.metrics().gauge(expectation.name).to_string()
    "final_tick" => sim.time().to_string()
    "final_tick_range" => range_actual(sim.time(), expectation.expected)
    "pending" => sim.pending_count().to_string()
    "digest" => sim.digest().to_string()
    "trace_contains" =>
      if sim.trace_text().contains(expectation.expected) {
        expectation.expected
      } else {
        ""
      }
    "trace_kind_count" =>
      trace_query_result(sim.trace()).kind_count(expectation.name).to_string()
    "sample_count" => sim.metrics().sample_count(expectation.name).to_string()
    "summary_min" => sim.metrics().summary(expectation.name).min.to_string()
    "summary_max" => sim.metrics().summary(expectation.name).max.to_string()
    "summary_average_range" =>
      range_actual(
        sim.metrics().summary(expectation.name).average().to_int(),
        expectation.expected,
      )
    "validation" => if sim.validate().passed() { "PASS" } else { "FAIL" }
    _ => ""
  }
  if actual != expectation.expected {
    report.failures.push({
      kind: expectation.kind,
      name: expectation.name,
      expected: expectation.expected,
      actual,
    })
  }
}

///|
fn range_actual(value : Int, expected : String) -> String {
  let bounds = parse_range(expected)
  if value >= bounds.min && value <= bounds.max {
    expected
  } else {
    value.to_string()
  }
}

///|
pub(all) struct IntRange {
  min : Int
  max : Int
}

///|
fn parse_range(text : String) -> IntRange {
  let mut split = -1
  for i in 0.. Int {
  let mut value = 0
  let mut sign = 1
  let mut i = start
  if i < end && text.code_unit_at(i).to_int() == 45 {
    sign = -1
    i += 1
  }
  while i < end {
    let code = text.code_unit_at(i).to_int()
    if code >= 48 && code <= 57 {
      value = value * 10 + (code - 48)
    }
    i += 1
  }
  value * sign
}

///|
pub fn ScenarioReport::summary(self : ScenarioReport) -> String {
  let status = if self.passed { "PASS" } else { "FAIL" }
  status +
  " " +
  self.name +
  " steps=" +
  self.steps.to_string() +
  " tick=" +
  self.final_tick.to_string() +
  " digest=" +
  self.digest.to_string()
}

///|
pub fn ScenarioReport::failure_text(self : ScenarioReport) -> String {
  let buf = StringBuilder::new()
  for i in 0.. 0 {
      buf.write_string("\n")
    }
    let failure = self.failures[i]
    buf.write_string(
      failure.kind +
      ":" +
      failure.name +
      " expected=" +
      failure.expected +
      " actual=" +
      failure.actual,
    )
  }
  buf.to_string()
}

///|
pub fn ScenarioReport::detailed_text(self : ScenarioReport) -> String {
  let base = self.summary()
  if self.passed {
    base
  } else {
    base + "\n" + self.failure_text()
  }
}

///|
pub fn ScenarioReport::failure_count(self : ScenarioReport) -> Int {
  self.failures.length()
}

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

///|
pub fn ScenarioSuite::add(
  self : ScenarioSuite,
  scenario : Scenario,
  sim : Sim,
) -> ScenarioSuite {
  self.cases.push({ scenario, sim })
  self
}

///|
pub fn ScenarioSuite::run(self : ScenarioSuite) -> ScenarioSuiteReport {
  let reports : Array[ScenarioReport] = []
  for item in self.cases {
    reports.push(item.scenario.verify(item.sim))
  }
  { name: self.name, reports }
}

///|
pub fn ScenarioSuiteReport::passed(self : ScenarioSuiteReport) -> Bool {
  self.failed_count() == 0
}

///|
pub fn ScenarioSuiteReport::passed_count(self : ScenarioSuiteReport) -> Int {
  let mut count = 0
  for report in self.reports {
    if report.passed {
      count += 1
    }
  }
  count
}

///|
pub fn ScenarioSuiteReport::failed_count(self : ScenarioSuiteReport) -> Int {
  self.reports.length() - self.passed_count()
}

///|
pub fn ScenarioSuiteReport::summary(self : ScenarioSuiteReport) -> String {
  let status = if self.passed() { "PASS" } else { "FAIL" }
  status +
  " suite=" +
  self.name +
  " passed=" +
  self.passed_count().to_string() +
  " failed=" +
  self.failed_count().to_string()
}

///|
pub fn ScenarioSuiteReport::render(self : ScenarioSuiteReport) -> String {
  let buf = StringBuilder::new()
  buf.write_string(self.summary())
  for report in self.reports {
    buf.write_string("\n- " + report.summary())
    if !report.passed {
      buf.write_string("\n" + report.failure_text())
    }
  }
  buf.to_string()
}