use std::collections::HashMap;
use super::{Fragment, FragmentOp, Item, MessageSort, Sequence};
pub(crate) const FRAME_LEFT: i64 = 8;
pub(crate) const FRAME_TOP: i64 = 8;
pub(crate) const HEAD_TOP: i64 = 40;
pub(crate) const HEAD_W: i64 = 100;
pub(crate) const HEAD_H: i64 = 40;
pub(crate) const LINE_TOP: i64 = HEAD_TOP + HEAD_H; pub(crate) const SELF_EXTRA: i64 = 24; pub(crate) const FRAG_HEADER: i64 = 28; const FIRST_HEAD_LEFT: i64 = 24;
const LL_GAP: i64 = 150; const FIRST_MSG_Y: i64 = LINE_TOP + 32;
const ROW: i64 = 40; const OPERAND_DIV: i64 = 22; const FRAG_PAD: i64 = 12; const FRAG_GAP: i64 = 12; const FRAG_MARGIN: i64 = 30;
pub(crate) struct PMsg {
pub(crate) from: usize,
pub(crate) to: usize,
pub(crate) text: String,
pub(crate) sort: MessageSort,
pub(crate) y: i64,
pub(crate) self_loop: bool,
}
pub(crate) struct PFrag {
pub(crate) operator: FragmentOp,
pub(crate) left: i64,
pub(crate) width: i64,
pub(crate) top: i64,
pub(crate) height: i64,
pub(crate) operands: Vec<(String, i64, i64)>,
}
pub(crate) struct Layout {
pub(crate) centers: Vec<i64>,
pub(crate) msgs: Vec<PMsg>,
pub(crate) frags: Vec<PFrag>,
pub(crate) bottom: i64,
index: HashMap<String, usize>,
y: i64,
}
pub(crate) fn layout(seq: &Sequence) -> Layout {
let mut lay = Layout::new(seq);
lay.walk(&seq.items);
lay
}
impl Layout {
fn new(seq: &Sequence) -> Self {
let mut centers = Vec::with_capacity(seq.participants.len());
let mut index = HashMap::new();
for (i, p) in seq.participants.iter().enumerate() {
centers.push(FIRST_HEAD_LEFT + HEAD_W / 2 + i as i64 * LL_GAP);
index.insert(p.id.clone(), i);
}
Layout {
centers,
index,
msgs: Vec::new(),
frags: Vec::new(),
y: FIRST_MSG_Y,
bottom: FIRST_MSG_Y,
}
}
fn idx(&self, id: &str) -> usize {
*self.index.get(id).unwrap_or(&0)
}
fn walk(&mut self, items: &[Item]) {
for item in items {
match item {
Item::Message(m) => {
let from = self.idx(&m.from);
let to = self.idx(&m.to);
let self_loop = from == to;
self.msgs.push(PMsg {
from,
to,
text: m.text.clone(),
sort: m.sort,
y: self.y,
self_loop,
});
self.y += if self_loop { ROW + SELF_EXTRA } else { ROW };
}
Item::Activate(_) | Item::Deactivate(_) | Item::Note(_) => {}
Item::Fragment(f) => {
let top = self.y;
self.y += FRAG_HEADER;
let mut operands = Vec::with_capacity(f.operands.len());
for (i, operand) in f.operands.iter().enumerate() {
if i > 0 {
self.y += OPERAND_DIV;
}
let op_top = self.y;
self.walk(&operand.items);
if self.y == op_top {
self.y += ROW; }
operands.push((operand.guard.clone(), op_top, self.y - op_top));
}
self.y += FRAG_PAD;
let bottom = self.y;
let (left, right) = self.span(f);
self.frags.push(PFrag {
operator: f.operator,
left,
width: right - left,
top,
height: bottom - top,
operands,
});
self.y += FRAG_GAP;
}
}
self.bottom = self.bottom.max(self.y);
}
}
fn span(&self, frag: &Fragment) -> (i64, i64) {
let mut touched = Vec::new();
for op in &frag.operands {
for it in &op.items {
collect(it, &self.index, &mut touched);
}
}
if touched.is_empty() {
let last = self.centers.len().saturating_sub(1);
return (
self.centers.first().copied().unwrap_or(0) - FRAG_MARGIN,
self.centers.get(last).copied().unwrap_or(0) + FRAG_MARGIN,
);
}
let min = touched.iter().map(|&i| self.centers[i]).min().unwrap();
let max = touched.iter().map(|&i| self.centers[i]).max().unwrap();
(min - FRAG_MARGIN, max + FRAG_MARGIN)
}
}
fn collect(item: &Item, index: &HashMap<String, usize>, out: &mut Vec<usize>) {
let mut add = |id: &str| {
if let Some(&i) = index.get(id) {
if !out.contains(&i) {
out.push(i);
}
}
};
match item {
Item::Message(m) => {
add(&m.from);
add(&m.to);
}
Item::Activate(x) | Item::Deactivate(x) => add(x),
Item::Note(n) => n.targets.iter().for_each(|t| add(t)),
Item::Fragment(f) => {
for op in &f.operands {
for it in &op.items {
collect(it, index, out);
}
}
}
}
}