use std::collections::{BTreeMap, BTreeSet};
use std::iter;
use std::ops::Bound;
use crate::link::Link;
use crate::model::{Edge, EdgeId, Element, Model, PlacementHint, Position};
use crate::path::ElementPath;
use crate::workflow::{ElementOverride, StepIndex, StepItem, Workflow};
const NO_STEPS: &BTreeSet<StepIndex> = &BTreeSet::new();
#[derive(Clone, Debug, Default, PartialEq)]
pub struct WorkflowGraph {
elements: BTreeMap<ElementPath, Element>,
child_placements: BTreeMap<ElementPath, PlacementHint>,
positions: BTreeMap<ElementPath, Position>,
unshown_link_targets: BTreeMap<ElementPath, ElementPath>,
edges: BTreeMap<EdgeId, Edge>,
element_steps: BTreeMap<ElementPath, BTreeSet<StepIndex>>,
edge_steps: BTreeMap<EdgeId, BTreeSet<StepIndex>>,
}
impl WorkflowGraph {
pub(crate) fn of(model: &Model, workflow: &Workflow) -> Self {
let (mut element_steps, edge_steps) = Self::step_memberships(workflow);
element_steps.retain(|path, _steps| model.element(path).is_some());
let mut elements = BTreeMap::new();
let mut child_placements = BTreeMap::new();
let mut positions = BTreeMap::new();
let mut unshown_link_targets = BTreeMap::new();
for path in element_steps.keys() {
let element = &model.elements[path];
let element_override = workflow.override_of(path);
let mut shown_element = element_override.map_or_else(
|| element.clone(),
|element_override| element_override.applied_to(element),
);
let unshown_link_target =
Self::unshown_link_target_of(&shown_element, model, &element_steps);
if let Some(unshown_link_target) = unshown_link_target {
shown_element.properties.link = None;
unshown_link_targets.insert(path.clone(), unshown_link_target);
}
elements.insert(path.clone(), shown_element);
let child_placement = element_override.and_then(ElementOverride::child_placement);
if let Some(child_placement) = child_placement {
child_placements.insert(path.clone(), child_placement);
}
let position = element_override.and_then(ElementOverride::position);
if let Some(position) = position {
positions.insert(path.clone(), position);
}
}
let edges = edge_steps
.keys()
.map(|id| {
let edge = workflow.edges[id].clone();
(id.clone(), edge)
})
.collect();
Self {
elements,
child_placements,
positions,
unshown_link_targets,
edges,
element_steps,
edge_steps,
}
}
fn step_memberships(
workflow: &Workflow,
) -> (
BTreeMap<ElementPath, BTreeSet<StepIndex>>,
BTreeMap<EdgeId, BTreeSet<StepIndex>>,
) {
let mut element_steps: BTreeMap<ElementPath, BTreeSet<StepIndex>> = BTreeMap::new();
let mut edge_steps: BTreeMap<EdgeId, BTreeSet<StepIndex>> = BTreeMap::new();
for (position, step) in workflow.steps().iter().enumerate() {
let step_index = StepIndex::new(position);
for item in step.items() {
let listed_paths = match item {
StepItem::Element(path) => vec![path],
StepItem::Edge(id) if workflow.edge(id).is_none() => continue,
StepItem::Edge(id) => {
let listing_steps = edge_steps.entry(id.clone()).or_default();
listing_steps.insert(step_index);
vec![id.from(), id.to()]
}
};
for listed_path in listed_paths {
let ancestors = listed_path.ancestors();
let lineage = ancestors.chain(iter::once(listed_path.clone()));
for shown_path in lineage {
let taking_part_steps = element_steps.entry(shown_path).or_default();
taking_part_steps.insert(step_index);
}
}
}
}
(element_steps, edge_steps)
}
fn unshown_link_target_of(
element: &Element,
model: &Model,
shown_steps: &BTreeMap<ElementPath, BTreeSet<StepIndex>>,
) -> Option<ElementPath> {
let properties = element.properties();
let raw_link = properties.link()?;
let declared_elements = model.elements();
let link = Link::resolve(raw_link, declared_elements);
let Link::Element(target) = link else {
return None;
};
let is_target_shown = shown_steps.contains_key(&target);
if is_target_shown {
return None;
}
Some(target)
}
pub(crate) fn edgeless_down_to_depth(&self, depth: usize) -> Self {
let elements = Self::entries_down_to_depth(&self.elements, depth);
let child_placements = Self::entries_down_to_depth(&self.child_placements, depth);
let positions = Self::entries_down_to_depth(&self.positions, depth);
let unshown_link_targets = Self::entries_down_to_depth(&self.unshown_link_targets, depth);
let element_steps = Self::entries_down_to_depth(&self.element_steps, depth);
Self {
elements,
child_placements,
positions,
unshown_link_targets,
edges: BTreeMap::new(),
element_steps,
edge_steps: BTreeMap::new(),
}
}
fn entries_down_to_depth<V: Clone>(
by_path: &BTreeMap<ElementPath, V>,
depth: usize,
) -> BTreeMap<ElementPath, V> {
let kept_entries = by_path
.iter()
.filter(|(path, _value)| path.depth() <= depth);
kept_entries
.map(|(path, value)| (path.clone(), value.clone()))
.collect()
}
pub(crate) fn insert_edge(&mut self, id: EdgeId, edge: Edge, steps: BTreeSet<StepIndex>) {
self.edges.insert(id.clone(), edge);
self.edge_steps.insert(id, steps);
}
pub fn arc_edges(&self) -> impl Iterator<Item = (&EdgeId, &Edge)> {
self.edges.iter().filter(|(_id, edge)| !edge.is_badge())
}
pub fn badge_edges(&self) -> impl Iterator<Item = &EdgeId> {
let badge_edges = self.edges.iter().filter(|(_id, edge)| edge.is_badge());
badge_edges.map(|(id, _edge)| id)
}
pub fn badge_edges_from<'a>(
&'a self,
source: &'a ElementPath,
) -> impl Iterator<Item = &'a EdgeId> {
let source_path = source.clone();
let root = ElementPath::root();
let first_edge_from_source = EdgeId::new(source_path, root, 0);
let from_source_onwards = self.edges.range(first_edge_from_source..);
let edges_from_source =
from_source_onwards.take_while(move |(id, _edge)| id.from() == source);
let badge_edges_from_source = edges_from_source.filter(|(_id, edge)| edge.is_badge());
badge_edges_from_source.map(|(id, _edge)| id)
}
pub fn is_badge_edge(&self, id: &EdgeId) -> bool {
let edge = self.edge(id);
edge.is_some_and(Edge::is_badge)
}
pub fn children<'a>(
&'a self,
parent: &'a ElementPath,
) -> impl Iterator<Item = (&'a ElementPath, &'a Element)> {
let child_depth = parent.depth() + 1;
self.descendants(parent)
.filter(move |(path, _element)| path.depth() == child_depth)
}
pub fn descendants<'a>(
&'a self,
ancestor: &'a ElementPath,
) -> impl Iterator<Item = (&'a ElementPath, &'a Element)> {
let after_ancestor = (Bound::Excluded(ancestor), Bound::Unbounded);
self.elements
.range::<ElementPath, _>(after_ancestor)
.take_while(|(path, _element)| ancestor.is_ancestor_of(path))
}
pub fn is_container(&self, path: &ElementPath) -> bool {
let mut descendants = self.descendants(path);
descendants.next().is_some()
}
pub fn child_placement(&self, path: &ElementPath) -> Option<PlacementHint> {
self.child_placements.get(path).copied()
}
pub fn position(&self, path: &ElementPath) -> Option<Position> {
self.positions.get(path).copied()
}
pub(crate) fn unshown_link_target(&self, path: &ElementPath) -> Option<&ElementPath> {
self.unshown_link_targets.get(path)
}
pub fn steps_of_element(&self, path: &ElementPath) -> &BTreeSet<StepIndex> {
self.element_steps.get(path).unwrap_or(NO_STEPS)
}
pub fn steps_of_edge(&self, id: &EdgeId) -> &BTreeSet<StepIndex> {
self.edge_steps.get(id).unwrap_or(NO_STEPS)
}
pub fn element(&self, path: &ElementPath) -> Option<&Element> {
self.elements.get(path)
}
pub fn elements(&self) -> &BTreeMap<ElementPath, Element> {
&self.elements
}
pub fn edge(&self, id: &EdgeId) -> Option<&Edge> {
self.edges.get(id)
}
pub fn edges(&self) -> &BTreeMap<EdgeId, Edge> {
&self.edges
}
}