///|
/// Evidence at multiple temporal scales.
pub struct ScaleEvidence {
  short_score : Double
  medium_score : Double
  long_score : Double
  consensus : Double
  changed : Bool
}

///|
pub fn ScaleEvidence::empty() -> ScaleEvidence {
  {
    short_score: 0.0,
    medium_score: 0.0,
    long_score: 0.0,
    consensus: 0.0,
    changed: false,
  }
}

///|
pub struct MultiScaleDetector {
  short : RobustZDetector
  medium : RobustZDetector
  long : TrendShiftDetector
  minimum_consensus : Double
  mut index : Int
}

///|
pub fn MultiScaleDetector::new(
  short? : Int = 8,
  medium? : Int = 24,
  long? : Int = 48,
  minimum_consensus? : Double = 0.5,
) -> MultiScaleDetector {
  {
    short: RobustZDetector::new(window_size=short),
    medium: RobustZDetector::new(window_size=medium),
    long: TrendShiftDetector::new(window_size=long),
    minimum_consensus: clamp_probability(minimum_consensus),
    index: 0,
  }
}

///|
pub fn MultiScaleDetector::update(
  self : MultiScaleDetector,
  value : Double,
) -> DetectionResult {
  self.index += 1
  let short_result = self.short.update(value)
  let medium_result = self.medium.update(value)
  let long_result = self.long.update(value)
  let mut votes = 0
  if short_result.changed {
    votes += 1
  }
  if medium_result.changed {
    votes += 1
  }
  if long_result.changed {
    votes += 1
  }
  let consensus = votes.to_double() / 3.0
  let best = if short_result.score > medium_result.score {
    if short_result.score > long_result.score {
      short_result
    } else {
      long_result
    }
  } else if medium_result.score > long_result.score {
    medium_result
  } else {
    long_result
  }
  {
    changed: consensus >= self.minimum_consensus,
    score: best.score,
    confidence: clamp_probability(consensus * best.confidence),
    direction: best.direction,
    index: self.index,
    evidence: consensus,
  }
}

///|
pub fn scale_profile(
  values : Array[Double],
  sizes : Array[Int],
) -> Array[ScaleEvidence] {
  let result : Array[ScaleEvidence] = []
  for size in sizes {
    let window = DoubleWindow::new(size)
    let mut previous = 0.0
    let mut score = 0.0
    for value in values {
      ignore(window.push(value))
      score = absolute(window.mean() - previous)
      previous = window.mean()
    }
    result.push({
      short_score: score,
      medium_score: window.standard_deviation(),
      long_score: window.slope(),
      consensus: clamp_probability(score),
      changed: score > 0.0,
    })
  }
  result
}

///|
pub fn adaptive_threshold(
  scores : Array[Double],
  false_positive_rate? : Double = 0.05,
) -> Double {
  threshold_for_false_positive(scores, false_positive_rate)
}

///|
pub fn consensus_score(results : Array[DetectionResult]) -> Double {
  if results.length() == 0 {
    return 0.0
  }
  let mut total = 0.0
  for result in results {
    total += if result.changed { 1.0 } else { 0.0 }
  }
  total / results.length().to_double()
}