///|
fn measure(
  source_ids : Array[UInt64],
  target_ids : Array[UInt64],
  config : @reconcile.Config,
) -> (Double, Double, Double, Double) raise {
  let start = @bench.monotonic_clock_start()
  let source = @reconcile.Snapshot::from_sorted_ids(source_ids.iter(), config)
  let target = @reconcile.Snapshot::from_sorted_ids(target_ids.iter(), config)
  let build_us = @bench.monotonic_clock_end(start)
  let wire_start = @bench.monotonic_clock_start()
  let received = @reconcile.Snapshot::decode(
    target.encode(),
    @reconcile.DecodeLimits::new(),
  )
  let wire_us = @bench.monotonic_clock_end(wire_start)
  let decode_start = @bench.monotonic_clock_start()
  let outcome = @reconcile.reconcile(source, received, @reconcile.Policy::new())
  let reconcile_us = @bench.monotonic_clock_end(decode_start)
  match outcome {
    Ready(plan) => {
      let inventory = @reconcile.Inventory::from_ids(source_ids)
      let apply_start = @bench.monotonic_clock_start()
      let result = inventory.apply(plan)
      let apply_us = @bench.monotonic_clock_end(apply_start)
      assert_eq(result.ids(), target_ids)
      (build_us, wire_us, reconcile_us, apply_us)
    }
    _ => fail("benchmark failed to recover; no timing row is emitted")
  }
}

///|
fn run() -> Unit raise {
  println(
    "members,changed_each_side,seed,width,received_bytes,build_two_us,encode_decode_us,reconcile_us,apply_us",
  )
  for members in [1000, 10000] {
    for changed in [0, 10, 100] {
      for seed in [1UL, 17UL] {
        let capacity = @reconcile.CapacityPlan::new(changed * 2, seed~)
        let config = capacity.initial()
        let source = Array::makei(members, fn(i) { i.to_uint64() })
        let target = Array::makei(members, fn(i) { (i + changed).to_uint64() })
        // Discard one warmup; report means of five verified repetitions.
        measure(source, target, config) |> ignore
        let mut build = 0.0
        let mut wire = 0.0
        let mut recover = 0.0
        let mut apply = 0.0
        for round = 0; round < 5; round = round + 1 {
          let (a, b, c, d) = measure(source, target, config)
          build += a
          wire += b
          recover += c
          apply += d
        }
        println(
          "\{members},\{changed},\{seed},\{config.width()},\{config.wire_bytes()},\{build / 5.0},\{wire / 5.0},\{recover / 5.0},\{apply / 5.0}",
        )
      }
    }
  }
}

///|
fn main {
  run() catch {
    error => abort(error.to_string())
  }
}