///|
/// Reproducible workload configuration for the command-line benchmark.
pub struct BenchmarkConfig {
  requested_sample_count : Int
  iterations : Int
  sampling_rate_hz : Double
  body_mass_kg : Double
} derive(Debug, Eq)

///|
pub fn BenchmarkConfig::new(
  requested_sample_count : Int,
  iterations : Int,
  sampling_rate_hz : Double,
  body_mass_kg : Double,
) -> BenchmarkConfig {
  {
    requested_sample_count: if requested_sample_count < 1 {
      1
    } else {
      requested_sample_count
    },
    iterations: if iterations < 1 {
      1
    } else {
      iterations
    },
    sampling_rate_hz: if sampling_rate_hz <= 0.0 || sampling_rate_hz.is_nan() {
      1000.0
    } else {
      sampling_rate_hz
    },
    body_mass_kg: if body_mass_kg <= 0.0 || body_mass_kg.is_nan() {
      70.0
    } else {
      body_mass_kg
    },
  }
}

///|
pub struct BenchmarkResult {
  iterations : Int
  requested_sample_count : Int
  actual_sample_count : Int
  elapsed_ms : Int
  trials_per_second : Double
  mean_jump_height_m : Double
  checksum_peak_force_n : Double
} derive(Debug, Eq)

///|
pub fn BenchmarkResult::empty() -> BenchmarkResult {
  {
    iterations: 0,
    requested_sample_count: 0,
    actual_sample_count: 0,
    elapsed_ms: 0,
    trials_per_second: 0.0,
    mean_jump_height_m: 0.0,
    checksum_peak_force_n: 0.0,
  }
}

///|
/// Runs the complete synthetic-data CMJ pipeline repeatedly. The returned
/// checksum makes it difficult for a benchmark caller to accidentally measure
/// dead code or omit the analysis result.
pub fn run_cmj_benchmark(config : BenchmarkConfig) -> BenchmarkResult {
  let start_ms = @env.now()
  let options = @pipeline.PipelineOptions::default()
  let mut height_sum = 0.0
  let mut peak_sum = 0.0
  let mut actual_samples = 0
  for iteration = 0; iteration < config.iterations; iteration = iteration + 1 {
    let generated = generate_synthetic_cmj_trial(
      "benchmark-" + iteration.to_string(),
      config.sampling_rate_hz,
      config.body_mass_kg,
    )
    let trial = if generated.length() > config.requested_sample_count {
      generated.slice(0, config.requested_sample_count)
    } else {
      generated
    }
    actual_samples = trial.length()
    let report = @pipeline.run_cmj(trial, config.body_mass_kg, options)
    match report.cmj_report {
      Some(cmj) => {
        height_sum = height_sum + cmj.jump_height_flight_m
        peak_sum = peak_sum + cmj.peak_force_n
      }
      None => ()
    }
  }
  let elapsed_ms = @env.now().to_int() - start_ms.to_int()
  let safe_elapsed = if elapsed_ms < 0 { 0 } else { elapsed_ms }
  let iterations = config.iterations
  {
    iterations,
    requested_sample_count: config.requested_sample_count,
    actual_sample_count: actual_samples,
    elapsed_ms: safe_elapsed,
    trials_per_second: if safe_elapsed == 0 {
      0.0
    } else {
      iterations.to_double() * 1000.0 / safe_elapsed.to_double()
    },
    mean_jump_height_m: height_sum / iterations.to_double(),
    checksum_peak_force_n: peak_sum,
  }
}

///|
pub fn format_benchmark_result(result : BenchmarkResult) -> String {
  "iterations=" +
  result.iterations.to_string() +
  " samples=" +
  result.actual_sample_count.to_string() +
  " elapsed_ms=" +
  result.elapsed_ms.to_string() +
  " trials_per_second=" +
  result.trials_per_second.to_string() +
  " mean_jump_height_m=" +
  result.mean_jump_height_m.to_string() +
  " checksum_peak_force_n=" +
  result.checksum_peak_force_n.to_string()
}