///|
/// Routes: the path a flight follows, the hops it is made of, and the time it
/// takes against a winds-aloft forecast.
///
/// A route is a list of waypoints, and between two of them the path is a
/// great circle. The course of a great circle drifts as it is flown — a
/// north Atlantic crossing leaves New York on 051 and reaches London on 108 —
/// so a long hop is better flown as several short legs, each steered by its
/// own course. `Route::subdivided` cuts the hops into those legs, and
/// `Route::plan` times each one against the forecast wind.

///|
/// One hop of a route: the two positions, the great-circle distance between
/// them and the course it is flown on.
pub struct RouteSegment {
  from : Coordinate
  to : Coordinate
  distance : Double
  true_course : Double
}

///|
/// The hop from one position to another, with its distance and course.
pub fn RouteSegment::new(from : Coordinate, to : Coordinate) -> RouteSegment {
  {
    from,
    to,
    distance: from.distance_to(to),
    true_course: from.initial_course_to(to),
  }
}

///|
/// Where the hop starts.
pub fn RouteSegment::from(self : RouteSegment) -> Coordinate {
  self.from
}

///|
/// Where the hop ends.
pub fn RouteSegment::to(self : RouteSegment) -> Coordinate {
  self.to
}

///|
/// The great-circle distance of the hop, in nautical miles.
pub fn RouteSegment::distance(self : RouteSegment) -> Double {
  self.distance
}

///|
/// The true course at the start of the hop, in degrees. The course drifts as
/// the hop is flown, so a long hop is not flown on one heading: cut it into
/// shorter legs to keep the course of each one nearly constant.
pub fn RouteSegment::true_course(self : RouteSegment) -> Double {
  self.true_course
}

///|
/// Half way along the hop, where a leg reads its wind from the forecast.
pub fn RouteSegment::midpoint(self : RouteSegment) -> Coordinate {
  self.from.midpoint(self.to)
}

///|
/// A route: the waypoints in the order they are flown.
pub struct Route {
  points : Array[Coordinate]
}

///|
/// A route from its waypoints, in order. A route needs two waypoints to have
/// any length; one with fewer is simply empty of hops.
pub fn Route::new(points : Array[Coordinate]) -> Route {
  { points, }
}

///|
/// The waypoints, in order.
pub fn Route::points(self : Route) -> Array[Coordinate] {
  self.points
}

///|
/// The hops between consecutive waypoints, in order, with their distances and
/// courses.
pub fn Route::segments(self : Route) -> Array[RouteSegment] {
  let segments : Array[RouteSegment] = []
  for i = 0; i + 1 < self.points.length(); i = i + 1 {
    segments.push(RouteSegment::new(self.points[i], self.points[i + 1]))
  }
  segments
}

///|
/// The length of the route along its waypoints, in nautical miles.
///
/// Only the hops are measured, so a route's length costs nothing it does not
/// use: the courses along the way are left to `segments`.
pub fn Route::distance(self : Route) -> Double {
  let mut total = 0.0
  for i = 0; i + 1 < self.points.length(); i = i + 1 {
    total += self.points[i].distance_to(self.points[i + 1])
  }
  total
}

///|
/// The same route flown in shorter hops: every hop divided into `count` equal
/// parts, so that the course of each part stays close to the course at its
/// start. A `count` below one leaves the route as it is.
///
/// The hop is walked from its two ends, which are read once for the whole
/// hop: cutting a route into many legs costs the same trigonometry whether a
/// hop is cut in two or in twenty.
pub fn Route::subdivided(self : Route, count : Int32) -> Route {
  let points : Array[Coordinate] = []
  if count < 1 {
    for point in self.points {
      points.push(point)
    }
  } else {
    for i = 0; i + 1 < self.points.length(); i = i + 1 {
      let from = self.points[i]
      let to = self.points[i + 1]
      points.push(from)
      let start = unit_vector(from)
      let end = unit_vector(to)
      let angle = angle_between(start, end)
      let walkable = angle > 1.0e-12 && angle < @math.PI - 1.0e-12
      for part = 1; part < count; part = part + 1 {
        let fraction = part.to_double() / count.to_double()
        points.push(
          if walkable {
            position_along(start, end, angle, fraction)
          } else {
            // The hop is too short to have parts, or runs between opposite
            // positions, where there is no path to walk: it stays where it is.
            from
          },
        )
      }
    }
    if self.points.length() > 0 {
      points.push(self.points[self.points.length() - 1])
    }
  }
  { points, }
}

///|
/// One leg of a planned route: the hop, the forecast wind that was used for
/// it, and the wind triangle that comes out of the two.
pub struct RouteLeg {
  segment : RouteSegment
  wind : WindVector
  triangle : WindTriangle
}

///|
/// The hop this leg flies.
pub fn RouteLeg::segment(self : RouteLeg) -> RouteSegment {
  self.segment
}

///|
/// The forecast wind the leg was planned with, as a vector.
pub fn RouteLeg::wind(self : RouteLeg) -> WindVector {
  self.wind
}

///|
/// The wind triangle of the leg: heading, ground speed and the components a
/// pilot reads the wind in.
pub fn RouteLeg::triangle(self : RouteLeg) -> WindTriangle {
  self.triangle
}

///|
/// The distance of the leg, in nautical miles.
pub fn RouteLeg::distance(self : RouteLeg) -> Double {
  self.segment.distance
}

///|
/// The course of the leg, in degrees true.
pub fn RouteLeg::true_course(self : RouteLeg) -> Double {
  self.segment.true_course
}

///|
/// The heading to steer along the leg, in degrees true.
pub fn RouteLeg::heading(self : RouteLeg) -> Double {
  self.triangle.heading()
}

///|
/// The ground speed the leg is flown at, in knots.
pub fn RouteLeg::ground_speed(self : RouteLeg) -> Double {
  self.triangle.ground_speed()
}

///|
/// The wind component along the leg, in knots: positive against the aircraft,
/// so a tailwind is negative.
pub fn RouteLeg::headwind(self : RouteLeg) -> Double {
  self.triangle.headwind()
}

///|
/// The wind component across the leg, in knots: positive from the right.
pub fn RouteLeg::crosswind(self : RouteLeg) -> Double {
  self.triangle.crosswind()
}

///|
/// The time the leg takes, in hours: its distance over its ground speed.
pub fn RouteLeg::time_hours(self : RouteLeg) -> Double {
  self.segment.distance / self.triangle.ground_speed()
}

///|
/// A route planned at one altitude and airspeed: the legs in order, each with
/// the wind it was planned with, and the totals over the whole route.
pub struct RoutePlan {
  source : String?
  altitude : Int32
  airspeed : Double
  legs : Array[RouteLeg]
}

///|
/// The station the forecast came from, when the plan was built on one
/// station's winds rather than on a wind the caller supplies.
pub fn RoutePlan::source(self : RoutePlan) -> String? {
  self.source
}

///|
/// The altitude the route is planned at, in feet.
pub fn RoutePlan::altitude(self : RoutePlan) -> Int32 {
  self.altitude
}

///|
/// The true airspeed the route is planned at, in knots.
pub fn RoutePlan::airspeed(self : RoutePlan) -> Double {
  self.airspeed
}

///|
/// The legs, in order.
pub fn RoutePlan::legs(self : RoutePlan) -> Array[RouteLeg] {
  self.legs
}

///|
/// The distance of the whole route, in nautical miles.
pub fn RoutePlan::total_distance(self : RoutePlan) -> Double {
  let mut total = 0.0
  for leg in self.legs {
    total += leg.distance()
  }
  total
}

///|
/// The time the whole route takes, in hours.
pub fn RoutePlan::total_time_hours(self : RoutePlan) -> Double {
  let mut total = 0.0
  for leg in self.legs {
    total += leg.time_hours()
  }
  total
}

///|
/// Plan the route at `altitude` feet and `airspeed` knots, reading the wind
/// for each leg from `wind_at`, which is given the middle of the leg and the
/// altitude.
///
/// The wind is read at the middle of a leg because that is the wind that
/// carries the aircraft over most of it, and the position is offered to the
/// caller so that a forecast which varies over the route can be used: the
/// station nearest each leg, or a grid of forecast winds.
///
/// A leg that has no forecast at that altitude is reported with
/// `FlightError::NoForecast`, and a leg the wind beats — one whose ground
/// speed comes out at or below zero — with `FlightError::NoGroundSpeed`,
/// rather than being planned with a time that means nothing.
pub fn Route::plan_with(
  self : Route,
  wind_at : (Coordinate, Int32) -> WindVector?,
  altitude : Int32,
  airspeed : Double,
) -> RoutePlan raise FlightError {
  let legs : Array[RouteLeg] = []
  for segment in self.segments() {
    let wind = match wind_at(segment.midpoint(), altitude) {
      Some(found) => found
      None => raise FlightError::NoForecast(altitude)
    }
    let triangle = WindTriangle::solve(segment.true_course, airspeed, wind)
    if triangle.ground_speed() <= 0.0 {
      raise FlightError::NoGroundSpeed(triangle.ground_speed())
    }
    legs.push({ segment, wind, triangle, })
  }
  { source: None, altitude, airspeed, legs, }
}

///|
/// Plan the route at `altitude` feet and `airspeed` knots with one station's
/// forecast, read at each leg's middle.
///
/// One station stands in for the whole route, which is what a route flown
/// within one station's area wants. A longer route is planned in parts, one
/// call per part, each with the station nearest it; the parts add up.
pub fn Route::plan(
  self : Route,
  station : StationWinds,
  altitude : Int32,
  airspeed : Double,
) -> RoutePlan raise FlightError {
  let plan = self.plan_with(
    fn(_position, level) { station.vector_at(level) },
    altitude,
    airspeed,
  )
  {
    source: Some(station.station()),
    altitude: plan.altitude(),
    airspeed: plan.airspeed(),
    legs: plan.legs(),
  }
}