///|
pub(all) struct RansacConfig {
  iterations : Int
  threshold : Double
  min_inliers : Int
  seed : UInt
} derive(Debug, Eq)

///|
pub fn RansacConfig::new(
  iterations? : Int = 128,
  threshold? : Double = 2.0,
  min_inliers? : Int = 4,
  seed? : UInt = 1U,
) -> RansacConfig {
  { iterations, threshold, min_inliers, seed }
}

///|
pub(all) struct InlierMask {
  values : Array[Bool]
} derive(Debug, Eq)

///|
pub(all) struct RansacResult[T] {
  model : T
  inliers : InlierMask
  inlier_count : Int
  score : Double
} derive(Debug, Eq)

///|
fn lcg_next(state : UInt) -> UInt {
  state * 1664525U + 1013904223U
}

///|
fn pick_index(state : UInt, len : Int) -> Int {
  (state % len.reinterpret_as_uint()).reinterpret_as_int()
}

///|
fn sample_four(len : Int, seed : UInt, iter : Int) -> (Int, Int, Int, Int) {
  let s0 = lcg_next(seed + iter.reinterpret_as_uint() * 747796405U)
  let i0 = pick_index(s0, len)
  let s1 = lcg_next(s0)
  let i1 = (pick_index(s1, len - 1) + i0 + 1) % len
  let s2 = lcg_next(s1)
  let i2 = (pick_index(s2, len - 2) + i1 + 1) % len
  let s3 = lcg_next(s2)
  let i3 = (pick_index(s3, len - 3) + i2 + 1) % len
  (i0, i1, i2, i3)
}

///|
fn make_four(
  points : ArrayView[@core.Point2],
  indices : (Int, Int, Int, Int),
) -> Array[@core.Point2] {
  let (i0, i1, i2, i3) = indices
  [points[i0], points[i1], points[i2], points[i3]]
}

///|
pub fn estimate_homography_ransac(
  src : ArrayView[@core.Point2],
  dst : ArrayView[@core.Point2],
  config? : RansacConfig = RansacConfig::new(),
) -> RansacResult[@multiview.Homography] raise @core.GeometryError {
  if src.length() != dst.length() || src.length() < 4 {
    raise @core.GeometryError::NotEnoughPoints(
      "homography RANSAC needs at least four paired points",
    )
  }
  let fallback = @multiview.estimate_homography_from_four(src, dst)
  let empty_mask = InlierMask::{ values: [] }
  let initial = RansacResult::{
    model: fallback,
    inliers: empty_mask,
    inlier_count: -1,
    score: 1.0e100,
  }
  for best = initial, iter = 0; iter < config.iterations; {
    let indices = sample_four(src.length(), config.seed, iter)
    let candidate_src = make_four(src, indices)
    let candidate_dst = make_four(dst, indices)
    let candidate = try
      @multiview.estimate_homography_from_four(candidate_src, candidate_dst)
    catch {
      _ => best
    } noraise {
      h => {
        let mask : Array[Bool] = []
        let mut count = 0
        let mut score = 0.0
        for i in 0.. 1.0e100
          }
          let ok = err <= config.threshold
          mask.push(ok)
          if ok {
            count += 1
            score += err
          }
        }
        if count > best.inlier_count ||
          (count == best.inlier_count && score < best.score) {
          RansacResult::{
            model: h,
            inliers: InlierMask::{ values: mask },
            inlier_count: count,
            score,
          }
        } else {
          best
        }
      }
    }
    continue candidate, iter + 1
  } nobreak {
    if best.inlier_count < config.min_inliers {
      raise @core.GeometryError::DegenerateInput(
        "RANSAC could not find enough homography inliers",
      )
    }
    best
  }
}

///|
pub fn estimate_fundamental_ransac(
  left : ArrayView[@core.Point2],
  right : ArrayView[@core.Point2],
  config? : RansacConfig = RansacConfig::new(),
) -> RansacResult[@multiview.FundamentalMatrix] raise @core.GeometryError {
  if left.length() != right.length() || left.length() < 2 {
    raise @core.GeometryError::NotEnoughPoints(
      "fundamental RANSAC needs paired points",
    )
  }
  let model = @multiview.estimate_fundamental_from_translation(
    @core.Vec3::new(x=1.0, y=0.0, z=0.0),
  )
  let mask : Array[Bool] = []
  let mut count = 0
  let mut score = 0.0
  for i in 0..