///|
pub(all) enum DriftLevel {
  Stable
  Watch
  Drift
} derive(Eq, Debug)

///|
/// Per-bucket comparison in basis points.
pub(all) struct BucketDelta {
  index : Int
  lower : Int
  upper : Int
  reference_bp : Int
  current_bp : Int
  delta_bp : Int
} derive(Eq, Debug)

///|
/// Drift report comparing a baseline histogram with a current histogram.
pub(all) struct DriftReport {
  level : DriftLevel
  score_bp : Int
  max_delta_bp : Int
  shifted_buckets : Int
  reference_total : Int
  current_total : Int
  buckets : Array[BucketDelta]
} derive(Eq, Debug)

///|
pub fn compare_histograms(
  reference : Histogram,
  current : Histogram,
) -> DriftReport {
  compare_histograms_with_policy(reference, current, DriftPolicy::default())
}

///|
pub fn compare_histograms_with_policy(
  reference : Histogram,
  current : Histogram,
  policy : DriftPolicy,
) -> DriftReport {
  let bucket_count = min_int(reference.counts.length(), current.counts.length())
  let deltas : Array[BucketDelta] = []
  let mut total_delta = 0
  let mut max_delta = 0
  let mut shifted = 0
  for i = 0; i < bucket_count; i = i + 1 {
    let reference_bp = reference.share_bp_at(i)
    let current_bp = current.share_bp_at(i)
    let delta = abs_int(current_bp - reference_bp)
    if delta > max_delta {
      max_delta = delta
    }
    if delta >= 500 {
      shifted = shifted + 1
    }
    total_delta = total_delta + delta
    deltas.push({
      index: i,
      lower: reference.spec.lower_bound(i),
      upper: reference.spec.upper_bound(i),
      reference_bp,
      current_bp,
      delta_bp: delta,
    })
  }
  let score = total_delta / 2
  {
    level: policy.classify(score, max_delta, shifted),
    score_bp: score,
    max_delta_bp: max_delta,
    shifted_buckets: shifted,
    reference_total: reference.total,
    current_total: current.total,
    buckets: deltas,
  }
}

///|
pub fn DriftReport::is_alert(self : DriftReport) -> Bool {
  match self.level {
    Drift => true
    _ => false
  }
}

///|
pub fn DriftReport::level_name(self : DriftReport) -> String {
  match self.level {
    Stable => "stable"
    Watch => "watch"
    Drift => "drift"
  }
}

///|
fn abs_int(value : Int) -> Int {
  if value < 0 {
    -value
  } else {
    value
  }
}

///|
fn min_int(a : Int, b : Int) -> Int {
  if a < b {
    a
  } else {
    b
  }
}