///|
pub struct Detector {
  config : Config
  mut step : Int
  mut log_posterior : Array[Double]
  mut counts : Array[Int]
  mut sums : Array[Double]
}

///|
pub fn Detector::new(config : Config) -> Detector {
  { config, step: 0, log_posterior: [0.0], counts: [0], sums: [0.0], }
}

///|
pub fn Detector::steps(self : Detector) -> Int {
  self.step
}

///|
pub fn Detector::run_length_posterior(self : Detector) -> Array[Double] {
  self.log_posterior.map(fn(value) { @math.exp(value) })
}

///|
pub fn Detector::snapshot(self : Detector) -> Snapshot {
  {
    step: self.step,
    log_posterior: self.log_posterior.copy(),
    counts: self.counts.copy(),
    sums: self.sums.copy(),
  }
}

///|
pub fn Detector::restore(self : Detector, snapshot : Snapshot) -> Unit {
  self.step = snapshot.step
  self.log_posterior = snapshot.log_posterior.copy()
  self.counts = snapshot.counts.copy()
  self.sums = snapshot.sums.copy()
}

///|
fn Detector::predictive(
  self : Detector,
  count : Int,
  sum : Double,
  value : Double,
) -> Double {
  let model = self.config.model
  let effective = model.prior_strength + count.to_double()
  let mean = (model.prior_strength * model.prior_mean + sum) / effective
  normal_log_density(
    value,
    mean,
    model.observation_variance * (1.0 + 1.0 / effective),
  )
}

///|
pub fn Detector::update(
  self : Detector,
  observation : Double,
) -> Result[Update, DetectError] {
  if !finite(observation) {
    return Err(NonFiniteObservation)
  }
  let old_size = self.log_posterior.length()
  let new_size = if old_size < self.config.max_run_length {
    old_size + 1
  } else {
    old_size
  }
  let next_log = Array::make(new_size, negative_infinity)
  let next_counts = Array::make(new_size, 0)
  let next_sums = Array::make(new_size, 0.0)
  let reset_predictive = self.predictive(0, 0.0, observation)
  // The previous run-length posterior is normalized, so reset mass is
  // hazard times prior predictive without a per-state log-sum-exp reduction.
  let changed = self.config.hazard_log + reset_predictive
  for run in 0.. Result[Array[Update], DetectError] {
  let updates : Array[Update] = []
  for observation in observations {
    match self.update(observation) {
      Ok(value) => updates.push(value)
      Err(error) => return Err(error)
    }
  }
  Ok(updates)
}