use std::sync::Arc;
use ifc_model::EntityId;
use super::read::{Lining, Panel};
use super::{
RefusedWindowOperation, WindowOperationError, WindowPanelOperation, WindowPanelPosition,
WindowPartitioning,
};
#[derive(Debug, Clone)]
pub(super) struct Placed {
pub(super) panel: Panel,
pub(super) x: [f64; 2],
pub(super) z: [f64; 2],
}
type Cell = (WindowPanelPosition, [f64; 2], [f64; 2]);
const ALL: [f64; 2] = [0.0, 1.0];
pub(super) fn classify_partitioning(
written: &Arc<str>,
) -> Result<WindowPartitioning, WindowOperationError> {
use WindowPartitioning as P;
let refuse = |reason| {
Err(WindowOperationError::RefusedPartitioning {
partitioning: written.clone(),
reason,
})
};
Ok(match &**written {
"SINGLE_PANEL" => P::SinglePanel,
"DOUBLE_PANEL_VERTICAL" => P::DoublePanelVertical,
"DOUBLE_PANEL_HORIZONTAL" => P::DoublePanelHorizontal,
"TRIPLE_PANEL_VERTICAL" => P::TriplePanelVertical,
"TRIPLE_PANEL_HORIZONTAL" => P::TriplePanelHorizontal,
"TRIPLE_PANEL_BOTTOM" => P::TriplePanelBottom,
"TRIPLE_PANEL_TOP" => P::TriplePanelTop,
"TRIPLE_PANEL_LEFT" => P::TriplePanelLeft,
"TRIPLE_PANEL_RIGHT" => P::TriplePanelRight,
"USERDEFINED" => return refuse(RefusedWindowOperation::UserDefined),
_ => return refuse(RefusedWindowOperation::NotDefined),
})
}
pub(super) fn classify_panel(
set: EntityId,
written: &Arc<str>,
) -> Result<WindowPanelOperation, WindowOperationError> {
use WindowPanelOperation as O;
let refuse = |reason| {
Err(WindowOperationError::RefusedPanelOperation {
set,
operation: written.clone(),
reason,
})
};
Ok(match &**written {
"SIDEHUNGRIGHTHAND" => O::SideHungRightHand,
"SIDEHUNGLEFTHAND" => O::SideHungLeftHand,
"TILTANDTURNRIGHTHAND" => O::TiltAndTurnRightHand,
"TILTANDTURNLEFTHAND" => O::TiltAndTurnLeftHand,
"TOPHUNG" => O::TopHung,
"BOTTOMHUNG" => O::BottomHung,
"SLIDINGHORIZONTAL" => O::SlidingHorizontal,
"SLIDINGVERTICAL" => O::SlidingVertical,
"REMOVABLECASEMENT" => O::RemovableCasement,
"FIXEDCASEMENT" => O::FixedCasement,
"PIVOTHORIZONTAL" | "PIVOTVERTICAL" => return refuse(RefusedWindowOperation::Pivot),
"OTHEROPERATION" => return refuse(RefusedWindowOperation::OtherOperation),
_ => return refuse(RefusedWindowOperation::NotDefined),
})
}
pub(super) fn place_panels(
window: EntityId,
partitioning: WindowPartitioning,
panels: &[Panel],
lining: Option<&Lining>,
) -> Result<Vec<Placed>, WindowOperationError> {
if partitioning == WindowPartitioning::SinglePanel {
count(panels, 1)?;
return Ok(vec![Placed {
panel: panels[0].clone(),
x: ALL,
z: ALL,
}]);
}
let lining = lining.ok_or(WindowOperationError::NoLiningProperties { window })?;
let cells = cells(partitioning, lining)?;
count(panels, cells.len())?;
let listed: Vec<WindowPanelPosition> = cells.iter().map(|cell| cell.0).collect();
cells
.into_iter()
.map(|(position, x, z)| {
Ok(Placed {
panel: at(panels, position, &listed)?.clone(),
x,
z,
})
})
.collect()
}
fn cells(
partitioning: WindowPartitioning,
lining: &Lining,
) -> Result<Vec<Cell>, WindowOperationError> {
use WindowPanelPosition::{Bottom, Left, Middle, Right, Top};
use WindowPartitioning as P;
let mullion = || split(lining, lining.first_mullion, "FirstMullionOffset", 0.0);
let transom = || split(lining, lining.first_transom, "FirstTransomOffset", 0.0);
Ok(match partitioning {
P::DoublePanelVertical => {
let m1 = mullion()?;
vec![(Left, [0.0, m1], ALL), (Right, [m1, 1.0], ALL)]
}
P::DoublePanelHorizontal => {
let t1 = transom()?;
vec![(Top, ALL, [t1, 1.0]), (Bottom, ALL, [0.0, t1])]
}
P::TriplePanelVertical => {
let m1 = mullion()?;
let m2 = split(lining, lining.second_mullion, "SecondMullionOffset", m1)?;
vec![
(Left, [0.0, m1], ALL),
(Middle, [m1, m2], ALL),
(Right, [m2, 1.0], ALL),
]
}
P::TriplePanelHorizontal => {
let t1 = transom()?;
let t2 = split(lining, lining.second_transom, "SecondTransomOffset", t1)?;
vec![
(Top, ALL, [t2, 1.0]),
(Middle, ALL, [t1, t2]),
(Bottom, ALL, [0.0, t1]),
]
}
P::TriplePanelBottom => {
let (m1, t1) = (mullion()?, transom()?);
vec![
(Left, [0.0, m1], [t1, 1.0]),
(Right, [m1, 1.0], [t1, 1.0]),
(Bottom, ALL, [0.0, t1]),
]
}
P::TriplePanelTop => {
let (m1, t1) = (mullion()?, transom()?);
vec![
(Top, ALL, [t1, 1.0]),
(Left, [0.0, m1], [0.0, t1]),
(Right, [m1, 1.0], [0.0, t1]),
]
}
P::TriplePanelLeft => {
let (m1, t1) = (mullion()?, transom()?);
vec![
(Left, [0.0, m1], ALL),
(Top, [m1, 1.0], [t1, 1.0]),
(Bottom, [m1, 1.0], [0.0, t1]),
]
}
P::TriplePanelRight => {
let (m1, t1) = (mullion()?, transom()?);
vec![
(Top, [0.0, m1], [t1, 1.0]),
(Bottom, [0.0, m1], [0.0, t1]),
(Right, [m1, 1.0], ALL),
]
}
P::SinglePanel => vec![(WindowPanelPosition::NotDefined, ALL, ALL)],
})
}
fn split(
lining: &Lining,
value: Option<f64>,
attribute: &'static str,
after: f64,
) -> Result<f64, WindowOperationError> {
let value = value.ok_or(WindowOperationError::MissingSplit {
set: lining.set,
attribute,
})?;
if value > after && value < 1.0 {
Ok(value)
} else {
Err(WindowOperationError::InvalidSplit {
set: lining.set,
attribute,
value,
})
}
}
fn count(panels: &[Panel], expected: usize) -> Result<(), WindowOperationError> {
if panels.len() == expected {
Ok(())
} else {
Err(WindowOperationError::PanelCount {
expected,
found: panels.len(),
})
}
}
fn at<'p>(
panels: &'p [Panel],
position: WindowPanelPosition,
listed: &[WindowPanelPosition],
) -> Result<&'p Panel, WindowOperationError> {
let mut found = panels.iter().filter(|panel| panel.position == position);
match (found.next(), found.next()) {
(Some(panel), None) => Ok(panel),
(Some(_), Some(second)) => Err(mismatch(second)),
(None, _) => {
let foreign = panels
.iter()
.find(|panel| !listed.contains(&panel.position));
let repeated = || {
panels.iter().enumerate().find_map(|(index, panel)| {
panels[..index]
.iter()
.any(|earlier| earlier.position == panel.position)
.then_some(panel)
})
};
let stray = foreign.or_else(repeated).unwrap_or(&panels[0]);
Err(mismatch(stray))
}
}
}
fn mismatch(panel: &Panel) -> WindowOperationError {
WindowOperationError::PanelMismatch {
set: panel.set,
attribute: "PanelPosition",
found: position_name(panel.position).into(),
}
}
fn position_name(position: WindowPanelPosition) -> &'static str {
match position {
WindowPanelPosition::Left => "LEFT",
WindowPanelPosition::Middle => "MIDDLE",
WindowPanelPosition::Right => "RIGHT",
WindowPanelPosition::Bottom => "BOTTOM",
WindowPanelPosition::Top => "TOP",
WindowPanelPosition::NotDefined => "NOTDEFINED",
}
}