use std::collections::BTreeMap;
use ifc_model::{EntityId, Model, Value};
use crate::error::PropertyAnomaly;
use crate::pset::{property_sets_by_object, AttachedSets, Attachment, PropertySet};
const REL_TYPE_RELATED_OBJECTS: usize = 4;
const REL_TYPE_RELATING_TYPE: usize = 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Source {
Occurrence,
Type(EntityId),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedSet {
pub source: Source,
pub set: PropertySet,
pub shadowed: Option<Box<ResolvedSet>>,
}
pub type ResolvedProperties = BTreeMap<EntityId, Vec<ResolvedSet>>;
pub fn resolved_properties(model: &Model) -> (ResolvedProperties, Vec<PropertyAnomaly>) {
let (direct, mut anomalies) = property_sets_by_object(model);
let types = type_assignments(model, &mut anomalies);
let mut objects: Vec<EntityId> = direct.keys().copied().collect();
objects.extend(types.keys().copied());
objects.sort_unstable();
objects.dedup();
duplicate_set_names(&direct, &mut anomalies);
let mut out: BTreeMap<EntityId, Vec<ResolvedSet>> = BTreeMap::new();
for object in objects {
let mut by_name: BTreeMap<String, ResolvedSet> = BTreeMap::new();
if let Some(&type_id) = types.get(&object) {
for (attachment, set) in direct.get(&type_id).into_iter().flatten() {
let _ = attachment;
if by_name.contains_key(&key(set)) {
continue;
}
by_name.insert(
key(set),
ResolvedSet {
source: Source::Type(type_id),
set: set.clone(),
shadowed: None,
},
);
}
}
for (attachment, set) in direct.get(&object).into_iter().flatten() {
if *attachment != Attachment::Occurrence {
continue;
}
if by_name
.get(&key(set))
.is_some_and(|r| r.source == Source::Occurrence)
{
continue;
}
let shadowed = by_name.remove(&key(set)).map(Box::new);
by_name.insert(
key(set),
ResolvedSet {
source: Source::Occurrence,
set: set.clone(),
shadowed,
},
);
}
if !by_name.is_empty() {
out.insert(object, by_name.into_values().collect());
}
}
(out, anomalies)
}
fn duplicate_set_names(direct: &AttachedSets, anomalies: &mut Vec<PropertyAnomaly>) {
for (&owner, sets) in direct {
let mut kept: BTreeMap<(bool, String), EntityId> = BTreeMap::new();
for (attachment, set) in sets {
let slot = (*attachment == Attachment::Type, key(set));
match kept.get(&slot) {
Some(&first) => anomalies.push(PropertyAnomaly::DuplicateSetName {
owner,
kept: first,
rejected: set.id,
}),
None => {
kept.insert(slot, set.id);
}
}
}
}
}
pub fn properties_of(model: &Model, object: EntityId) -> Vec<ResolvedSet> {
resolved_properties(model)
.0
.remove(&object)
.unwrap_or_default()
}
pub fn property_value<'a>(
resolved: &'a [ResolvedSet],
set_name: &str,
property_name: &str,
) -> Option<&'a crate::pset::Property> {
resolved
.iter()
.find(|r| r.set.name.as_deref() == Some(set_name))
.and_then(|r| r.set.property(property_name))
}
fn type_assignments(
model: &Model,
anomalies: &mut Vec<PropertyAnomaly>,
) -> BTreeMap<EntityId, EntityId> {
let mut out: BTreeMap<EntityId, EntityId> = BTreeMap::new();
let mut relations = model.ids_of_type("IFCRELDEFINESBYTYPE").to_vec();
relations.sort_unstable();
for id in relations {
let Some(rel) = model.get(id) else { continue };
let Some(type_id) = rel.attributes.get(REL_TYPE_RELATING_TYPE).and_then(one_ref) else {
continue;
};
if model.get(type_id).is_none() {
anomalies.push(PropertyAnomaly::MissingDefinition {
relationship: id,
definition: type_id,
});
continue;
}
for object in rel
.attributes
.get(REL_TYPE_RELATED_OBJECTS)
.and_then(refs)
.unwrap_or_default()
{
if model.get(object).is_none() {
anomalies.push(PropertyAnomaly::MissingObject {
relationship: id,
object,
});
continue;
}
match out.get(&object) {
None => {
out.insert(object, type_id);
}
Some(&kept) if kept != type_id => anomalies.push(PropertyAnomaly::TypedTwice {
object,
kept,
rejected: type_id,
relation: id,
}),
Some(_) => {}
}
}
}
out
}
fn key(set: &PropertySet) -> String {
match &set.name {
Some(name) => name.to_string(),
None => format!("#{}", set.id.0),
}
}
fn one_ref(value: &Value) -> Option<EntityId> {
match value.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
}
}
fn refs(value: &Value) -> Option<Vec<EntityId>> {
match value {
Value::List(items) => Some(items.iter().filter_map(one_ref).collect()),
_ => None,
}
}