// Ported from georust/geo
// https://github.com/georust/geo/blob/main/geo/src/algorithm/densify_haversine.rs
// (and the metric-space `Densifiable` impls in
// `algorithm/line_measures/densify.rs` which the deprecated
// `densify_haversine` now delegates to)
//
// Inserts intermediate vertices into a geometry so no two consecutive
// coordinates are farther apart than `max_segment_length` along a haversine
// great-circle path. New vertices are interpolated by `Haversine()`-based
// `point_at_ratio_between`.

///|
/// Inserts haversine-interpolated points so consecutive coords are no farther
/// apart than `max_segment_length` (meters). Pushes intermediate points into
/// `out` (the caller controls the start/end push). Asserts
/// `max_segment_length > 0`.
fn densify_between_haversine(
  start : @type.Point,
  end : @type.Point,
  out : Array[@type.Point],
  max_segment_length : Double,
) -> Unit {
  let h = haversine()
  let dist = h.distance(start, end)
  let num_segments = @math.ceil(dist / max_segment_length).to_int()
  if num_segments <= 1 {
    return
  }
  let frac = 1.0 / num_segments.to_double()
  for k in 1..= 3)
/// }
/// ```
pub fn densify_haversine_line(
  line : @type.Line,
  max_segment_length : Double,
) -> @type.LineString {
  let pts : Array[@type.Point] = []
  pts.push(@type.Point::from_coord(line.start()))
  densify_between_haversine(
    @type.Point::from_coord(line.start()),
    @type.Point::from_coord(line.end()),
    pts,
    max_segment_length,
  )
  pts.push(@type.Point::from_coord(line.end()))
  @type.LineString::LineString(pts.map(@type.Point::coord))
}

///|
/// Densifies a `LineString` along haversine paths. Empty input returns
/// `LineString::empty()`. Single-coord input returns the input unchanged.
pub fn densify_haversine_line_string(
  ls : @type.LineString,
  max_segment_length : Double,
) -> @type.LineString {
  let coords = ls.coords()
  let n = coords.length()
  if n == 0 {
    return @type.LineString::empty()
  }
  if n == 1 {
    return ls
  }
  let pts : Array[@type.Point] = []
  for i in 0..<(n - 1) {
    let start = @type.Point::from_coord(coords[i])
    let end = @type.Point::from_coord(coords[i + 1])
    pts.push(start)
    densify_between_haversine(start, end, pts, max_segment_length)
  }
  pts.push(@type.Point::from_coord(coords[n - 1]))
  @type.LineString::LineString(pts.map(@type.Point::coord))
}

///|
/// Densifies all rings of a `Polygon` along haversine paths.
pub fn densify_haversine_polygon(
  polygon : @type.Polygon,
  max_segment_length : Double,
) -> @type.Polygon {
  let exterior = densify_haversine_line_string(
    polygon.exterior(),
    max_segment_length,
  )
  let interiors = polygon
    .interiors()
    .map(fn(ring) { densify_haversine_line_string(ring, max_segment_length) })
  @type.Polygon::Polygon(exterior, interiors)
}

///|
/// Densifies all line strings of a `MultiLineString` along haversine paths.
pub fn densify_haversine_multi_line_string(
  mls : @type.MultiLineString,
  max_segment_length : Double,
) -> @type.MultiLineString {
  @type.MultiLineString::MultiLineString(
    mls
    .line_strings()
    .map(fn(ls) { densify_haversine_line_string(ls, max_segment_length) }),
  )
}

///|
/// Densifies all polygons of a `MultiPolygon` along haversine paths.
pub fn densify_haversine_multi_polygon(
  mp : @type.MultiPolygon,
  max_segment_length : Double,
) -> @type.MultiPolygon {
  @type.MultiPolygon::MultiPolygon(
    mp
    .polygons()
    .map(fn(p) { densify_haversine_polygon(p, max_segment_length) }),
  )
}