///|
pub(all) struct VlasovStepReport {
  before_mass : Double
  after_mass : Double
  mass_error : Double
  minimum : Double
  maximum : Double
  time : Double
} derive(Debug, ToJson)

///|
pub fn vlasov_advect_x(state : VlasovState, velocity : Double) -> VlasovState {
  let shift = velocity * state.config.dt
  vlasov_shift_distribution(state, shift)
}

///|
pub fn vlasov_clip_negative(state : VlasovState) -> VlasovState {
  let distribution = distribution_positive_part(state)
  {
    ..state,
    distribution,
    charge_density: vlasov_density(state.config, distribution),
  }
}

///|
pub fn vlasov_rescale_mass(state : VlasovState, target : Double) -> VlasovState {
  let current = distribution_integral(state)
  if current == 0.0 {
    state
  } else {
    let factor = target / current
    let distribution = state.distribution.map(fn(value) { value * factor })
    {
      ..state,
      distribution,
      charge_density: vlasov_density(state.config, distribution),
    }
  }
}

///|
pub fn vlasov_step_report(state : VlasovState) -> VlasovStepReport {
  let next = state.step()
  let before_mass = distribution_integral(state)
  let after_mass = distribution_integral(next)
  {
    before_mass,
    after_mass,
    mass_error: (after_mass - before_mass).abs(),
    minimum: distribution_minimum(next),
    maximum: distribution_maximum(next),
    time: next.time,
  }
}

///|
pub fn vlasov_run_reports(
  state : VlasovState,
  steps : Int,
) -> Array[VlasovStepReport] {
  let reports : Array[VlasovStepReport] = []
  let mut current = state
  for _ in 0.. VlasovState {
  vlasov_rescale_mass(state, reference_mass)
}

///|
pub fn vlasov_charge_from_density(
  _state : VlasovState,
  density : ArrayView[Double],
) -> Array[Double] {
  density.map(fn(value) { -elementary_charge * value })
}

///|
pub fn vlasov_distribution_difference(
  first : VlasovState,
  second : VlasovState,
) -> Array[Double] {
  let output = zeros(first.distribution.length())
  for i in 0.. Double {
  l2_norm(vlasov_distribution_difference(first, second))
}

///|
pub fn vlasov_report_to_csv(reports : ArrayView[VlasovStepReport]) -> String {
  let output = StringBuilder()
  output.write_string(
    "before_mass,after_mass,mass_error,minimum,maximum,time\n",
  )
  for report in reports {
    output.write_string(
      "\{report.before_mass},\{report.after_mass},\{report.mass_error},\{report.minimum},\{report.maximum},\{report.time}\n",
    )
  }
  output.to_string()
}

///|
pub fn vlasov_cfl(config : VlasovConfig) -> Double {
  let max_velocity = config.velocity_min.abs().max(config.velocity_max.abs())
  safe_ratio(config.grid.dx, max_velocity, 0.0)
}

///|
pub fn vlasov_is_positive(state : VlasovState) -> Bool {
  distribution_minimum(state) >= 0.0
}

///|
pub fn vlasov_is_normalized(
  state : VlasovState,
  expected : Double,
  tolerance : Double,
) -> Bool {
  (distribution_integral(state) - expected).abs() <= tolerance
}