use std::collections::BTreeMap;
use std::fmt;
use std::ops::Bound;
use miette::SourceSpan;
use serde::de;
use serde::{Deserialize, Deserializer, Serialize};
use strum::{EnumString, IntoStaticStr, VariantNames};
use crate::legend::Legend;
use crate::path::ElementPath;
use crate::unique_key_map::UniqueKeyMapVisitor;
pub const MAX_MAGNITUDE: f64 = 1_000_000.0;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Model {
pub(crate) title: Option<String>,
pub(crate) subtitle: Option<String>,
pub(crate) description: Option<String>,
pub(crate) legend: Option<Legend>,
pub(crate) elements: BTreeMap<ElementPath, Element>,
pub(crate) edges: BTreeMap<EdgeId, Edge>,
}
impl Model {
pub(crate) fn insert_edge_with_next_free_ordinal(
&mut self,
from: ElementPath,
to: ElementPath,
edge: Edge,
) -> EdgeId {
let first_parallel_id = EdgeId::new(from, to, 0);
let last_parallel_id = first_parallel_id.parallel(u32::MAX);
let parallel_ids = &first_parallel_id..=&last_parallel_id;
let mut parallel_edges = self.edges.range::<EdgeId, _>(parallel_ids);
let last_parallel_edge = parallel_edges.next_back();
let ordinal = last_parallel_edge.map_or(0, |(id, _edge)| id.ordinal() + 1);
let id = first_parallel_id.parallel(ordinal);
let inserted_id = id.clone();
self.edges.insert(inserted_id, edge);
id
}
pub fn layoutable_edges(&self) -> impl Iterator<Item = (&EdgeId, &Edge)> {
self.edges
.iter()
.filter(|(id, _edge)| !id.joins_ancestor_and_descendant())
}
pub fn arc_edges(&self) -> impl Iterator<Item = (&EdgeId, &Edge)> {
self.layoutable_edges()
.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 title(&self) -> Option<&str> {
self.title.as_deref()
}
pub fn subtitle(&self) -> Option<&str> {
self.subtitle.as_deref()
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn legend(&self) -> Option<&Legend> {
self.legend.as_ref()
}
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
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
struct MovedElementBadge;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Element {
label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
caption: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
shape: Option<Shape>,
#[serde(default, skip_serializing_if = "Option::is_none")]
height: Option<f64>,
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
deserialize_with = "UniqueKeyMapVisitor::deserialize"
)]
style: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
child_placement: Option<PlacementHint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
position: Option<Position>,
#[serde(default, skip_serializing_if = "Option::is_none")]
link: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
link_span: Option<ByteRange>,
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
deserialize_with = "UniqueKeyMapVisitor::deserialize"
)]
metadata: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
span: Option<ByteRange>,
#[serde(default, rename = "badge", skip_serializing)]
moved_badge: MovedElementBadge,
}
impl Element {
pub fn new(label: impl Into<String>) -> Self {
let label = label.into();
Self {
label,
caption: None,
description: None,
shape: None,
height: None,
style: BTreeMap::new(),
child_placement: None,
position: None,
link: None,
link_span: None,
metadata: BTreeMap::new(),
span: None,
moved_badge: MovedElementBadge,
}
}
pub fn relabeled(mut self, label: impl Into<String>) -> Self {
self.label = label.into();
self
}
pub fn with_caption(mut self, caption: impl Into<String>) -> Self {
let caption = caption.into();
self.caption = Some(caption);
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
let description = description.into();
self.description = Some(description);
self
}
pub fn with_shape(mut self, shape: Shape) -> Self {
self.shape = Some(shape);
self
}
pub fn with_height(mut self, height: f64) -> Self {
self.height = Some(height);
self
}
pub fn with_style(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let key = key.into();
let value = value.into();
self.style.insert(key, value);
self
}
pub fn with_child_placement(mut self, child_placement: PlacementHint) -> Self {
self.child_placement = Some(child_placement);
self
}
pub fn with_position(mut self, position: Position) -> Self {
self.position = Some(position);
self
}
pub fn with_link(mut self, link: impl Into<String>) -> Self {
let link = link.into();
self.link = Some(link);
self
}
pub fn with_link_span(mut self, link_span: ByteRange) -> Self {
self.link_span = Some(link_span);
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let key = key.into();
let value = value.into();
self.metadata.insert(key, value);
self
}
pub fn with_span(mut self, span: ByteRange) -> Self {
self.span = Some(span);
self
}
pub fn label(&self) -> &str {
&self.label
}
pub fn caption(&self) -> Option<&str> {
self.caption.as_deref()
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn shape(&self) -> Option<Shape> {
self.shape
}
pub fn height(&self) -> Option<f64> {
self.height
}
pub fn style(&self) -> &BTreeMap<String, String> {
&self.style
}
pub fn child_placement(&self) -> Option<PlacementHint> {
self.child_placement
}
pub fn position(&self) -> Option<Position> {
self.position
}
pub fn link(&self) -> Option<&str> {
self.link.as_deref()
}
pub fn link_span(&self) -> Option<ByteRange> {
self.link_span
}
pub fn metadata(&self) -> &BTreeMap<String, String> {
&self.metadata
}
pub fn span(&self) -> Option<ByteRange> {
self.span
}
pub fn source_span(&self) -> Option<SourceSpan> {
self.span.map(SourceSpan::from)
}
}
impl<'de> Deserialize<'de> for MovedElementBadge {
fn deserialize<D: Deserializer<'de>>(_deserializer: D) -> Result<Self, D::Error> {
let moved_badge_message = "`badge` moved from elements to edges: set `\"badge\": true` on each edge into this element";
let moved_badge_error = de::Error::custom(moved_badge_message);
Err(moved_badge_error)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(EnumString, IntoStaticStr, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum Shape {
Box,
Slab,
Cylinder,
Disc,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[derive(EnumString, IntoStaticStr, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum Traveler {
Dot,
Envelope,
Parcel,
Document,
Key,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", deny_unknown_fields)]
#[derive(EnumString, IntoStaticStr, VariantNames)]
#[strum(serialize_all = "lowercase")]
pub enum PlacementHint {
Layered,
Grid { columns: u32 },
Row,
Column,
}
impl PlacementHint {
pub const MAX_PACKED_TRACKS: u32 = 32_766;
pub fn packed_tracks(self, child_count: usize) -> Option<(usize, usize)> {
match self {
Self::Layered | Self::Grid { columns: 0 } => None,
Self::Grid { columns } => {
let column_count = usize::try_from(columns).unwrap_or(usize::MAX);
let row_count = child_count.div_ceil(column_count);
Some((column_count, row_count))
}
Self::Row => Some((child_count, 1)),
Self::Column => Some((1, child_count)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Position {
pub x: f64,
pub y: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ByteRange {
pub start: usize,
pub end: usize,
}
impl ByteRange {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
}
impl From<ByteRange> for SourceSpan {
fn from(range: ByteRange) -> Self {
let byte_range = range.start..range.end;
byte_range.into()
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EdgeId {
from: ElementPath,
to: ElementPath,
ordinal: u32,
}
impl EdgeId {
pub fn new(from: ElementPath, to: ElementPath, ordinal: u32) -> Self {
Self { from, to, ordinal }
}
pub fn home_level(&self) -> ElementPath {
let is_self_loop = self.from == self.to;
if is_self_loop {
return self.from.enclosing_level();
}
self.from.lowest_common_ancestor(&self.to)
}
pub(crate) fn parallel(&self, ordinal: u32) -> Self {
let from = self.from.clone();
let to = self.to.clone();
Self { from, to, ordinal }
}
pub fn joins_ancestor_and_descendant(&self) -> bool {
let is_from_ancestor = self.from.is_ancestor_of(&self.to);
let is_to_ancestor = self.to.is_ancestor_of(&self.from);
is_from_ancestor || is_to_ancestor
}
pub fn from(&self) -> &ElementPath {
&self.from
}
pub fn to(&self) -> &ElementPath {
&self.to
}
pub fn ordinal(&self) -> u32 {
self.ordinal
}
}
impl fmt::Display for EdgeId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"({} -> {})[{}]",
self.from, self.to, self.ordinal
)
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Edge {
pub(crate) label: Option<String>,
pub(crate) description: Option<String>,
pub(crate) style: BTreeMap<String, String>,
pub(crate) traveler: Option<Traveler>,
pub(crate) badge: bool,
pub(crate) span: Option<ByteRange>,
}
impl Edge {
pub fn new() -> Self {
Self::default()
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
let label = label.into();
self.label = Some(label);
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
let description = description.into();
self.description = Some(description);
self
}
pub fn with_style(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let key = key.into();
let value = value.into();
self.style.insert(key, value);
self
}
pub fn with_traveler(mut self, traveler: Traveler) -> Self {
self.traveler = Some(traveler);
self
}
pub fn with_badge(mut self) -> Self {
self.badge = true;
self
}
pub fn with_span(mut self, span: ByteRange) -> Self {
self.span = Some(span);
self
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn style(&self) -> &BTreeMap<String, String> {
&self.style
}
pub fn traveler(&self) -> Option<Traveler> {
self.traveler
}
pub fn is_badge(&self) -> bool {
self.badge
}
pub fn span(&self) -> Option<ByteRange> {
self.span
}
pub fn source_span(&self) -> Option<SourceSpan> {
self.span.map(SourceSpan::from)
}
}