///|
/// Geometry diagnostics for a station array.
pub struct ArrayGeometryDiagnostics {
  station_count : Int
  aperture_km : Double
  minimum_spacing_km : Double
  maximum_spacing_km : Double
  mean_spacing_km : Double
  azimuthal_gap_degrees : Double
  usable : Bool
}

///|
/// Calculate pairwise geometry diagnostics.
pub fn array_geometry_diagnostics(
  stations : Array[StationPosition],
) -> ArrayGeometryDiagnostics {
  if stations.length() < 2 {
    return {
      station_count: stations.length(),
      aperture_km: 0.0,
      minimum_spacing_km: 0.0,
      maximum_spacing_km: 0.0,
      mean_spacing_km: 0.0,
      azimuthal_gap_degrees: 360.0,
      usable: false,
    }
  }
  let mut minimum = 1.0e300
  let mut maximum = 0.0
  let mut total = 0.0
  let mut pair_count = 0
  let azimuths = Array::new()
  for left = 0; left < stations.length(); left = left + 1 {
    for right = left + 1; right < stations.length(); right = right + 1 {
      let distance = surface_distance_km(stations[left], stations[right])
      minimum = minimum.min(distance)
      maximum = maximum.max(distance)
      total += distance
      pair_count += 1
    }
    azimuths.push(azimuth_degrees(stations[0], stations[left]))
  }
  azimuths.sort()
  let mut largest_gap = 0.0
  for index = 1; index < azimuths.length(); index = index + 1 {
    largest_gap = largest_gap.max(azimuths[index] - azimuths[index - 1])
  }
  if azimuths.length() > 1 {
    largest_gap = largest_gap.max(
      azimuths[0] + 360.0 - azimuths[azimuths.length() - 1],
    )
  }
  {
    station_count: stations.length(),
    aperture_km: maximum,
    minimum_spacing_km: minimum,
    maximum_spacing_km: maximum,
    mean_spacing_km: if pair_count == 0 {
      0.0
    } else {
      total / pair_count.to_double()
    },
    azimuthal_gap_degrees: largest_gap,
    usable: minimum > 0.0 && largest_gap < 270.0,
  }
}

///|
/// Return stations inside an array aperture radius.
pub fn stations_inside_radius(
  stations : Array[StationPosition],
  reference : StationPosition,
  radius_km : Double,
) -> Array[StationPosition] {
  stations.filter(station => {
    surface_distance_km(reference, station) <= radius_km.abs()
  })
}

///|
/// Estimate the maximum useful slowness from array aperture.
pub fn useful_slowness_limit(
  stations : Array[StationPosition],
  sample_rate : Double,
) -> Double {
  let diagnostics = array_geometry_diagnostics(stations)
  if diagnostics.aperture_km <= 0.0 || sample_rate <= 0.0 {
    0.0
  } else {
    sample_rate / diagnostics.aperture_km
  }
}

///|
/// Export array geometry diagnostics.
pub fn array_geometry_text(diagnostics : ArrayGeometryDiagnostics) -> String {
  "stations=" +
  diagnostics.station_count.to_string() +
  " aperture_km=" +
  diagnostics.aperture_km.to_string() +
  " min_spacing_km=" +
  diagnostics.minimum_spacing_km.to_string() +
  " max_spacing_km=" +
  diagnostics.maximum_spacing_km.to_string() +
  " mean_spacing_km=" +
  diagnostics.mean_spacing_km.to_string() +
  " azimuthal_gap=" +
  diagnostics.azimuthal_gap_degrees.to_string() +
  " usable=" +
  diagnostics.usable.to_string()
}