///|
pub(all) struct PicDiagnostics {
  time : Double
  particles : Int
  total_charge : Double
  kinetic_energy : Double
  field_energy : Double
  center_of_mass : Double
  mean_velocity : Double
  velocity_variance : Double
} derive(Debug, ToJson)

///|
pub fn PicDiagnostics::from_state(state : PicState) -> PicDiagnostics {
  {
    time: state.time,
    particles: state.particles.length(),
    total_charge: total_particle_charge(state.particles),
    kinetic_energy: kinetic_energy(state.particles),
    field_energy: field_energy(state.field),
    center_of_mass: center_of_mass(state.grid, state.particles),
    mean_velocity: velocity_mean(state.particles),
    velocity_variance: velocity_variance(state.particles),
  }
}

///|
pub fn PicDiagnostics::total_energy(d : PicDiagnostics) -> Double {
  d.kinetic_energy + d.field_energy
}

///|
pub fn diagnostics_to_csv(samples : ArrayView[PicDiagnostics]) -> String {
  let out = StringBuilder()
  out.write_string(
    "time,particles,total_charge,kinetic_energy,field_energy,total_energy,center_of_mass,mean_velocity,velocity_variance\n",
  )
  for d in samples {
    out.write_string(
      "\{d.time},\{d.particles},\{d.total_charge},\{d.kinetic_energy},\{d.field_energy},\{d.total_energy()},\{d.center_of_mass},\{d.mean_velocity},\{d.velocity_variance}\n",
    )
  }
  out.to_string()
}

///|
pub fn trace_pic(
  state : PicState,
  dt : Double,
  steps : Int,
  every? : Int = 1,
) -> Array[PicDiagnostics] {
  let samples : Array[PicDiagnostics] = []
  let mut current = state
  for i in 0..<=steps {
    if i % every == 0 {
      samples.push(PicDiagnostics::from_state(current))
    }
    if i < steps {
      current = current.step(dt)
    }
  }
  samples
}