use std::collections::BTreeSet;
use std::sync::Arc;
use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::SchemaVersion;
use crate::nesting::{MAX_COMPLEX_DEPTH, MAX_COMPLEX_MEMBERS};
use super::quantity::{simple_quantity_value, slot};
use super::refs::{nonempty_refs_at, text_at};
use super::release::Release;
use super::set::simple_property_value;
use super::value::ResolvedValue;
use super::{ExactComplexMember, ExactComplexValue, ExactPropertyError, ExactValue};
pub(super) fn is_complex(release: Release, entity: &Entity) -> bool {
let type_name = entity.type_name.as_ref();
release.schema.is_a(type_name, "IFCCOMPLEXPROPERTY")
|| release.schema.is_a(type_name, "IFCPHYSICALCOMPLEXQUANTITY")
}
pub(super) fn complex_value(
model: &Model,
release: Release,
id: EntityId,
entity: &Entity,
) -> Result<ResolvedValue, ExactPropertyError> {
let mut walk = Walk {
model,
release,
path: Vec::new(),
followed: 0,
};
Ok(ResolvedValue {
value: ExactValue::Complex(walk.complex(id, entity)?),
value_type: None,
unit_id: None,
})
}
#[derive(Clone, Copy)]
enum Family {
Properties,
Quantities,
}
impl Family {
fn of(release: Release, entity: &Entity) -> Self {
if release
.schema
.is_a(entity.type_name.as_ref(), "IFCCOMPLEXPROPERTY")
{
Self::Properties
} else {
Self::Quantities
}
}
fn entity(self) -> &'static str {
match self {
Self::Properties => "IFCCOMPLEXPROPERTY",
Self::Quantities => "IFCPHYSICALCOMPLEXQUANTITY",
}
}
fn members(self) -> &'static str {
match self {
Self::Properties => "HasProperties",
Self::Quantities => "HasQuantities",
}
}
fn member_type(self) -> &'static str {
match self {
Self::Properties => "IFCPROPERTY",
Self::Quantities => "IFCPHYSICALQUANTITY",
}
}
fn unique_names_rule(self, version: SchemaVersion) -> Option<&'static str> {
match (self, version) {
(Self::Properties, _) => Some("WR22"),
(Self::Quantities, SchemaVersion::Ifc2x3) => None,
(Self::Quantities, _) => Some("UniqueQuantityNames"),
}
}
}
struct Walk<'m> {
model: &'m Model,
release: Release,
path: Vec<EntityId>,
followed: usize,
}
impl<'m> Walk<'m> {
fn complex(
&mut self,
id: EntityId,
entity: &Entity,
) -> Result<ExactComplexValue, ExactPropertyError> {
if self.path.len() >= MAX_COMPLEX_DEPTH {
return Err(ExactPropertyError::ComplexTooDeep {
complex: id,
limit: MAX_COMPLEX_DEPTH,
});
}
let family = Family::of(self.release, entity);
let attribute = |name| {
entity
.attributes
.get(slot(self.release, family.entity(), name))
};
let member_ids = nonempty_refs_at(id, attribute(family.members()), family.members())?;
let (usage, discrimination, quality) = match family {
Family::Properties => (
Some(text_at(id, attribute("UsageName"), "UsageName")?.into()),
None,
None,
),
Family::Quantities => (
optional_text(id, attribute("Usage"), "Usage")?,
Some(text_at(id, attribute("Discrimination"), "Discrimination")?.into()),
optional_text(id, attribute("Quality"), "Quality")?,
),
};
self.path.push(id);
let mut names = BTreeSet::new();
let mut members = Vec::with_capacity(member_ids.len());
for member_id in member_ids {
self.followed += 1;
if self.followed > MAX_COMPLEX_MEMBERS {
return Err(ExactPropertyError::ComplexBudgetExceeded {
complex: id,
limit: MAX_COMPLEX_MEMBERS,
});
}
if self.path.contains(&member_id) {
return Err(ExactPropertyError::ComplexCycle {
complex: id,
member: member_id,
});
}
let (member, name) = self.member(id, family, member_id)?;
if !names.insert(name) {
if let Some(rule) = family.unique_names_rule(self.release.version) {
return Err(ExactPropertyError::InconsistentValues { entity: id, rule });
}
}
let resolved = if is_complex(self.release, member) {
ResolvedValue {
value: ExactValue::Complex(self.complex(member_id, member)?),
value_type: None,
unit_id: None,
}
} else {
match family {
Family::Properties => {
simple_property_value(self.model, self.release, member_id, member)?
}
Family::Quantities => {
simple_quantity_value(self.model, self.release, member_id, member)?
}
}
};
members.push(ExactComplexMember {
name: name.into(),
id: member_id,
value_type: resolved.value_type,
unit_id: resolved.unit_id,
value: resolved.value,
});
}
self.path.pop();
Ok(ExactComplexValue {
usage,
discrimination,
quality,
members,
})
}
fn member(
&self,
id: EntityId,
family: Family,
member_id: EntityId,
) -> Result<(&'m Entity, &'m str), ExactPropertyError> {
let schema = self.release.schema;
let model: &'m Model = self.model;
let member = model
.get(member_id)
.ok_or(ExactPropertyError::MissingReference {
from: id,
to: member_id,
})?;
if schema.entity(member.type_name.as_ref()).is_none() {
return Err(self
.release
.not_in_schema(member_id, member.type_name.clone()));
}
if !schema.is_a(member.type_name.as_ref(), family.member_type()) {
return Err(ExactPropertyError::UnsupportedProperty {
entity: member_id,
type_name: member.type_name.clone(),
});
}
self.release.require_exact_slots(member_id, member)?;
let name = text_at(member_id, member.attributes.first(), "Name")?;
Ok((member, name))
}
}
fn optional_text(
id: EntityId,
value: Option<&Value>,
attribute: &'static str,
) -> Result<Option<Arc<str>>, ExactPropertyError> {
match value {
Some(Value::Null) => Ok(None),
value => text_at(id, value, attribute).map(|text| Some(text.into())),
}
}