///|
pub(all) struct BenchmarkRecord {
  name : String
  cells : Int
  particles : Int
  steps : Int
  work_units : Int
  final_particles : Int
  charge_error : Double
  energy_drift : Double
} derive(Debug, ToJson)

///|
pub(all) struct BenchmarkSuite {
  records : Array[BenchmarkRecord]
} derive(Debug, ToJson)

///|
pub fn benchmark_scenario(scenario : SimulationScenario) -> BenchmarkRecord {
  let diagnostics = scenario_trace(scenario)
  let final_particles = if diagnostics.length() == 0 {
    0
  } else {
    diagnostics[diagnostics.length() - 1].particles
  }
  let charge_error = scenario_charge_error(scenario)
  let energies = diagnostics.map(fn(item) { item.total_energy() })
  {
    name: scenario.name,
    cells: scenario.grid.cells,
    particles: scenario.particles,
    steps: scenario.steps,
    work_units: scenario_work_units(scenario),
    final_particles,
    charge_error,
    energy_drift: if energies.length() == 0 {
      0.0
    } else {
      relative_change(energies[0], energies[energies.length() - 1])
    },
  }
}

///|
pub fn benchmark_suite() -> BenchmarkSuite {
  let records = [
    benchmark_scenario(standard_scenario("tiny", 8, 16, 2)),
    benchmark_scenario(standard_scenario("medium", 32, 128, 4)),
    benchmark_scenario(standard_scenario("large", 64, 256, 6)),
  ]
  { records, }
}

///|
pub fn BenchmarkSuite::total_work(suite : BenchmarkSuite) -> Int {
  suite.records.fold(init=0, fn(acc, record) { acc + record.work_units })
}

///|
pub fn BenchmarkSuite::maximum_charge_error(suite : BenchmarkSuite) -> Double {
  let mut maximum = 0.0
  for record in suite.records {
    maximum = maximum.max(record.charge_error)
  }
  maximum
}

///|
pub fn BenchmarkSuite::maximum_energy_drift(suite : BenchmarkSuite) -> Double {
  let mut maximum = 0.0
  for record in suite.records {
    maximum = maximum.max(record.energy_drift)
  }
  maximum
}

///|
pub fn benchmark_suite_to_csv(suite : BenchmarkSuite) -> String {
  let output = StringBuilder()
  output.write_string(
    "name,cells,particles,steps,work_units,final_particles,charge_error,energy_drift\n",
  )
  for record in suite.records {
    output.write_string(
      "\{record.name},\{record.cells},\{record.particles},\{record.steps},\{record.work_units},\{record.final_particles},\{record.charge_error},\{record.energy_drift}\n",
    )
  }
  output.to_string()
}

///|
pub fn benchmark_suite_summary(suite : BenchmarkSuite) -> String {
  "records=\{suite.records.length()}\nwork_units=\{suite.total_work()}\nmax_charge_error=\{suite.maximum_charge_error()}\nmax_energy_drift=\{suite.maximum_energy_drift()}\n"
}

///|
pub fn benchmark_record_to_json_like(record : BenchmarkRecord) -> String {
  "{name:\"\{record.name}\",cells:\{record.cells},particles:\{record.particles},steps:\{record.steps},work_units:\{record.work_units},charge_error:\{record.charge_error},energy_drift:\{record.energy_drift}}"
}