use ifc_model::{EntityId, Model, Transaction, Value};
use ifc_schema::{SchemaVersion, TypeKind};
use crate::quantity::release::{bind, layout, Layout};
use crate::{PropertyError, PropertyResult};
use super::authoring::optional_text;
use super::root_authoring::require_owner_history;
pub(super) struct Rooted<'a> {
pub(super) entity: &'static str,
pub(super) global_id: &'a str,
pub(super) name: Option<&'a str>,
pub(super) description: Option<&'a str>,
pub(super) values: Vec<(&'static str, Value)>,
}
impl Rooted<'_> {
fn named(self, owner_history: Option<EntityId>) -> Vec<(&'static str, Value)> {
let mut named = vec![
("GlobalId", Value::Text(self.global_id.into())),
(
"OwnerHistory",
owner_history.map_or(Value::Null, Value::Ref),
),
("Name", optional_text(self.name)),
("Description", optional_text(self.description)),
];
named.extend(self.values);
named
}
}
pub(super) fn stage_ifc4(tx: &mut Transaction, rooted: Rooted<'_>) -> PropertyResult<EntityId> {
let entity = rooted.entity;
let record = layout(SchemaVersion::Ifc4)?.named_record(entity, rooted.named(None))?;
Ok(tx.create(record))
}
pub(super) fn stage_owned(
tx: &mut Transaction,
model: &Model,
rooted: Rooted<'_>,
owner_history: EntityId,
) -> PropertyResult<EntityId> {
let layout = bind(model)?;
let entity = rooted.entity;
if layout.schema().entity(entity).is_none_or(|e| e.abstract_) {
return Err(PropertyError::EntityNotInSchema {
entity,
schema: layout.version(),
});
}
for (attribute, value) in &rooted.values {
check_value(layout, entity, attribute, value)?;
}
require_owner_history(tx, model, layout, entity, owner_history)?;
let record = layout.named_record(entity, rooted.named(Some(owner_history)))?;
Ok(tx.create(record))
}
fn check_value(
layout: Layout,
entity: &'static str,
attribute: &'static str,
value: &Value,
) -> PropertyResult<()> {
let schema = layout.schema();
let Some(declared) = schema
.attributes(entity)
.into_iter()
.find(|found| found.name.eq_ignore_ascii_case(attribute))
else {
return Ok(());
};
let refused = || PropertyError::AuthoringInvalid {
entity,
attribute,
value: format!(
"{value:?} is not a {} in {:?}",
declared.type_name,
layout.version()
),
};
match value {
Value::Real(number)
if declared
.type_name
.eq_ignore_ascii_case("IfcPositiveLengthMeasure")
&& *number <= 0.0 =>
{
Err(refused())
}
Value::Enum(token) => match schema.type_def(&declared.type_name).map(|t| &t.kind) {
Some(TypeKind::Enumeration(members))
if !members.iter().any(|member| member == token.as_ref()) =>
{
Err(refused())
}
_ => Ok(()),
},
_ => Ok(()),
}
}