// Ported from georust/geo
// https://github.com/georust/geo/blob/main/geo/src/algorithm/extremes.rs

///|
/// Indices and coordinates of the extreme points of a geometry along the four
/// cardinal directions.
pub(all) struct Outcome {
  x_min : @type.Coord
  x_max : @type.Coord
  y_min : @type.Coord
  y_max : @type.Coord
} derive(Eq, Debug)

///|
/// Canonical constructor for `Outcome`.
pub fn Outcome::Outcome(
  x_min : @type.Coord,
  x_max : @type.Coord,
  y_min : @type.Coord,
  y_max : @type.Coord,
) -> Outcome {
  Outcome::{ x_min, x_max, y_min, y_max }
}

///|
/// Returns the extremes (xmin, xmax, ymin, ymax) of a coordinate array. Returns
/// `None` for an empty array.
pub fn extremes_of_coords(coords : Array[@type.Coord]) -> Outcome? {
  if coords.length() == 0 {
    return None
  }
  let init = (coords[0], coords[0], coords[0], coords[0])
  let acc = coords
    .iter()
    .fold(init~, fn(acc, c) {
      let x_min = if c.x() < acc.0.x() { c } else { acc.0 }
      let x_max = if c.x() > acc.1.x() { c } else { acc.1 }
      let y_min = if c.y() < acc.2.y() { c } else { acc.2 }
      let y_max = if c.y() > acc.3.y() { c } else { acc.3 }
      (x_min, x_max, y_min, y_max)
    })
  let (x_min, x_max, y_min, y_max) = acc
  Some(Outcome(x_min, x_max, y_min, y_max))
}

// MARK: trait HasExtremes + impls -------------------------------------------

///|
/// Anything whose extreme coordinates (xmin / xmax / ymin / ymax) can be
/// taken. Returns `None` when there are no coordinates at all.
pub(open) trait HasExtremes {
  fn extremes(Self) -> Outcome?
}

///|
pub impl HasExtremes for @type.Point with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.Line with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.LineString with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.MultiPoint with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.MultiLineString with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.Polygon with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.MultiPolygon with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.Rect with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.Triangle with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.GeometryCollection with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}

///|
pub impl HasExtremes for @type.Geometry with fn extremes(self) {
  extremes_of_coords(CoordsCarrier::coords(self))
}