use crate::parameters::DiveParameters;
use crate::plan::DivePlan;
use crate::result::DiveResult;
use crate::{PPO2_MAXIMUM_DECO, PPO2_MINIMUM};
use capra_core::common::dive_segment::SegmentType::{AscDesc, DecoStop};
use capra_core::common::dive_segment::{DiveSegment, SegmentType};
use capra_core::common::gas::Gas;
use capra_core::common::tank::Tank;
use capra_core::common::time_taken;
use capra_core::deco::deco_algorithm::DecoAlgorithm;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::iter;
use time::Duration;
#[derive(Copy, Clone, Debug)]
pub struct OpenCircuit<'a, T: DecoAlgorithm> {
deco_algorithm: T,
deco_gases: &'a [(Gas, Option<usize>)],
bottom_segments: &'a [(DiveSegment, Gas)],
parameters: DiveParameters,
}
impl<'a, T: DecoAlgorithm> OpenCircuit<'a, T> {
pub fn new(
deco_algorithm: T,
deco_gases: &'a [(Gas, Option<usize>)],
bottom_segments: &'a [(DiveSegment, Gas)],
parameters: DiveParameters,
) -> Self {
OpenCircuit {
deco_algorithm,
deco_gases,
bottom_segments,
parameters,
}
}
fn filter_gases<'b>(
segment: &DiveSegment,
gases: &'b [(Gas, Option<usize>)],
metres_per_bar: f64,
) -> Vec<&'b Gas> {
let mut candidates = gases
.iter()
.filter(|x| x.1.map_or(true, |t| t >= segment.start_depth()))
.map(|x| &x.0)
.filter(|a| a.in_ppo2_range(segment.start_depth(), PPO2_MINIMUM, PPO2_MAXIMUM_DECO))
.filter(|a| a.equivalent_narcotic_depth(segment.start_depth()) <= segment.start_depth())
.collect::<Vec<&Gas>>();
candidates.sort_by(|a, b| {
a.pp_o2(segment.start_depth(), metres_per_bar)
.partial_cmp(&b.pp_o2(segment.start_depth(), metres_per_bar))
.unwrap()
});
candidates
}
fn find_gas_switch_point<'c>(
segments: &'c [DiveSegment],
current_gas: &Gas,
gases: &'c [(Gas, Option<usize>)],
metres_per_bar: f64,
) -> Option<(&'c DiveSegment, &'c Gas)> {
for stop in segments.iter().filter(|x| x.segment_type() != AscDesc) {
let candidate_gases = <OpenCircuit<'a, T>>::filter_gases(stop, gases, metres_per_bar);
if candidate_gases.is_empty() {
continue;
}
if candidate_gases[candidate_gases.len() - 1] != current_gas {
return Some((stop, &candidate_gases[candidate_gases.len() - 1]));
}
}
None
}
pub(crate) fn level_to_level(
&self,
mut deco: T,
start: &(DiveSegment, Gas),
end: Option<&(DiveSegment, Gas)>,
stops_performed: &mut Vec<(DiveSegment, Gas)>,
) -> T {
if let Some(t) = end {
match start.0.end_depth().cmp(&t.0.start_depth()) {
Ordering::Less => {
let descent = DiveSegment::new(
SegmentType::AscDesc,
start.0.end_depth(),
t.0.start_depth(),
time_taken(
self.parameters.descent_rate,
start.0.end_depth(),
t.0.start_depth(),
),
self.parameters.ascent_rate,
self.parameters.descent_rate,
)
.unwrap();
deco.add_dive_segment(&descent, &start.1, self.parameters.metres_per_bar);
stops_performed.push((descent, start.1));
return deco;
}
Ordering::Equal => {
return deco;
}
Ordering::Greater => {}
}
}
let mut virtual_deco = deco;
let end_depth = match end {
Some(t) => t.0.start_depth(),
None => 0,
};
let stops = virtual_deco
.surface(
self.parameters.ascent_rate,
self.parameters.descent_rate,
&start.1,
self.parameters.metres_per_bar,
)
.into_iter()
.take_while(|x| x.start_depth() > end_depth)
.collect::<Vec<DiveSegment>>();
let switch_gases: Vec<(Gas, Option<usize>)> = match end {
Some(t) => vec![(start.1, None), (t.1, None)],
None => self.deco_gases.to_vec(),
};
let switch_point = <OpenCircuit<'a, T>>::find_gas_switch_point(
&stops,
&start.1,
&switch_gases,
self.parameters.metres_per_bar,
);
if let (Some(switch), true) = (
switch_point,
stops.iter().any(|x| x.segment_type() == DecoStop),
) {
virtual_deco = deco;
for stop in stops
.iter()
.take_while(|x| x.start_depth() > switch.0.start_depth())
{
virtual_deco.add_dive_segment(&stop, &start.1, self.parameters.metres_per_bar);
stops_performed.push((*stop, start.1));
}
let new_stop = match virtual_deco
.get_stops(
self.parameters.ascent_rate,
self.parameters.descent_rate,
&switch.1,
self.parameters.metres_per_bar,
)
.into_iter()
.find(|x| x.segment_type() == DecoStop && x.start_depth() == switch.0.start_depth())
{
Some(t) => t,
None => DiveSegment::new(
SegmentType::DecoStop,
switch.0.start_depth(),
switch.0.end_depth(),
Duration::minute(),
self.parameters.ascent_rate,
self.parameters.descent_rate,
)
.unwrap(),
};
virtual_deco.add_dive_segment(&new_stop, switch.1, self.parameters.metres_per_bar);
stops_performed.push((new_stop, *switch.1));
deco = virtual_deco;
self.level_to_level(deco, &(new_stop, *switch.1), end, stops_performed)
} else {
stops_performed.append(&mut stops.into_iter().zip(iter::repeat(start.1)).collect());
deco = virtual_deco;
deco
}
}
}
impl<'a, T: DecoAlgorithm> DivePlan<T> for OpenCircuit<'a, T> {
fn plan(&self) -> DiveResult<T> {
let mut total_segments: Vec<(DiveSegment, Gas)> = Vec::new();
let mut deco = self.deco_algorithm;
let descent_to_beginning = DiveSegment::new(
AscDesc,
0,
self.bottom_segments[0].0.start_depth(),
time_taken(
self.parameters.descent_rate,
0,
self.bottom_segments[0].0.start_depth(),
),
self.parameters.ascent_rate,
self.parameters.descent_rate,
)
.unwrap();
deco.add_dive_segment(
&descent_to_beginning,
&self.bottom_segments[0].1,
self.parameters.metres_per_bar,
);
total_segments.push((descent_to_beginning, self.bottom_segments[0].1));
for win in self.bottom_segments.windows(2) {
let mut stops_performed: Vec<(DiveSegment, Gas)> = Vec::new();
let start = win[0];
let end = win[1];
deco.add_dive_segment(&start.0, &start.1, self.parameters.metres_per_bar);
total_segments.push(start);
deco = self.level_to_level(deco, &start, Some(&end), &mut stops_performed);
total_segments.append(&mut stops_performed);
}
let final_stop = self.bottom_segments.last().unwrap();
deco.add_dive_segment(&final_stop.0, &final_stop.1, self.parameters.metres_per_bar);
total_segments.push(*final_stop);
let mut stops_performed: Vec<(DiveSegment, Gas)> = Vec::new();
deco = self.level_to_level(deco, &final_stop, None, &mut stops_performed);
total_segments.append(&mut stops_performed);
let mut gas_plan: HashMap<Gas, usize> = HashMap::new();
for (segment, gas) in &total_segments {
let gas_consumed =
match segment.segment_type() {
SegmentType::DecoStop => segment
.gas_consumed(self.parameters.sac_deco, self.parameters.metres_per_bar),
SegmentType::NoDeco => 0,
_ => segment
.gas_consumed(self.parameters.sac_bottom, self.parameters.metres_per_bar),
};
let gas_needed = *(gas_plan.entry(*gas).or_insert(0)) + gas_consumed;
gas_plan.insert(*gas, gas_needed);
}
DiveResult::new(deco, total_segments, gas_plan)
}
fn plan_backwards(&self, _tanks: &[Tank]) -> DiveResult<T> {
unimplemented!()
}
}