///|
pub(all) struct PlasmaBenchmarkCase {
  name : String
  cells : Int
  particles : Int
  steps : Int
  initial_energy : Double
  final_energy : Double
  charge_error : Double
} derive(Debug, ToJson)

///|
pub(all) struct PlasmaBenchmarkReport {
  cases : Array[PlasmaBenchmarkCase]
  total_steps : Int
} derive(Debug, ToJson)

///|
fn make_pic_case(
  name : String,
  cells : Int,
  particles : Int,
  steps : Int,
) -> PlasmaBenchmarkCase {
  let grid = Grid1D::new(cells, 1.0)
  let initial = PicState::new(grid, two_stream_particles(particles, 1.0, 1.0e5))
  let initial_energy = kinetic_energy(initial.particles) +
    field_energy(initial.field)
  let final_state = initial.run(1.0e-12, steps)
  let final_energy = kinetic_energy(final_state.particles) +
    field_energy(final_state.field)
  {
    name,
    cells,
    particles,
    steps,
    initial_energy,
    final_energy,
    charge_error: (total_particle_charge(final_state.particles) -
    total_particle_charge(initial.particles)).abs(),
  }
}

///|
fn make_landau_case() -> PlasmaBenchmarkCase {
  let config = VlasovConfig::new(
    Grid1D::new(32, 1.0),
    velocity_min=-4.0,
    velocity_max=4.0,
    velocity_bins=32,
    dt=0.001,
  )
  let initial = make_landau_initial(config, density0=1.0e12, thermal_speed=1.0)
  let final_state = initial.step().step()
  let initial_mass = distribution_sum(config, initial.distribution)
  let final_mass = distribution_sum(config, final_state.distribution)
  {
    name: "landau-advection",
    cells: config.grid.cells,
    particles: config.velocity_bins,
    steps: 2,
    initial_energy: initial_mass,
    final_energy: final_mass,
    charge_error: (final_mass - initial_mass).abs(),
  }
}

///|
pub fn run_plasma_benchmark_catalog() -> PlasmaBenchmarkReport {
  let cases = [
    make_pic_case("two-stream-small", 32, 64, 8),
    make_pic_case("two-stream-medium", 64, 128, 16),
    make_pic_case("two-stream-boundary", 16, 32, 4),
    make_landau_case(),
  ]
  let total_steps = cases.fold(init=0, fn(total, sample) {
    total + sample.steps
  })
  { cases, total_steps }
}

///|
pub fn benchmark_report_csv(report : PlasmaBenchmarkReport) -> String {
  let out = StringBuilder::new()
  out.write_string(
    "name,cells,particles,steps,initial_energy,final_energy,charge_error\n",
  )
  for sample in report.cases {
    out.write_string(
      "\{sample.name},\{sample.cells},\{sample.particles},\{sample.steps},\{sample.initial_energy},\{sample.final_energy},\{sample.charge_error}\n",
    )
  }
  out.to_string()
}