///|
/// Update an inlier estimate without restarting a running solver.
pub fn update_inlier_ratio(
  previous : Double,
  observed : Int,
  total : Int,
  momentum? : Double = 0.25,
) -> Double {
  if total <= 0 {
    return previous.max(0.0).min(1.0)
  }
  let current = observed.to_double() / total.to_double()
  let alpha = momentum.max(0.0).min(1.0)
  previous.max(0.0).min(1.0) * (1.0 - alpha) + current * alpha
}

///|
pub(all) struct AdaptiveConsensus {
  mut ratio : Double
  threshold : Double
  mut iterations : Int
}

///|
pub fn AdaptiveConsensus::new(threshold~ : Double) -> AdaptiveConsensus {
  { ratio: 0.0, threshold: threshold.max(0.0), iterations: 0 }
}

///|
pub fn AdaptiveConsensus::update(
  state : AdaptiveConsensus,
  residuals : ArrayView[Double],
) -> Unit {
  let mut count = 0
  for residual in residuals {
    if @core.abs(residual) <= state.threshold {
      count += 1
    }
  }
  state.ratio = update_inlier_ratio(state.ratio, count, residuals.length())
  state.iterations += 1
}

///|
pub fn AdaptiveConsensus::ratio(state : AdaptiveConsensus) -> Double {
  state.ratio
}

///|
pub fn AdaptiveConsensus::iterations(state : AdaptiveConsensus) -> Int {
  state.iterations
}

///|
pub fn AdaptiveConsensus::should_stop(
  state : AdaptiveConsensus,
  minimum_ratio : Double,
  minimum_iterations : Int,
) -> Bool {
  state.iterations >= minimum_iterations && state.ratio >= minimum_ratio
}

///|
pub fn adaptive_threshold(
  residuals : ArrayView[Double],
  floor : Double,
  multiplier : Double,
) -> Double raise @core.GeometryError {
  if residuals.length() == 0 {
    raise @core.GeometryError::NotEnoughPoints(
      "adaptive threshold needs residuals",
    )
  }
  let mut sum = 0.0
  for residual in residuals {
    sum += @core.abs(residual)
  }
  (sum / residuals.length().to_double() * multiplier).max(floor)
}