///|
pub(all) struct PointMatch {
  left_index : Int
  right_index : Int
  distance : Double
} derive(Debug, Eq)

///|
pub fn nearest_point(
  point : @core.Point2,
  candidates : ArrayView[@core.Point2],
) -> PointMatch raise @core.GeometryError {
  if candidates.length() == 0 {
    raise @core.GeometryError::NotEnoughPoints("nearest point needs candidates")
  }
  let mut best = 0
  let mut best_distance = point.minus(candidates[0]).norm()
  for i in 1.. Array[PointMatch] raise @core.GeometryError {
  if maximum_distance <= 0.0 {
    raise @core.GeometryError::DegenerateInput(
      "match distance must be positive",
    )
  }
  let result : Array[PointMatch] = []
  for i in 0.. Array[PointMatch] raise @core.GeometryError {
  let result : Array[PointMatch] = []
  let forward = nearest_matches(left, right, maximum_distance~)
  for item in forward {
    let backward = nearest_point(right[item.right_index], left)
    if backward.right_index == item.left_index {
      result.push(item)
    }
  }
  result
}

///|
pub fn match_distance_statistics(
  matches : ArrayView[PointMatch],
) -> (Double, Double) raise @core.GeometryError {
  if matches.length() == 0 {
    raise @core.GeometryError::NotEnoughPoints("match statistics need matches")
  }
  let values : Array[Double] = []
  for item in matches {
    values.push(item.distance)
  }
  (@core.mean(values), @core.median(values))
}

///|
pub fn filter_matches(
  matches : ArrayView[PointMatch],
  maximum_distance~ : Double,
) -> Array[PointMatch] {
  let result : Array[PointMatch] = []
  for item in matches {
    if item.distance <= maximum_distance {
      result.push(item)
    }
  }
  result
}

///|
pub fn match_indices(
  matches : ArrayView[PointMatch],
) -> (Array[Int], Array[Int]) {
  let left : Array[Int] = []
  let right : Array[Int] = []
  for item in matches {
    left.push(item.left_index)
    right.push(item.right_index)
  }
  (left, right)
}

///|
pub fn unique_right_matches(
  matches : ArrayView[PointMatch],
) -> Array[PointMatch] {
  let result : Array[PointMatch] = []
  for item in matches {
    let mut exists = false
    for previous in result {
      if previous.right_index == item.right_index {
        exists = true
      }
    }
    if !exists {
      result.push(item)
    }
  }
  result
}