use super::{Diagram, Ends, Graph, Shape};
use crate::spec::{Dash, DiagramRole};
type Key = (
usize,
Option<usize>,
Option<usize>,
Option<Dash>,
Option<u64>,
Option<DiagramRole>,
);
pub(super) struct Trunks {
pub leave: Vec<Option<usize>>,
pub reach: Vec<Option<usize>>,
pub shared: Vec<Trunk>,
}
pub(super) struct Trunk {
pub leaving: bool,
pub edges: Vec<usize>,
}
impl Trunks {
pub(super) fn none(edges: usize) -> Self {
Self {
leave: vec![None; edges],
reach: vec![None; edges],
shared: Vec::new(),
}
}
pub(super) fn of(diagram: &Diagram, graph: &Graph) -> Self {
let edges = diagram.edges.len();
let mut trunks = Self::none(edges);
let shareable: Vec<bool> = (0..edges)
.map(|edge| {
let spec = &diagram.edges[edge];
graph.chains[edge]
.as_ref()
.is_some_and(|chain| chain.items.len() == 2 && !chain.reversed)
&& spec.label.is_none()
&& spec.technology.is_none()
&& !spec.main
&& spec.ends == Ends::arrow()
&& !spec.sides.any()
})
.collect();
for leaving in [true, false] {
let mut candidates: Vec<(Key, Vec<usize>)> = Vec::new();
for edge in (0..edges).filter(|&edge| shareable[edge] && trunks.leave[edge].is_none()) {
let spec = &diagram.edges[edge];
let (node, other) = if leaving {
(spec.from, spec.to)
} else {
(spec.to, spec.from)
};
if leaving && diagram.nodes[node].shape == Shape::Decision {
continue;
}
let key = (
node,
diagram.nodes[other].lane,
diagram.nodes[other].group,
spec.dash,
spec.weight.map(f64::to_bits),
spec.role,
);
match candidates
.iter_mut()
.find(|(other_key, _)| *other_key == key)
{
Some((_, members)) => members.push(edge),
None => candidates.push((key, vec![edge])),
}
}
for (_, members) in candidates {
let other = |edge: usize| {
let spec = &diagram.edges[edge];
if leaving { spec.to } else { spec.from }
};
let distinct = members.iter().enumerate().all(|(index, &edge)| {
members[..index].iter().all(|&e| other(e) != other(edge))
});
if members.len() < 2 || !distinct {
continue;
}
let trunk = trunks.shared.len();
for &edge in &members {
if leaving {
trunks.leave[edge] = Some(trunk);
} else {
trunks.reach[edge] = Some(trunk);
}
}
trunks.shared.push(Trunk {
leaving,
edges: members,
});
}
}
trunks
}
pub(super) fn at(&self, edge: usize, leaving: bool) -> Option<usize> {
if leaving {
self.leave[edge]
} else {
self.reach[edge]
}
}
}
pub(super) fn branches(shared: f64, bus: f64, others: &[f64]) -> Vec<(f64, f64)> {
let side = |sign: f64| {
let mut side: Vec<f64> = others
.iter()
.copied()
.filter(|other| (other - shared) * sign > 0.5)
.collect();
crate::sort::by(&mut side, |a, b| {
((a - shared) * sign).total_cmp(&((b - shared) * sign))
});
side
};
let (before, after) = (side(-1.0), side(1.0));
let straight = others.iter().any(|other| (other - shared).abs() <= 0.5);
let ways =
usize::from(!before.is_empty()) + usize::from(!after.is_empty()) + usize::from(straight);
let mut points = Vec::new();
if ways >= 2 {
points.push((bus, shared));
}
for side in [before, after] {
if let Some((_, inner)) = side.split_last() {
points.extend(inner.iter().map(|&other| (bus, other)));
}
}
points
}
#[cfg(test)]
mod tests {
use super::branches;
#[test]
fn a_trunk_branches_where_its_line_goes_on_in_more_than_one_way() {
assert_eq!(
branches(0.0, 5.0, &[-30.0, -10.0, -20.0]),
[(5.0, -10.0), (5.0, -20.0)]
);
assert_eq!(
branches(0.0, 5.0, &[-10.0, 0.0, 10.0, 20.0]),
[(5.0, 0.0), (5.0, 10.0)]
);
assert!(
branches(0.0, 5.0, &[10.0]).is_empty(),
"{:?}",
branches(0.0, 5.0, &[10.0])
);
}
}