use std::cmp::Ordering;
use disposition_ir_model::{
edge::{Edge, EdgeFaceAssignment, EdgeFaceAssignments, EdgeGroups},
entity::EntityTypes,
node::{NodeFace, NodeId, NodeNestingInfos, NodeRank, NodeRanksNested},
};
use disposition_model_common::RankDir;
use crate::EdgeIdGenerator;
#[derive(Clone, Copy, Debug)]
pub struct EdgeFaceAssigner;
struct LcaInfo<'id> {
divergent_from: NodeId<'id>,
divergent_to: NodeId<'id>,
lca_container: Option<NodeId<'id>>,
sibling_index_from: usize,
sibling_index_to: usize,
}
impl EdgeFaceAssigner {
pub fn compute<'id>(
edge_groups: &EdgeGroups<'id>,
_entity_types: &EntityTypes<'id>,
node_nesting_infos: &NodeNestingInfos<'id>,
node_ranks_nested: &NodeRanksNested<'id>,
rank_dir: RankDir,
) -> EdgeFaceAssignments<'id> {
let mut assignments = EdgeFaceAssignments::new();
for (edge_group_id, edge_group) in edge_groups.iter() {
for (edge_index, edge) in edge_group.iter().enumerate() {
let edge_id = EdgeIdGenerator::generate(edge_group_id, edge_index);
let assignment = Self::edge_face_assignment_compute(
edge,
node_nesting_infos,
node_ranks_nested,
rank_dir,
);
assignments.insert(edge_id, assignment);
}
}
assignments
}
fn edge_face_assignment_compute<'id>(
edge: &Edge<'id>,
node_nesting_infos: &NodeNestingInfos<'id>,
node_ranks_nested: &NodeRanksNested<'id>,
rank_dir: RankDir,
) -> EdgeFaceAssignment {
if edge.is_self_loop() {
return EdgeFaceAssignment {
from_face: Some(NodeFace::Bottom),
to_face: None,
};
}
let Some(lca_info) = Self::lca_info_compute(&edge.from, &edge.to, node_nesting_infos)
else {
return Self::contained_edge_face_assignment(
&edge.from,
&edge.to,
node_nesting_infos,
rank_dir,
);
};
let (rank_from, rank_to) = Self::lca_ranks_compute(&lca_info, node_ranks_nested);
let (from_face, to_face) = if rank_from == rank_to {
Self::cycle_faces(
lca_info.sibling_index_from,
lca_info.sibling_index_to,
rank_dir,
)
} else if rank_from < rank_to {
Self::forward_faces(rank_dir)
} else {
let (f, t) = Self::forward_faces(rank_dir);
(t, f)
};
EdgeFaceAssignment {
from_face: Some(from_face),
to_face: Some(to_face),
}
}
fn lca_info_compute<'id>(
from_id: &NodeId<'id>,
to_id: &NodeId<'id>,
node_nesting_infos: &NodeNestingInfos<'id>,
) -> Option<LcaInfo<'id>> {
let info_from = node_nesting_infos.get(from_id)?;
let info_to = node_nesting_infos.get(to_id)?;
let chain_from = &info_from.ancestor_chain;
let chain_to = &info_to.ancestor_chain;
let lca_depth = chain_from
.iter()
.zip(chain_to.iter())
.take_while(|(a, b)| a == b)
.count();
if lca_depth >= chain_from.len() || lca_depth >= chain_to.len() {
return None;
}
let divergent_from = chain_from[lca_depth].clone();
let divergent_to = chain_to[lca_depth].clone();
if divergent_from == divergent_to {
return None;
}
let lca_container = lca_depth.checked_sub(1).map(|i| chain_from[i].clone());
let sibling_index_from = info_from.nesting_path[lca_depth];
let sibling_index_to = info_to.nesting_path[lca_depth];
Some(LcaInfo {
divergent_from,
divergent_to,
lca_container,
sibling_index_from,
sibling_index_to,
})
}
fn lca_ranks_compute<'id>(
lca_info: &LcaInfo<'id>,
node_ranks_nested: &NodeRanksNested<'id>,
) -> (NodeRank, NodeRank) {
let container_ranks = node_ranks_nested.ranks_for(lca_info.lca_container.as_ref());
let rank_from = container_ranks
.and_then(|ranks| ranks.get(&lca_info.divergent_from).copied())
.unwrap_or_default();
let rank_to = container_ranks
.and_then(|ranks| ranks.get(&lca_info.divergent_to).copied())
.unwrap_or_default();
(rank_from, rank_to)
}
fn forward_faces(rank_dir: RankDir) -> (NodeFace, NodeFace) {
match rank_dir {
RankDir::LeftToRight => (NodeFace::Right, NodeFace::Left),
RankDir::RightToLeft => (NodeFace::Left, NodeFace::Right),
RankDir::TopToBottom => (NodeFace::Bottom, NodeFace::Top),
RankDir::BottomToTop => (NodeFace::Top, NodeFace::Bottom),
}
}
fn cycle_faces(
sibling_index_from: usize,
sibling_index_to: usize,
rank_dir: RankDir,
) -> (NodeFace, NodeFace) {
let sibling_index_from_cmp_to = sibling_index_from.cmp(&sibling_index_to);
let sibling_index_abs_diff = sibling_index_from.abs_diff(sibling_index_to);
match (rank_dir, sibling_index_from_cmp_to, sibling_index_abs_diff) {
(RankDir::LeftToRight, Ordering::Less, 1) => (NodeFace::Bottom, NodeFace::Top),
(RankDir::LeftToRight, Ordering::Equal | Ordering::Greater, 1) => {
(NodeFace::Top, NodeFace::Bottom)
}
(RankDir::RightToLeft, Ordering::Less, 1) => (NodeFace::Top, NodeFace::Bottom),
(RankDir::RightToLeft, Ordering::Equal | Ordering::Greater, 1) => {
(NodeFace::Bottom, NodeFace::Top)
}
(RankDir::TopToBottom, Ordering::Less, 1) => (NodeFace::Right, NodeFace::Left),
(RankDir::TopToBottom, Ordering::Equal | Ordering::Greater, 1) => {
(NodeFace::Left, NodeFace::Right)
}
(RankDir::BottomToTop, Ordering::Less, 1) => (NodeFace::Left, NodeFace::Right),
(RankDir::BottomToTop, Ordering::Equal | Ordering::Greater, 1) => {
(NodeFace::Right, NodeFace::Left)
}
(RankDir::LeftToRight, Ordering::Less, _) => (NodeFace::Right, NodeFace::Right),
(RankDir::LeftToRight, Ordering::Equal | Ordering::Greater, _) => {
(NodeFace::Left, NodeFace::Left)
}
(RankDir::RightToLeft, Ordering::Less, _) => (NodeFace::Left, NodeFace::Left),
(RankDir::RightToLeft, Ordering::Equal | Ordering::Greater, _) => {
(NodeFace::Right, NodeFace::Right)
}
(RankDir::TopToBottom, Ordering::Less, _) => (NodeFace::Top, NodeFace::Top),
(RankDir::TopToBottom, Ordering::Equal | Ordering::Greater, _) => {
(NodeFace::Bottom, NodeFace::Bottom)
}
(RankDir::BottomToTop, Ordering::Less, _) => (NodeFace::Bottom, NodeFace::Bottom),
(RankDir::BottomToTop, Ordering::Equal | Ordering::Greater, _) => {
(NodeFace::Top, NodeFace::Top)
}
}
}
fn contained_edge_face_assignment<'id>(
from_id: &NodeId<'id>,
to_id: &NodeId<'id>,
node_nesting_infos: &NodeNestingInfos<'id>,
rank_dir: RankDir,
) -> EdgeFaceAssignment {
let Some(info_from) = node_nesting_infos.get(from_id) else {
return EdgeFaceAssignment::default();
};
let Some(info_to) = node_nesting_infos.get(to_id) else {
return EdgeFaceAssignment::default();
};
let chain_from = &info_from.ancestor_chain;
let chain_to = &info_to.ancestor_chain;
let lca_depth = chain_from
.iter()
.zip(chain_to.iter())
.take_while(|(a, b)| a == b)
.count();
let (from_face, to_face) = if lca_depth >= chain_from.len() {
Self::forward_faces(rank_dir)
} else {
let (f, t) = Self::forward_faces(rank_dir);
(t, f)
};
EdgeFaceAssignment {
from_face: Some(from_face),
to_face: Some(to_face),
}
}
}