// Ported from georust/geo
// https://github.com/georust/geo/blob/main/geo/src/algorithm/chamberlain_duquette_area.rs
//
// References:
//   Robert G. Chamberlain and William H. Duquette,
//   "Some Algorithms for Polygons on a Sphere",
//   JPL Publication 07-03, June 2007.
//
// The signed area of a spherical polygon expressed in meters², approximated
// using a sphere of radius `EQUATORIAL_EARTH_RADIUS` (WGS84). Result is
// signed: positive for CCW (counter-clockwise) outer boundaries on the
// surface, negative for CW.
//
// Caller note (matches upstream): the sign convention here is *opposite*
// to the planar `Area` trait's `signed_area`. CCW lng/lat polygons that
// look "normal" on a Mercator map will yield a positive signed area here.

///|
/// WGS84 equatorial radius in meters. Mirrors `geo`'s `EQUATORIAL_EARTH_RADIUS`.
const CHAMBERLAIN_EARTH_RADIUS : Double = 6_378_137.0

///|
/// Signed approximate spherical area of a single ring (`LineString` with
/// first == last). Returns 0 for rings with fewer than 3 points.
fn ring_chamberlain_duquette_signed_area(ring : @type.LineString) -> Double {
  let coords = ring.coords()
  let n = coords.length()
  if n <= 2 {
    return 0.0
  }
  let mut total = 0.0
  for i in 0.. Double { x * @math.PI / 180.0 }
    total = total +
      (to_rad(p3.x()) - to_rad(p1.x())) * @math.sin(to_rad(p2.y()))
  }
  total * CHAMBERLAIN_EARTH_RADIUS * CHAMBERLAIN_EARTH_RADIUS / -2.0
}

///|
/// Signed Chamberlain-Duquette spherical area of a polygon (meters²).
fn chamberlain_duquette_signed_area_polygon(polygon : @type.Polygon) -> Double {
  polygon
  .interiors()
  .iter()
  .fold(init=ring_chamberlain_duquette_signed_area(polygon.exterior()), fn(
    total,
    next,
  ) {
    total - ring_chamberlain_duquette_signed_area(next)
  })
}

///|
/// Unsigned (absolute) Chamberlain-Duquette spherical area of a polygon
/// (meters²).
fn chamberlain_duquette_unsigned_area_polygon(
  polygon : @type.Polygon,
) -> Double {
  chamberlain_duquette_signed_area_polygon(polygon).abs()
}

///|
/// Signed Chamberlain-Duquette spherical area of a `MultiPolygon` (sum of
/// per-component signed areas; meters²).
fn chamberlain_duquette_signed_area_multi_polygon(
  mp : @type.MultiPolygon,
) -> Double {
  mp
  .polygons()
  .iter()
  .fold(init=0.0, fn(total, p) {
    total + chamberlain_duquette_signed_area_polygon(p)
  })
}

///|
/// Unsigned Chamberlain-Duquette spherical area of a `MultiPolygon`
/// (meters²).
fn chamberlain_duquette_unsigned_area_multi_polygon(
  mp : @type.MultiPolygon,
) -> Double {
  mp
  .polygons()
  .iter()
  .fold(init=0.0, fn(total, p) {
    total + chamberlain_duquette_unsigned_area_polygon(p)
  })
}

///|
/// Signed Chamberlain-Duquette spherical area of a `Geometry`. Per upstream:
/// non-area geometries (Point/Line/LineString/MultiPoint/MultiLineString)
/// return 0. `Rect` and `Triangle` delegate to their `to_polygon()`.
/// `GeometryCollection` sums per-component signed areas.
pub fn chamberlain_duquette_signed_area_geometry(g : @type.Geometry) -> Double {
  match g {
    @type.Geometry::Point(_) => 0.0
    @type.Geometry::Line(_) => 0.0
    @type.Geometry::LineString(_) => 0.0
    @type.Geometry::MultiPoint(_) => 0.0
    @type.Geometry::MultiLineString(_) => 0.0
    @type.Geometry::Polygon(p) => chamberlain_duquette_signed_area_polygon(p)
    @type.Geometry::MultiPolygon(mp) =>
      chamberlain_duquette_signed_area_multi_polygon(mp)
    @type.Geometry::Rect(r) =>
      chamberlain_duquette_signed_area_polygon(r.to_polygon())
    @type.Geometry::Triangle(t) =>
      chamberlain_duquette_signed_area_polygon(t.to_polygon())
    @type.Geometry::GeometryCollection(gc) =>
      gc
      .geometries()
      .iter()
      .fold(init=0.0, fn(total, child) {
        total + chamberlain_duquette_signed_area_geometry(child)
      })
  }
}

///|
/// Unsigned (absolute) Chamberlain-Duquette spherical area of a `Geometry`
/// (meters²).
///
/// ```check
/// test "chamberlain_duquette_unsigned_area_geometry - square" {
///   let ring = @type.LineString::from_tuples([
///     (0.0, 0.0),
///     (1.0, 0.0),
///     (1.0, 1.0),
///     (0.0, 1.0),
///     (0.0, 0.0),
///   ])
///   let geometry = @type.Geometry::Polygon(@type.Polygon::Polygon(ring, []))
///   let area = chamberlain_duquette_unsigned_area_geometry(geometry)
///   assert_true(area > 1.0e10)
/// }
/// ```
pub fn chamberlain_duquette_unsigned_area_geometry(
  g : @type.Geometry,
) -> Double {
  match g {
    @type.Geometry::Polygon(p) => chamberlain_duquette_unsigned_area_polygon(p)
    @type.Geometry::MultiPolygon(mp) =>
      chamberlain_duquette_unsigned_area_multi_polygon(mp)
    _ => chamberlain_duquette_signed_area_geometry(g).abs()
  }
}