use crate::detect::DiagramType;
use crate::errors::{Error, ErrorCode};
use crate::js::math::{max, min};
use crate::js::number::to_string;
#[derive(Clone, Debug, Default)]
pub(crate) struct Cell {
pub(crate) children: Vec<usize>,
pub(crate) columns: f64,
pub(crate) span: f64,
pub(crate) space: bool,
pub(crate) head: f64,
pub(crate) nw: f64,
pub(crate) nh: f64,
pub(crate) col: f64,
pub(crate) row: f64,
pub(crate) cols: f64,
pub(crate) heights: Vec<f64>,
pub(crate) row_gaps: Vec<f64>,
pub(crate) col_gaps: Vec<f64>,
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) w: f64,
pub(crate) h: f64,
}
const MAX_SPAN: f64 = 1000.0;
fn semantic(message: String) -> Error {
let mut e = Error::new(message, ErrorCode::Semantic);
e.diagram = Some(DiagramType::Block);
e
}
fn is_integer(v: f64) -> bool {
v.is_finite() && v.trunc() == v
}
pub(crate) fn calculate_block_position(columns: f64, position: f64) -> Result<(f64, f64), Error> {
if columns == 0.0 || !is_integer(columns) {
return Err(semantic("Columns must be an integer !== 0.".into()));
}
if position < 0.0 || !is_integer(position) {
return Err(semantic(format!(
"Position must be a non-negative integer.{}",
to_string(position)
)));
}
if columns < 0.0 {
return Ok((position, 0.0));
}
Ok((position % columns, (position / columns).floor()))
}
fn total(gaps: &[f64]) -> f64 {
gaps.iter().sum()
}
fn gap_at(gaps: &[f64], i: f64) -> f64 {
gaps.get(i as usize).copied().unwrap_or(0.0)
}
fn measure(cells: &mut [Cell], at: usize, gap: f64, inset: f64) -> Result<(), Error> {
let columns = cells[at].columns;
let mut heights: Vec<f64> = Vec::new();
let mut position = 0.0;
let mut cols = 0.0;
let mut cw = 0.0;
let mut tallest = 0.0;
for k in 0..cells[at].children.len() {
let c = cells[at].children[k];
let child = &mut cells[c];
let (px, py) = calculate_block_position(columns, position)?;
let mut span = if child.span >= 1.0 {
min(child.span, MAX_SPAN)
} else {
1.0
};
if columns > 0.0 && span > columns - px {
span = columns - px;
}
child.span = span;
child.col = px;
child.row = py;
position += span;
if px + span > cols {
cols = px + span;
}
let row = py as usize;
if row == heights.len() {
heights.push(0.0);
}
if child.children.is_empty() {
heights[row] = -1.0;
} else if heights[row] >= 0.0 && child.nh > heights[row] {
heights[row] = child.nh;
}
if child.space {
continue;
}
let width = (child.nw - gap * (span - 1.0)) / span;
if width > cw {
cw = width;
}
if child.nh > tallest {
tallest = child.nh;
}
}
let b = &mut cells[at];
let mut height = (heights.len() as f64 - 1.0) * gap + total(&b.row_gaps);
for h in &mut heights {
if *h < 0.0 {
*h = tallest;
}
height += *h;
}
b.cols = cols;
b.heights = heights;
b.nw = max(
b.nw,
cols * cw + (cols - 1.0) * gap + total(&b.col_gaps) + 2.0 * inset,
);
b.nh = height + 2.0 * inset + b.head;
Ok(())
}
fn arrange(cells: &mut [Cell], at: usize, gap: f64, inset: f64) {
let b = &cells[at];
let cols = b.cols;
let rows = b.heights.len() as f64;
let cw = (b.w - 2.0 * inset - (cols - 1.0) * gap - total(&b.col_gaps)) / cols;
let room = b.h - 2.0 * inset - b.head - (rows - 1.0) * gap - total(&b.row_gaps);
let mut offsets: Vec<f64> = Vec::new();
if !b.col_gaps.is_empty() {
let mut sum = 0.0;
let mut col = 0.0;
while col < cols {
offsets.push(sum);
sum += gap_at(&b.col_gaps, col);
col += 1.0;
}
}
let needed: f64 = b.heights.iter().sum();
let scale = if needed > 0.0 { room / needed } else { 0.0 };
let share = if needed > 0.0 { 0.0 } else { room / rows };
let left = b.x - b.w / 2.0 + inset;
let mut top = b.y - b.h / 2.0 + inset + b.head;
let (heights, row_gaps, children) = (b.heights.clone(), b.row_gaps.clone(), b.children.clone());
let mut row = 0.0;
for k in children {
let child = &mut cells[k];
while row < child.row {
top += heights[row as usize] * scale + share + gap + gap_at(&row_gaps, row);
row += 1.0;
}
let before = gap_at(&offsets, child.col);
child.w = cw * child.span
+ gap * (child.span - 1.0)
+ gap_at(&offsets, child.col + child.span - 1.0)
- before;
child.h = heights[row as usize] * scale + share;
child.x = left + child.col * (cw + gap) + before + child.w / 2.0;
child.y = top + child.h / 2.0;
}
}
pub(crate) fn layout(cells: &mut [Cell], gap: f64) -> Result<(f64, f64), Error> {
let mut order = vec![0];
let mut i = 0;
while i < order.len() {
order.extend(cells[order[i]].children.iter().copied());
i += 1;
}
for (i, &c) in order.iter().enumerate().rev() {
if !cells[c].children.is_empty() {
measure(cells, c, gap, if i == 0 { 0.0 } else { gap })?;
}
}
let root = &mut cells[0];
root.w = root.nw;
root.h = root.nh;
root.x = root.w / 2.0;
root.y = root.h / 2.0;
for (i, &c) in order.iter().enumerate() {
if !cells[c].children.is_empty() {
arrange(cells, c, gap, if i == 0 { 0.0 } else { gap });
}
}
Ok((cells[0].w, cells[0].h))
}