///|
pub(all) struct SimulationScenario {
  name : String
  grid : Grid1D
  particles : Int
  steps : Int
  dt : Double
  boundary : Boundary1D
  shape : ShapeKind
  seed : Int
} derive(Debug, ToJson)

///|
pub fn standard_scenario(
  name : String,
  cells : Int,
  particles : Int,
  steps : Int,
) -> SimulationScenario {
  {
    name,
    grid: Grid1D::new(cells.max(2), 1.0),
    particles: particles.max(0),
    steps: steps.max(0),
    dt: 1.0e-4,
    boundary: Periodic,
    shape: CIC,
    seed: deterministic_seed(name, cells + particles + steps),
  }
}

///|
pub fn scenario_with_dt(
  scenario : SimulationScenario,
  dt : Double,
) -> SimulationScenario {
  { ..scenario, dt, }
}

///|
pub fn scenario_with_boundary(
  scenario : SimulationScenario,
  boundary : Boundary1D,
) -> SimulationScenario {
  { ..scenario, boundary, }
}

///|
pub fn scenario_with_shape(
  scenario : SimulationScenario,
  shape : ShapeKind,
) -> SimulationScenario {
  { ..scenario, shape, }
}

///|
pub fn scenario_initial_state(scenario : SimulationScenario) -> PicState {
  let particles = sample_particles(
    scenario.grid,
    scenario.particles,
    scenario.seed,
    0.0,
    1.0e4,
  )
  PicState::new(scenario.grid, particles)
}

///|
pub fn scenario_step(
  state : PicState,
  scenario : SimulationScenario,
) -> PicState {
  let stepped = state.step(scenario.dt)
  let particles = apply_particle_boundary_batch(
      scenario.grid,
      scenario.boundary,
      stepped.particles,
    ).alive
  let density = deposit_charge_with_shape(
    scenario.grid,
    particles,
    scenario.shape,
  )
  {
    grid: scenario.grid,
    particles,
    field: solve_periodic_field(scenario.grid, density),
    time: stepped.time,
  }
}

///|
pub fn scenario_run(scenario : SimulationScenario) -> PicState {
  let mut state = scenario_initial_state(scenario)
  for _ in 0.. Array[PicDiagnostics] {
  let output : Array[PicDiagnostics] = []
  let mut state = scenario_initial_state(scenario)
  output.push(PicDiagnostics::from_state(state))
  for _ in 0.. Int {
  scenario.particles
}

///|
pub fn scenario_work_units(scenario : SimulationScenario) -> Int {
  scenario.grid.cells * scenario.particles * scenario.steps
}

///|
pub fn scenario_description(scenario : SimulationScenario) -> String {
  "\{scenario.name}: cells=\{scenario.grid.cells}, particles=\{scenario.particles}, steps=\{scenario.steps}, dt=\{scenario.dt}, shape=\{shape_name(scenario.shape)}"
}

///|
pub fn scenario_diagnostics_csv(scenario : SimulationScenario) -> String {
  diagnostics_to_csv(scenario_trace(scenario))
}

///|
pub fn scenario_charge_error(scenario : SimulationScenario) -> Double {
  let diagnostics = scenario_trace(scenario)
  if diagnostics.length() == 0 {
    0.0
  } else {
    (diagnostics[diagnostics.length() - 1].total_charge -
    diagnostics[0].total_charge).abs()
  }
}