use std::fmt;
use std::rc::Rc;
use log::{debug, trace, warn};
use crate::error::Error;
use crate::fp::{ClimbDescentPerformance, LegPerformance};
use crate::measurements::Speed;
use crate::nd::*;
use crate::VerticalDistance;
mod accumulator;
mod leg;
mod leg_fuel;
mod profile;
mod token;
pub use accumulator::TotalsToLeg;
pub use leg::Leg;
pub use leg_fuel::LegFuel;
pub use profile::{AirspaceIntersection, VerticalPoint, VerticalProfile};
use token::Tokens;
pub use token::{Token, TokenKind};
#[derive(Clone, PartialEq, Debug, Default)]
pub struct Route {
tokens: Tokens,
legs: Vec<Leg>,
speed: Option<Speed>,
level: Option<VerticalDistance>,
origin: Option<Rc<Airport>>,
takeoff_rwy: Option<Runway>,
destination: Option<Rc<Airport>>,
landing_rwy: Option<Runway>,
alternate: Option<NavAid>,
}
impl Route {
pub fn new() -> Self {
Self::default()
}
pub fn decode(&mut self, route: &str, nd: &NavigationData) -> Result<(), Error> {
debug!("route decode: {:?}", route);
self.clear();
self.tokens = Tokens::new(route, nd);
let mut builder = Leg::builder();
let mut from: Option<NavAid> = None;
let mut to: Option<NavAid> = None;
let destination_idx = self
.tokens
.tokens()
.iter()
.rposition(|t| matches!(t.kind(), TokenKind::Airport { .. }));
for (i, token) in self.tokens.tokens().iter().enumerate() {
match token.kind() {
TokenKind::Speed(value) => {
builder.tas(*value);
}
TokenKind::Level(value) => {
builder.cruise(*value);
}
TokenKind::LevelAtFix(value) => {
builder.level_at_fix(*value);
}
TokenKind::Wind(value) => {
builder.wind(*value);
}
TokenKind::Airport { arpt, rwy } => {
let navaid = NavAid::Airport(Rc::clone(arpt));
if from.is_none() {
from = Some(navaid.clone());
} else if to.is_none() {
to = Some(navaid.clone());
}
if self.origin.is_none() {
debug!(
"origin set to {} (RWY {:?})",
arpt.ident(),
rwy.as_ref().map(|r| &r.designator)
);
self.origin = Some(Rc::clone(arpt));
self.takeoff_rwy = rwy.clone();
} else if Some(i) == destination_idx {
debug!(
"destination set to {} (RWY {:?})",
arpt.ident(),
rwy.as_ref().map(|r| &r.designator)
);
self.destination = Some(Rc::clone(arpt));
self.landing_rwy = rwy.clone();
builder.destination(&navaid);
}
}
TokenKind::NavAid(navaid) => {
if from.is_none() {
from = Some(navaid.clone());
} else if to.is_none() {
to = Some(navaid.clone());
}
}
TokenKind::Err(err) => {
warn!("error token encountered during route decode: {}", err);
return Err(err.clone());
}
_ => (),
}
match (&from, &to) {
(Some(from), Some(to)) => {
trace!("creating leg: {} -> {}", from.ident(), to.ident());
self.legs.push(builder.build(from.clone(), to.clone()));
}
_ => continue,
}
(from, to) = (to, None);
}
debug!("route decoded: {} leg(s)", self.legs.len());
Ok(())
}
pub fn tokens(&self) -> &[Token] {
self.tokens.tokens()
}
pub fn clear(&mut self) {
self.tokens.clear();
self.legs.clear();
self.origin.take();
self.takeoff_rwy.take();
self.destination.take();
self.landing_rwy.take();
self.alternate.take();
}
pub fn legs(&self) -> &[Leg] {
&self.legs
}
#[deprecated]
pub fn set_cruise(&mut self, _speed: Option<Speed>, _level: Option<VerticalDistance>) {
todo!("Add/remove speed and level from the elements")
}
#[deprecated]
pub fn speed(&self) -> Option<Speed> {
self.speed
}
#[deprecated]
pub fn level(&self) -> Option<VerticalDistance> {
self.level
}
pub fn set_alternate(&mut self, alternate: Option<NavAid>) {
self.alternate = alternate;
}
pub fn alternate(&self) -> Option<Leg> {
let alternate = self.alternate.clone()?;
self.legs
.last()
.map(|final_leg| final_leg.divert(alternate))
}
pub fn origin(&self) -> Option<Rc<Airport>> {
self.origin.as_ref().map(Rc::clone)
}
pub fn takeoff_rwy(&self) -> Option<&Runway> {
self.takeoff_rwy.as_ref()
}
pub fn destination(&self) -> Option<Rc<Airport>> {
self.destination.as_ref().map(Rc::clone)
}
pub fn landing_rwy(&self) -> Option<&Runway> {
self.landing_rwy.as_ref()
}
pub fn accumulate_legs<'a>(
&'a self,
perf: Option<&'a LegPerformance<'a>>,
) -> impl Iterator<Item = TotalsToLeg> + 'a {
self.legs
.iter()
.scan(None, move |totals_to_leg: &mut Option<TotalsToLeg>, leg| {
*totals_to_leg = Some(match totals_to_leg.as_ref() {
None => TotalsToLeg::new(leg, perf),
Some(prev) => prev.accumulate(leg, perf),
});
*totals_to_leg
})
}
pub fn totals(&self, perf: Option<&LegPerformance>) -> Option<TotalsToLeg> {
self.accumulate_legs(perf).last()
}
pub fn vertical_profile(
&self,
nd: &NavigationData,
climb: Option<&ClimbDescentPerformance>,
descent: Option<&ClimbDescentPerformance>,
) -> VerticalProfile {
VerticalProfile::new(self, nd, climb, descent)
}
}
impl fmt::Display for Route {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.tokens)
}
}