use std::collections::HashMap;
use log::{debug, error, info, trace, warn};
use crate::error::{Error, Result};
use crate::fp::{FlightPlanning, FlightPlanningBuilder};
use crate::nd::{Fix, NavigationData};
use crate::route::Route;
mod printer;
pub use printer::*;
#[derive(Clone, PartialEq, Debug, Default)]
struct Context {
route: String,
flight_planning_builder: Option<FlightPlanningBuilder>,
}
#[derive(Debug, Default)]
pub struct FMS {
nd: NavigationData,
context: Context,
route: Route,
flight_planning: Option<FlightPlanning>,
}
impl FMS {
pub fn new() -> Self {
Self::default()
}
pub fn nd(&self) -> &NavigationData {
&self.nd
}
pub fn modify_nd<F>(&mut self, f: F) -> Result<()>
where
F: FnOnce(&mut NavigationData),
{
info!("modifying navigation data");
f(&mut self.nd);
EvalPipeline::default()
.inspect_err(EvalStage::Route, |_, fms| fms.route.clear())
.eval(self)
}
pub fn route(&self) -> &Route {
&self.route
}
pub fn modify_route<F>(&mut self, f: F) -> Result<()>
where
F: FnOnce(&mut Route),
{
debug!("modifying route");
f(&mut self.route);
self.context.route = self.route.to_string();
EvalPipeline::default().eval(self)
}
pub fn decode(&mut self, route: String) -> Result<()> {
info!("decoding route: {:?}", route);
self.context.route = route;
EvalPipeline::default().eval(self)
}
pub fn set_alternate(&mut self, ident: &str) -> Result<()> {
info!("setting alternate to {:?}", ident);
match self.nd.find(ident) {
Some(alternate) => {
debug!("alternate resolved to {}", alternate.ident());
self.route.set_alternate(Some(alternate));
EvalPipeline::default().eval(self)
}
None => {
warn!("alternate ident {:?} not found in navigation data", ident);
Err(Error::UnknownIdent(ident.to_string()))
}
}
}
pub fn set_flight_planning(&mut self, builder: FlightPlanningBuilder) -> Result<()> {
info!("setting flight planning");
self.context.flight_planning_builder = Some(builder);
EvalPipeline::default()
.skip_until(EvalStage::FlightPlanning)
.eval(self)
}
pub fn flight_planning(&self) -> Option<&FlightPlanning> {
self.flight_planning.as_ref()
}
pub fn print(&self, line_length: usize) -> String {
let printer = Printer { line_length };
printer
.print(&self.route, self.flight_planning.as_ref())
.unwrap_or_default()
}
}
type Inspector = Box<dyn FnOnce(&Error, &mut FMS)>;
struct EvalPipeline {
stages: [EvalStage; 2],
stage_range: std::ops::Range<usize>,
inspectors: HashMap<EvalStage, Inspector>,
}
impl EvalPipeline {
fn skip_until(mut self, stage: EvalStage) -> Self {
if let Some(i) = self.stages[self.stage_range.clone()]
.iter()
.position(|s| s == &stage)
{
self.stage_range.start += i;
}
self
}
fn inspect_err<F>(mut self, stage: EvalStage, f: F) -> Self
where
F: FnOnce(&Error, &mut FMS) + 'static,
{
self.inspectors.insert(stage, Box::new(f));
self
}
fn eval(mut self, fms: &mut FMS) -> Result<()> {
debug!("running evaluation pipeline");
for stage in &self.stages[self.stage_range] {
trace!("evaluating stage {:?}", stage);
let result = stage.eval(fms);
if let Err(ref e) = result {
error!("evaluation stage {:?} failed: {}", stage, e);
if let Some(inspector) = self.inspectors.remove(stage) {
inspector(e, fms);
}
}
result?;
}
debug!("evaluation pipeline completed");
Ok(())
}
}
impl Default for EvalPipeline {
fn default() -> Self {
Self {
stages: [EvalStage::Route, EvalStage::FlightPlanning],
stage_range: 0..2,
inspectors: HashMap::new(),
}
}
}
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
enum EvalStage {
Route,
FlightPlanning,
}
impl EvalStage {
fn eval(&self, fms: &mut FMS) -> Result<()> {
match self {
EvalStage::Route => {
debug!("decoding route from context: {:?}", fms.context.route);
fms.route.decode(&fms.context.route, &fms.nd)?;
debug!(
"route decoded: {} leg(s), origin={:?}, destination={:?}",
fms.route.legs().len(),
fms.route.origin().as_ref().map(|a| a.ident()),
fms.route.destination().as_ref().map(|a| a.ident()),
);
}
EvalStage::FlightPlanning => {
if let Some(builder) = &fms.context.flight_planning_builder.clone() {
debug!("building flight planning");
let flight_planning = builder.build(&fms.route)?;
debug!(
"flight planning built: fuel_planning={}, mb={}, balanced={:?}",
flight_planning.fuel_planning().is_some(),
flight_planning.mb().is_some(),
flight_planning.is_balanced(),
);
fms.flight_planning = Some(flight_planning);
} else {
trace!("no flight planning builder configured, skipping");
}
}
}
Ok(())
}
}