///|
/// Deterministic sample-set diagnostics for reproducible RANSAC runs.
pub(all) struct SampleBatchSummary {
  samples : Int
  dimensions : Int
  unique : Int
  duplicate_count : Int
  coverage : Double
  score : Double
} derive(Debug, Eq)

///|
pub fn sample_batch_unique(indices : ArrayView[Int]) -> Array[Int] {
  let result : Array[Int] = []
  for index in indices {
    if !result.contains(index) {
      result.push(index)
    }
  }
  result
}

///|
pub fn sample_batch_duplicate_count(indices : ArrayView[Int]) -> Int {
  indices.length() - sample_batch_unique(indices).length()
}

///|
pub fn sample_batch_minimum(indices : ArrayView[Int]) -> Int {
  if indices.length() == 0 {
    0
  } else {
    let mut result = indices[0]
    for index in indices {
      result = result.min(index)
    }
    result
  }
}

///|
pub fn sample_batch_maximum(indices : ArrayView[Int]) -> Int {
  let mut result = 0
  for index in indices {
    result = result.max(index)
  }
  result
}

///|
pub fn sample_batch_span(indices : ArrayView[Int]) -> Int {
  sample_batch_maximum(indices) - sample_batch_minimum(indices)
}

///|
pub fn sample_batch_coverage(
  indices : ArrayView[Int],
  population : Int,
) -> Double {
  if population <= 0 {
    0.0
  } else {
    sample_batch_unique(indices).length().to_double() / population.to_double()
  }
}

///|
pub fn sample_batch_is_valid(
  indices : ArrayView[Int],
  population : Int,
  minimum : Int,
) -> Bool {
  indices.length() >= minimum.max(0) &&
  sample_batch_duplicate_count(indices) == 0 &&
  sample_batch_minimum(indices) >= 0 &&
  sample_batch_maximum(indices) < population
}

///|
pub fn sample_batch_out_of_range(
  indices : ArrayView[Int],
  population : Int,
) -> Array[Int] {
  let result : Array[Int] = []
  for index in indices {
    if index < 0 || index >= population {
      result.push(index)
    }
  }
  result
}

///|
pub fn sample_batch_sorted(indices : ArrayView[Int]) -> Array[Int] {
  let result = indices.to_owned()
  result.sort()
  result
}

///|
pub fn sample_batch_delta(indices : ArrayView[Int]) -> Array[Int] {
  let sorted = sample_batch_sorted(indices)
  let result : Array[Int] = []
  if sorted.length() < 2 {
    return result
  }
  for i in 0..<(sorted.length() - 1) {
    result.push(sorted[i + 1] - sorted[i])
  }
  result
}

///|
pub fn sample_batch_mean_delta(indices : ArrayView[Int]) -> Double {
  let deltas = sample_batch_delta(indices)
  if deltas.length() == 0 {
    0.0
  } else {
    let mut total = 0
    for delta in deltas {
      total += delta
    }
    total.to_double() / deltas.length().to_double()
  }
}

///|
pub fn sample_batch_evenness(indices : ArrayView[Int]) -> Double {
  let deltas = sample_batch_delta(indices)
  if deltas.length() == 0 {
    0.0
  } else {
    let mean = sample_batch_mean_delta(indices)
    let mut error = 0.0
    for delta in deltas {
      error += (delta.to_double() - mean).abs()
    }
    1.0 / (1.0 + error / deltas.length().to_double())
  }
}

///|
pub fn sample_batch_summary(
  indices : ArrayView[Int],
  population : Int,
  dimensions : Int,
) -> SampleBatchSummary {
  let unique = sample_batch_unique(indices).length()
  let duplicates = indices.length() - unique
  let coverage = sample_batch_coverage(indices, population)
  {
    samples: indices.length(),
    dimensions: dimensions.max(0),
    unique,
    duplicate_count: duplicates,
    coverage,
    score: coverage *
    sample_batch_evenness(indices) *
    (if duplicates == 0 { 1.0 } else { 0.5 }),
  }
}

///|
pub fn sample_batch_intersection(
  left : ArrayView[Int],
  right : ArrayView[Int],
) -> Array[Int] {
  let result : Array[Int] = []
  for index in sample_batch_unique(left) {
    if right.contains(index) {
      result.push(index)
    }
  }
  result
}

///|
pub fn sample_batch_difference(
  left : ArrayView[Int],
  right : ArrayView[Int],
) -> Array[Int] {
  let result : Array[Int] = []
  for index in sample_batch_unique(left) {
    if !right.contains(index) {
      result.push(index)
    }
  }
  result
}

///|
pub fn sample_batch_union(
  left : ArrayView[Int],
  right : ArrayView[Int],
) -> Array[Int] {
  sample_batch_unique(left.to_owned() + right.to_owned())
}

///|
pub fn sample_batch_jaccard(
  left : ArrayView[Int],
  right : ArrayView[Int],
) -> Double {
  let intersection = sample_batch_intersection(left, right).length().to_double()
  let union = sample_batch_union(left, right).length().to_double()
  if union <= 0.0 {
    1.0
  } else {
    intersection / union
  }
}

///|
pub fn sample_batch_partition(
  indices : ArrayView[Int],
  partition_count : Int,
) -> Array[Array[Int]] {
  let result : Array[Array[Int]] = []
  let count = partition_count.max(1)
  for _ in 0.. Double {
  let parts = sample_batch_partition(indices, partition_count)
  if parts.length() == 0 {
    0.0
  } else {
    let mut minimum = parts[0].length()
    let mut maximum = parts[0].length()
    for part in parts {
      minimum = minimum.min(part.length())
      maximum = maximum.max(part.length())
    }
    if maximum == 0 {
      1.0
    } else {
      minimum.to_double() / maximum.to_double()
    }
  }
}

///|
pub fn sample_batch_stride(
  population : Int,
  stride : Int,
  offset : Int,
) -> Array[Int] {
  let result : Array[Int] = []
  let step = stride.max(1)
  let start = offset.max(0)
  let end = population.max(start)
  for index in start.. Array[Int] {
  let result : Array[Int] = []
  let first = start.max(0)
  let last = (first + width.max(0)).min(population.max(0))
  for index in first.. Array[Int] {
  let result : Array[Int] = []
  if indices.length() == 0 {
    return result
  }
  for i in 0.. Array[Int] {
  let result : Array[Int] = []
  for _ in 0..