///|
pub fn contour_points(mask : BinaryMask) -> Array[Point] {
  let result : Array[Point] = []
  let boundary = mask.boundary()
  for y in 0.. Array[Array[Point]] {
  let loops : Array[Array[Point]] = []
  let boundary = mask.boundary()
  let visited = BinaryMask(width=mask.width, height=mask.height)
  for y in 0.. Double {
  if points.length() < 3 {
    0.0
  } else {
    let mut total = 0.0
    for i in 0.. Double {
  if points.length() < 2 {
    0.0
  } else {
    let mut total = 0.0
    for i in 0.. Point {
  if points.is_empty() {
    Point::Point(x=0.0, y=0.0)
  } else {
    let signed_area = polygon_area(points)
    if signed_area.abs() <= 0.0000000001 {
      let mut x = 0.0
      let mut y = 0.0
      for point in points {
        x += point.x
        y += point.y
      }
      Point::Point(
        x=x / Double::from_int(points.length()),
        y=y / Double::from_int(points.length()),
      )
    } else {
      let mut x = 0.0
      let mut y = 0.0
      for i in 0.. Bounds {
  if points.is_empty() {
    { min_x: 0, min_y: 0, max_x: -1, max_y: -1 }
  } else {
    let mut min_x = points[0].x.floor().to_int()
    let mut min_y = points[0].y.floor().to_int()
    let mut max_x = points[0].x.ceil().to_int()
    let mut max_y = points[0].y.ceil().to_int()
    for point in points[1:] {
      min_x = min_x.min(point.x.floor().to_int())
      min_y = min_y.min(point.y.floor().to_int())
      max_x = max_x.max(point.x.ceil().to_int())
      max_y = max_y.max(point.y.ceil().to_int())
    }
    { min_x, min_y, max_x, max_y }
  }
}

///|
pub fn polygon_metrics(points : Array[Point]) -> PolygonMetrics {
  let area = polygon_area(points).abs()
  let perimeter = polygon_perimeter(points)
  let bounds = polygon_bounds(points)
  let box_area = Double::from_int(
    (bounds.max_x - bounds.min_x + 1).max(0) *
    (bounds.max_y - bounds.min_y + 1).max(0),
  )
  {
    area,
    perimeter,
    centroid: polygon_centroid(points),
    circularity: if perimeter <= 0.0 {
      0.0
    } else {
      4.0 * 3.141592653589793 * area / (perimeter * perimeter)
    },
    rectangularity: if box_area <= 0.0 {
      0.0
    } else {
      area / box_area
    },
  }
}

///|
fn lexicographic_before(a : Point, b : Point) -> Bool {
  a.x < b.x || (a.x == b.x && a.y < b.y)
}

///|
fn cross_at(origin : Point, a : Point, b : Point) -> Double {
  (a.x - origin.x) * (b.y - origin.y) - (a.y - origin.y) * (b.x - origin.x)
}

///|
pub fn convex_hull(points : Array[Point]) -> Array[Point] {
  if points.length() < 3 {
    points
  } else {
    let ordered : Array[Point] = []
    for point in points {
      let mut index = 0
      while index < ordered.length() &&
            lexicographic_before(ordered[index], point) {
        index += 1
      }
      ordered.insert(index, point)
    }
    let lower : Array[Point] = []
    for point in ordered {
      while lower.length() >= 2 &&
            cross_at(
              lower[lower.length() - 2],
              lower[lower.length() - 1],
              point,
            ) <=
            0.0 {
        ignore(lower.pop())
      }
      lower.push(point)
    }
    let upper : Array[Point] = []
    let mut reverse_index = ordered.length() - 1
    while reverse_index >= 0 {
      let point = ordered[reverse_index]
      while upper.length() >= 2 &&
            cross_at(
              upper[upper.length() - 2],
              upper[upper.length() - 1],
              point,
            ) <=
            0.0 {
        ignore(upper.pop())
      }
      upper.push(point)
      reverse_index -= 1
    }
    ignore(lower.pop())
    ignore(upper.pop())
    lower.append(upper)
    lower
  }
}

///|
pub fn simplify_polygon(
  points : Array[Point],
  tolerance : Double,
) -> Array[Point] {
  if points.length() <= 2 {
    points
  } else {
    let result : Array[Point] = [points[0]]
    for point in points[1:] {
      if distance(result[result.length() - 1], point) >= tolerance.max(0.0) {
        result.push(point)
      }
    }
    if result.length() > 1 &&
      distance(result[0], result[result.length() - 1]) < tolerance.max(0.0) {
      ignore(result.pop())
    }
    result
  }
}

///|
pub fn contour_curvature(points : Array[Point]) -> Array[Double] {
  let result : Array[Double] = []
  if points.length() < 3 {
    result
  } else {
    for i in 0.. Double {
  let metrics = polygon_metrics(points)
  if metrics.perimeter <= 0.0 {
    0.0
  } else {
    metrics.area / metrics.perimeter
  }
}