///|
pub struct WorkloadCounters {
  mut scenes : Int
  mut pixels : Int
  mut boxes : Int
  mut mask_pixels : Int
  mut flow_vectors : Int
  mut image_checksum : UInt
  mut annotation_checksum : UInt
} derive(Debug, Eq)

///|
pub fn WorkloadCounters::empty() -> WorkloadCounters {
  {
    scenes: 0,
    pixels: 0,
    boxes: 0,
    mask_pixels: 0,
    flow_vectors: 0,
    image_checksum: 2166136261,
    annotation_checksum: 2166136261,
  }
}

///|
pub fn WorkloadCounters::add_scene(
  self : WorkloadCounters,
  scene : SceneOutput,
) -> Unit {
  self.scenes += 1
  self.pixels += scene.image.width * scene.image.height
  self.boxes += scene.boxes.length()
  self.mask_pixels += scene.mask.count(1)
  self.flow_vectors += scene.flow.length()
  self.image_checksum = (self.image_checksum ^ image_checksum(scene.image)) *
    16777619
  self.annotation_checksum = (
      self.annotation_checksum ^ mask_checksum(scene.mask)
    ) *
    16777619
  for box in scene.boxes {
    self.annotation_checksum = (
        self.annotation_checksum ^ box.rect.x.reinterpret_as_uint()
      ) *
      16777619
    self.annotation_checksum = (
        self.annotation_checksum ^ box.rect.y.reinterpret_as_uint()
      ) *
      16777619
  }
}

///|
pub fn WorkloadCounters::add(
  self : WorkloadCounters,
  other : WorkloadCounters,
) -> Unit {
  self.scenes += other.scenes
  self.pixels += other.pixels
  self.boxes += other.boxes
  self.mask_pixels += other.mask_pixels
  self.flow_vectors += other.flow_vectors
  self.image_checksum = (self.image_checksum ^ other.image_checksum) * 16777619
  self.annotation_checksum = (
      self.annotation_checksum ^ other.annotation_checksum
    ) *
    16777619
}

///|
pub fn measure_workload(scenes : Array[SceneOutput]) -> WorkloadCounters {
  let counters = WorkloadCounters::empty()
  for scene in scenes {
    counters.add_scene(scene)
  }
  counters
}

///|
pub fn measure_plan_workload(plans : Array[ScenePlan]) -> WorkloadCounters {
  measure_workload(render_suite(plans))
}

///|
pub fn WorkloadCounters::mean_pixels(self : WorkloadCounters) -> Int {
  self.pixels / max_i(1, self.scenes)
}

///|
pub fn WorkloadCounters::annotation_density_per_1000(
  self : WorkloadCounters,
) -> Int {
  self.boxes * 1000 / max_i(1, self.scenes)
}

///|
pub fn WorkloadCounters::to_json(self : WorkloadCounters) -> String {
  "{\"scenes\":\{self.scenes},\"pixels\":\{self.pixels},\"boxes\":\{self.boxes},\"mask_pixels\":\{self.mask_pixels},\"flow_vectors\":\{self.flow_vectors},\"image_checksum\":\{self.image_checksum},\"annotation_checksum\":\{self.annotation_checksum},\"mean_pixels\":\{self.mean_pixels()}}"
}

///|
pub struct BenchmarkSnapshot {
  name : String
  counters : WorkloadCounters
  stable : Bool
} derive(Debug, Eq)

///|
pub fn BenchmarkSnapshot::capture(
  name~ : String,
  plans : Array[ScenePlan],
) -> BenchmarkSnapshot {
  let first = measure_plan_workload(plans)
  let second = measure_plan_workload(plans)
  { name, counters: first, stable: first == second }
}

///|
pub fn BenchmarkSnapshot::to_json(self : BenchmarkSnapshot) -> String {
  "{\"name\":\"\{escape_json(self.name)}\",\"stable\":\{self.stable},\"counters\":{self.counters.to_json()}}"
}

///|
pub fn compare_workloads(
  before : WorkloadCounters,
  after : WorkloadCounters,
) -> String {
  "{\"scene_delta\":\{after.scenes - before.scenes},\"pixel_delta\":\{after.pixels - before.pixels},\"box_delta\":\{after.boxes - before.boxes},\"flow_delta\":\{after.flow_vectors - before.flow_vectors},\"checksum_changed\":\{before.image_checksum != after.image_checksum}}"
}

///|
pub fn validate_workload(counters : WorkloadCounters) -> ValidationReport {
  let report = ValidationReport::new()
  if counters.scenes < 0 || counters.pixels < 0 {
    report.error("benchmark counters cannot be negative")
  }
  if counters.boxes < 0 || counters.flow_vectors < 0 {
    report.error("annotation counters cannot be negative")
  }
  if counters.pixels > 0 && counters.scenes == 0 {
    report.error("pixel count requires at least one scene")
  }
  report
}

///|
pub fn suite_benchmark_json(plans : Array[ScenePlan], name : String) -> String {
  BenchmarkSnapshot::capture(name~, plans).to_json()
}

///|
pub fn benchmark_checksum(plans : Array[ScenePlan]) -> UInt {
  let counters = measure_plan_workload(plans)
  counters.image_checksum ^ counters.annotation_checksum
}