///|
pub fn require_non_empty_points(points : Array[Point]) -> Bool {
  points.length() > 0
}

///|
pub fn require_line_points(points : Array[Point]) -> Bool {
  points.length() >= 2 && distance(points[0], points[points.length() - 1]) > 0.0
}

///|
pub fn require_circle_points(points : Array[Point]) -> Bool {
  points.length() >= 3 && polygon_area(points) != 0.0
}

///|
pub fn require_image(image : GrayImage) -> Bool {
  image.width > 0 &&
  image.height > 0 &&
  image.pixels.length() == image.width * image.height
}

///|
pub fn require_mask(mask : BinaryMask) -> Bool {
  mask.width > 0 &&
  mask.height > 0 &&
  mask.values.length() == mask.width * mask.height
}

///|
pub fn require_tolerance(tolerance : Tolerance) -> Bool {
  tolerance.lower <= tolerance.nominal && tolerance.nominal <= tolerance.upper
}

///|
pub fn require_calibration(fit : CalibrationFit) -> Bool {
  calibration_is_usable(fit, 1.0) &&
  fit.transform.determinant().abs() > 0.0000001
}

///|
pub fn contract_diagnostics_image(
  image : GrayImage,
) -> Array[PipelineDiagnostic] {
  if require_image(image) {
    [diagnostic_ok("contract", "image shape is valid")]
  } else {
    [diagnostic_error("contract", "image shape is invalid")]
  }
}

///|
pub fn contract_diagnostics_mask(
  mask : BinaryMask,
) -> Array[PipelineDiagnostic] {
  if require_mask(mask) {
    [diagnostic_ok("contract", "mask shape is valid")]
  } else {
    [diagnostic_error("contract", "mask shape is invalid")]
  }
}

///|
pub fn contract_diagnostics_points(
  points : Array[Point],
  minimum : Int,
) -> Array[PipelineDiagnostic] {
  if points.length() >= minimum {
    [diagnostic_ok("contract", "point count is sufficient")]
  } else {
    [diagnostic_error("contract", "point count is insufficient")]
  }
}

///|
pub fn contract_diagnostics_tolerance(
  tolerance : Tolerance,
) -> Array[PipelineDiagnostic] {
  if require_tolerance(tolerance) {
    [diagnostic_ok("contract", "tolerance contains its nominal")]
  } else {
    [diagnostic_error("contract", "tolerance does not contain its nominal")]
  }
}

///|
pub fn contract_diagnostics_calibration(
  fit : CalibrationFit,
) -> Array[PipelineDiagnostic] {
  if require_calibration(fit) {
    [diagnostic_ok("contract", "calibration is usable")]
  } else {
    [diagnostic_error("contract", "calibration is degenerate or inaccurate")]
  }
}

///|
pub fn safe_fit_line(points : Array[Point]) -> Line? {
  if require_line_points(points) {
    Some(fit_line(points))
  } else {
    None
  }
}

///|
pub fn safe_fit_circle(points : Array[Point]) -> Circle? {
  if require_circle_points(points) {
    Some(detect_circle_from_points(points))
  } else {
    None
  }
}

///|
pub fn safe_fit_affine(
  source : Array[Point],
  target : Array[Point],
) -> CalibrationFit? {
  if source.length() >= 3 && source.length() == target.length() {
    Some(calibration_fit(source, target))
  } else {
    None
  }
}

///|
pub fn safe_measure_caliper(
  image : GrayImage,
  config : CaliperConfig,
  tolerance : Tolerance,
) -> PipelineResult {
  let diagnostics = contract_diagnostics_image(image)
  diagnostics.append(validate_caliper_config(config))
  diagnostics.append(contract_diagnostics_tolerance(tolerance))
  if diagnostics_ok(diagnostics) {
    measure_caliper(image, config, tolerance)
  } else {
    { measurements: [], diagnostics, accepted: false, confidence: 0.0 }
  }
}

///|
pub fn safe_label_components(mask : BinaryMask) -> Array[Component] {
  if require_mask(mask) {
    label_components(mask)
  } else {
    []
  }
}

///|
pub fn safe_contour_metrics(mask : BinaryMask) -> PolygonMetrics {
  polygon_metrics(convex_hull(contour_points(mask)))
}

///|
pub fn safe_calibration_report(fit : CalibrationFit) -> String {
  if require_calibration(fit) {
    calibration_report(fit)
  } else {
    "invalid calibration"
  }
}

///|
pub fn contract_summary(diagnostics : Array[PipelineDiagnostic]) -> String {
  decision_name(diagnostic_severity(diagnostics)) +
  ":" +
  diagnostics.length().to_string()
}

///|
pub fn contract_merge(
  first : Array[PipelineDiagnostic],
  second : Array[PipelineDiagnostic],
) -> Array[PipelineDiagnostic] {
  let result = first
  result.append(second)
  result
}

///|
pub fn contract_passed(diagnostics : Array[PipelineDiagnostic]) -> Bool {
  diagnostics_ok(diagnostics)
}

///|
pub fn stable_checksum(values : Array[Double]) -> Double {
  let mut result = 0.0
  for i in 0.. Double {
  stable_checksum(image.pixels)
}

///|
pub fn stable_checksum_measurements(
  measurements : Array[Measurement],
) -> Double {
  stable_checksum(measurement_values(measurements))
}

///|
pub fn stable_checksum_batch(batch : BatchRun) -> Double {
  stable_checksum_measurements(batch.records)
}

///|
pub fn deterministic_sort_values(values : Array[Double]) -> Array[Double] {
  let result : Array[Double] = []
  for value in values {
    let mut index = 0
    while index < result.length() && result[index] <= value {
      index += 1
    }
    result.insert(index, value)
  }
  result
}

///|
pub fn deterministic_sort_measurements(
  measurements : Array[Measurement],
) -> Array[Measurement] {
  stable_measurement_order(measurements)
}

///|
pub fn deterministic_sort_components(
  components : Array[Component],
) -> Array[Component] {
  let result : Array[Component] = []
  for component in components {
    let mut index = 0
    while index < result.length() && result[index].label <= component.label {
      index += 1
    }
    result.insert(index, component)
  }
  result
}

///|
pub fn api_surface_summary() -> String {
  "visual-caliper: image, profile, geometry, calibration, tolerance, quality, reports"
}