///|
pub(all) struct VerificationBundle {
  scenario : SimulationScenario
  manifest : ReproducibilityManifest
  fingerprint : ResultFingerprint
  metrics : Array[QualityMetric]
  invariants : Array[InvariantCheck]
} derive(Debug, ToJson)

///|
pub fn build_verification_bundle(
  scenario : SimulationScenario,
  toolchain : String,
) -> VerificationBundle {
  let state = scenario_run(scenario)
  let initial_charge = total_particle_charge(
    scenario_initial_state(scenario).particles,
  )
  {
    scenario,
    manifest: manifest_from_scenario(scenario, toolchain),
    fingerprint: fingerprint_from_scenario(scenario),
    metrics: scenario_quality_metrics(scenario),
    invariants: pic_invariants(state, initial_charge),
  }
}

///|
pub fn VerificationBundle::passed(bundle : VerificationBundle) -> Bool {
  all_metrics_pass(bundle.metrics) &&
  invariant_all(bundle.invariants) &&
  result_is_well_formed(bundle.scenario, bundle.fingerprint)
}

///|
pub fn VerificationBundle::work_units(bundle : VerificationBundle) -> Int {
  manifest_work_units(bundle.manifest)
}

///|
pub fn VerificationBundle::summary(bundle : VerificationBundle) -> String {
  "name=\{bundle.manifest.name}\npassed=\{bundle.passed()}\nwork_units=\{bundle.work_units()}\nchecksum=\{bundle.fingerprint.checksum}\n"
}

///|
pub fn VerificationBundle::to_csv(bundle : VerificationBundle) -> String {
  let output = StringBuilder()
  output.write_string("section,key,value\n")
  output.write_string("manifest,name,\{bundle.manifest.name}\n")
  output.write_string("manifest,seed,\{bundle.manifest.seed}\n")
  output.write_string("manifest,cells,\{bundle.manifest.cells}\n")
  output.write_string("manifest,particles,\{bundle.manifest.particles}\n")
  output.write_string("manifest,steps,\{bundle.manifest.steps}\n")
  output.write_string("manifest,dt,\{bundle.manifest.dt}\n")
  output.write_string("manifest,shape,\{bundle.manifest.shape}\n")
  output.write_string("manifest,boundary,\{bundle.manifest.boundary}\n")
  output.write_string(
    "fingerprint,sample_count,\{bundle.fingerprint.sample_count}\n",
  )
  output.write_string(
    "fingerprint,work_units,\{bundle.fingerprint.work_units}\n",
  )
  output.write_string(
    "fingerprint,charge_error,\{bundle.fingerprint.charge_error}\n",
  )
  output.write_string(
    "fingerprint,energy_drift,\{bundle.fingerprint.energy_drift}\n",
  )
  output.write_string("fingerprint,checksum,\{bundle.fingerprint.checksum}\n")
  output.write_string(
    "quality,metrics_pass,\{all_metrics_pass(bundle.metrics)}\n",
  )
  output.write_string(
    "quality,invariants_pass,\{invariant_all(bundle.invariants)}\n",
  )
  output.to_string()
}

///|
pub fn VerificationBundle::metric_report(bundle : VerificationBundle) -> String {
  quality_report(bundle.metrics)
}

///|
pub fn VerificationBundle::invariant_report(
  bundle : VerificationBundle,
) -> String {
  invariant_report(bundle.invariants)
}

///|
pub fn VerificationBundle::reproducibility_report(
  bundle : VerificationBundle,
) -> String {
  reproducibility_report(bundle.scenario, bundle.manifest.toolchain)
}

///|
pub fn verification_bundle_to_text(bundle : VerificationBundle) -> String {
  let output = StringBuilder()
  output.write_string(bundle.summary())
  output.write_string("\n")
  output.write_string(bundle.metric_report())
  output.write_string("\n")
  output.write_string(bundle.invariant_report())
  output.write_string("\n")
  output.write_string(bundle.reproducibility_report())
  output.to_string()
}

///|
pub fn verification_bundle_records(
  bundle : VerificationBundle,
) -> Array[QualityMetric] {
  bundle.metrics.copy()
}

///|
pub fn verification_bundle_check_count(bundle : VerificationBundle) -> Int {
  bundle.metrics.length() + bundle.invariants.length()
}

///|
pub fn verification_bundle_checksum(bundle : VerificationBundle) -> Int {
  let metrics_hash = fingerprint_values(
    bundle.metrics.map(fn(item) { item.value }),
  )
  let invariant_hash = fingerprint_values(
    bundle.invariants.map(fn(item) { item.observed }),
  )
  (metrics_hash * 31 + invariant_hash + bundle.fingerprint.checksum) %
  2147483647
}

///|
pub fn verification_bundle_schema() -> String {
  "section,key,value"
}

///|
pub fn verification_bundle_is_deterministic(
  scenario : SimulationScenario,
  toolchain : String,
) -> Bool {
  let first = build_verification_bundle(scenario, toolchain)
  let second = build_verification_bundle(scenario, toolchain)
  verification_bundle_checksum(first) == verification_bundle_checksum(second)
}

///|
pub fn verification_bundle_passes_thresholds(
  bundle : VerificationBundle,
  charge : Double,
  energy : Double,
) -> Bool {
  bundle.fingerprint.charge_error <= charge &&
  bundle.fingerprint.energy_drift <= energy
}

///|
pub fn verification_bundle_expected_work(bundle : VerificationBundle) -> Int {
  bundle.scenario.grid.cells * bundle.scenario.particles * bundle.scenario.steps
}

///|
pub fn verification_bundle_matches_work(bundle : VerificationBundle) -> Bool {
  bundle.work_units() == verification_bundle_expected_work(bundle)
}

///|
pub fn verification_bundle_status(bundle : VerificationBundle) -> String {
  if bundle.passed() && verification_bundle_matches_work(bundle) {
    "pass"
  } else {
    "review"
  }
}