use std::collections::BTreeMap;
use ifc_model::{EntityId, Model, Value};
use crate::slots::Slots;
pub mod slot {
pub const RELATING_BUILDING_ELEMENT: usize = 4;
pub const RELATED_OPENING_ELEMENT: usize = 5;
}
const VOIDS_ELEMENT: &str = "IFCRELVOIDSELEMENT";
#[cfg_attr(not(feature = "lowering"), allow(dead_code))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Voiding {
pub(crate) relation: EntityId,
pub(crate) opening: EntityId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub struct VoidingConflict {
pub opening: EntityId,
pub kept_host: EntityId,
pub rejected_host: EntityId,
pub relation: EntityId,
}
fn all_voidings(model: &Model) -> Vec<(EntityId, EntityId, EntityId)> {
let mut all: Vec<_> = model
.of_type(VOIDS_ELEMENT)
.filter_map(|(relation, entity)| {
let slots = Slots::new(relation, entity);
let host = ref_at(slots.opt(slot::RELATING_BUILDING_ELEMENT))?;
let opening = ref_at(slots.opt(slot::RELATED_OPENING_ELEMENT))?;
Some((relation, host, opening))
})
.collect();
all.sort_unstable();
all
}
fn kept_hosts(all: &[(EntityId, EntityId, EntityId)]) -> BTreeMap<EntityId, EntityId> {
let mut kept = BTreeMap::new();
for &(_, host, opening) in all {
kept.entry(opening).or_insert(host);
}
kept
}
#[must_use]
pub fn voiding_conflicts(model: &Model) -> Vec<VoidingConflict> {
let all = all_voidings(model);
let kept = kept_hosts(&all);
all.iter()
.filter(|(_, host, opening)| kept[opening] != *host)
.map(|&(relation, host, opening)| VoidingConflict {
opening,
kept_host: kept[&opening],
rejected_host: host,
relation,
})
.collect()
}
pub(crate) fn voidings_of(model: &Model, host: EntityId) -> Vec<Voiding> {
let all = all_voidings(model);
let kept = kept_hosts(&all);
let mut found: Vec<Voiding> = all
.into_iter()
.filter(|(_, relating, opening)| *relating == host && kept[opening] == host)
.map(|(relation, _, opening)| Voiding { relation, opening })
.collect();
found.sort_by_key(|voiding| (voiding.opening, voiding.relation));
found.dedup_by_key(|voiding| voiding.opening);
found
}
pub fn openings_of(model: &Model, host: EntityId) -> Vec<EntityId> {
voidings_of(model, host)
.into_iter()
.map(|voiding| voiding.opening)
.collect()
}
fn ref_at(value: Option<&Value>) -> Option<EntityId> {
match value? {
Value::Ref(id) => Some(*id),
_ => None,
}
}