///|
pub fn safe_pixel(image : GrayImage, point : Point, default : Double) -> Double {
  if image.inside(point.x.floor().to_int(), point.y.floor().to_int()) {
    image.sample_bilinear(point)
  } else {
    default
  }
}

///|
pub fn safe_bounds(width : Int, height : Int, bounds : Bounds) -> Bounds {
  {
    min_x: bounds.min_x.clamp(min=0, max=width.max(0)),
    min_y: bounds.min_y.clamp(min=0, max=height.max(0)),
    max_x: bounds.max_x.clamp(min=-1, max=width - 1),
    max_y: bounds.max_y.clamp(min=-1, max=height - 1),
  }
}

///|
pub fn bounds_from_center(center : Point, width : Int, height : Int) -> Bounds {
  let half_width = width.max(0) / 2
  let half_height = height.max(0) / 2
  {
    min_x: center.x.to_int() - half_width,
    min_y: center.y.to_int() - half_height,
    max_x: center.x.to_int() + half_width,
    max_y: center.y.to_int() + half_height,
  }
}

///|
pub fn bounds_intersection(first : Bounds, second : Bounds) -> Bounds {
  {
    min_x: first.min_x.max(second.min_x),
    min_y: first.min_y.max(second.min_y),
    max_x: first.max_x.min(second.max_x),
    max_y: first.max_y.min(second.max_y),
  }
}

///|
pub fn bounds_union(first : Bounds, second : Bounds) -> Bounds {
  {
    min_x: first.min_x.min(second.min_x),
    min_y: first.min_y.min(second.min_y),
    max_x: first.max_x.max(second.max_x),
    max_y: first.max_y.max(second.max_y),
  }
}

///|
pub fn bounds_is_empty(bounds : Bounds) -> Bool {
  bounds.max_x < bounds.min_x || bounds.max_y < bounds.min_y
}

///|
pub fn bounds_contains(bounds : Bounds, point : Point) -> Bool {
  point.x >= Double::from_int(bounds.min_x) &&
  point.x <= Double::from_int(bounds.max_x) &&
  point.y >= Double::from_int(bounds.min_y) &&
  point.y <= Double::from_int(bounds.max_y)
}

///|
pub fn vector_to_string(vector : Vector2) -> String {
  "(" + vector.x.to_string() + ", " + vector.y.to_string() + ")"
}

///|
pub fn point_to_string(point : Point) -> String {
  "(" + point.x.to_string() + ", " + point.y.to_string() + ")"
}

///|
pub fn line_to_string(line : Line) -> String {
  line.a.to_string() +
  "x + " +
  line.b.to_string() +
  "y + " +
  line.c.to_string() +
  " = 0"
}

///|
pub fn circle_to_string(circle : Circle) -> String {
  "center=" +
  point_to_string(circle.center) +
  "; radius=" +
  circle.radius.to_string() +
  "; rms=" +
  circle.rms_error.to_string()
}

///|
pub fn transform_to_string(transform : AffineTransform) -> String {
  "[" +
  transform.a.to_string() +
  "," +
  transform.b.to_string() +
  ";" +
  transform.c.to_string() +
  "," +
  transform.d.to_string() +
  ";" +
  transform.tx.to_string() +
  "," +
  transform.ty.to_string() +
  "]"
}

///|
pub fn unit_convert(value : Double, source : String, target : String) -> Double {
  if source == target {
    value
  } else if source == "mm" && target == "um" {
    value * 1000.0
  } else if source == "um" && target == "mm" {
    value / 1000.0
  } else if source == "in" && target == "mm" {
    value * 25.4
  } else if source == "mm" && target == "in" {
    value / 25.4
  } else if source == "px" || target == "px" {
    value
  } else {
    value
  }
}

///|
pub fn convert_measurement_unit(
  measurement : Measurement,
  target : String,
) -> Measurement {
  {
    ..measurement,
    value: unit_convert(measurement.value, measurement.unit, target),
    unit: target,
  }
}

///|
pub fn convert_tolerance_unit(
  tolerance : Tolerance,
  target : String,
) -> Tolerance {
  {
    nominal: unit_convert(tolerance.nominal, tolerance.unit, target),
    lower: unit_convert(tolerance.lower, tolerance.unit, target),
    upper: unit_convert(tolerance.upper, tolerance.unit, target),
    unit: target,
  }
}

///|
pub fn image_scale_values(image : GrayImage, factor : Double) -> GrayImage {
  let result = image.copy()
  for i in 0.. GrayImage {
  let result = image.copy()
  for i in 0.. GrayImage {
  let result = image.copy()
  for i in 0.. Double {
  mean_value(patch_difference(first, second).pixels)
}

///|
pub fn image_difference_maximum(
  first : GrayImage,
  second : GrayImage,
) -> Double {
  maximum_value(patch_difference(first, second).pixels)
}

///|
pub fn image_equal_with_tolerance(
  first : GrayImage,
  second : GrayImage,
  tolerance : Double,
) -> Bool {
  first.width == second.width &&
  first.height == second.height &&
  image_difference_maximum(first, second) <= tolerance.max(0.0)
}

///|
pub fn measurement_equal_with_tolerance(
  first : Measurement,
  second : Measurement,
  tolerance : Double,
) -> Bool {
  first.name == second.name &&
  first.unit == second.unit &&
  approx_equal(first.value, second.value, tolerance~)
}

///|
pub fn report_measurement_count(report : MeasurementReport) -> Int {
  report.measurements.length()
}