use std::collections::BTreeSet;
use ifc_model::{Edit, EntityId, Model, Transaction, Value};
use super::check_relate;
use super::owned_relationships::refs;
use super::release::{bind, projected_type, stage, Release};
use super::{invalid, SpatialAuthoringError, SpatialAuthoringResult};
use crate::relation::slots::SERVICES_BUILDINGS;
const ENTITY: &str = SERVICES_BUILDINGS.type_name;
pub fn serve_buildings(
tx: &mut Transaction,
model: &Model,
global_id: &str,
system: EntityId,
buildings: &[EntityId],
) -> SpatialAuthoringResult<EntityId> {
serve(tx, model, global_id, system, buildings, None)
}
pub fn serve_buildings_with_owner_history(
tx: &mut Transaction,
model: &Model,
global_id: &str,
system: EntityId,
buildings: &[EntityId],
owner_history: EntityId,
) -> SpatialAuthoringResult<EntityId> {
serve(tx, model, global_id, system, buildings, Some(owner_history))
}
fn serve(
tx: &mut Transaction,
model: &Model,
global_id: &str,
system: EntityId,
buildings: &[EntityId],
owner_history: Option<EntityId>,
) -> SpatialAuthoringResult<EntityId> {
let release = bind(model)?;
check_relate(SERVICES_BUILDINGS, global_id, system, buildings)?;
let mut seen = BTreeSet::new();
if let Some(repeated) = buildings.iter().find(|id| !seen.insert(**id)) {
return Err(invalid(
ENTITY,
"RelatedBuildings",
format!("#{} repeats in a SET", repeated.0),
));
}
require(release, tx, model, "RelatingSystem", system)?;
for &building in buildings {
require(release, tx, model, "RelatedBuildings", building)?;
}
if let Some(existing) = services_of(tx, model, system) {
return Err(invalid(
ENTITY,
"RelatingSystem",
format!(
"#{} already services buildings through #{}",
system.0, existing.0
),
));
}
let values = vec![
("GlobalId", Value::Text(global_id.into())),
("RelatingSystem", Value::Ref(system)),
("RelatedBuildings", refs(buildings)),
];
stage(tx, model, ENTITY, values, owner_history)
}
fn require(
release: Release,
tx: &Transaction,
model: &Model,
attribute: &'static str,
target: EntityId,
) -> SpatialAuthoringResult<()> {
let actual = projected_type(tx, model, target)
.ok_or(SpatialAuthoringError::MissingReference {
entity: ENTITY,
attribute,
target,
})?
.to_ascii_uppercase();
let schema = release.schema();
let declared = schema
.attributes(ENTITY)
.iter()
.find(|a| a.name.eq_ignore_ascii_case(attribute))
.map(|a| a.type_name.as_str())
.ok_or(SpatialAuthoringError::EntityNotInSchema {
entity: ENTITY,
schema: release.version(),
})?;
if schema.accepts_type(declared, &actual) {
Ok(())
} else {
Err(SpatialAuthoringError::WrongReferenceType {
entity: ENTITY,
attribute,
target,
actual,
expected: declared,
})
}
}
fn services_of(tx: &Transaction, model: &Model, system: EntityId) -> Option<EntityId> {
let staged = tx.edits().iter().filter_map(|edit| match edit {
Edit::Create { id, .. } => Some(*id),
_ => None,
});
let mut candidates: Vec<EntityId> = model.ids_of_type(ENTITY).to_vec();
candidates.extend(staged);
candidates.sort_unstable();
candidates.dedup();
candidates.into_iter().find(|&id| {
projected_type(tx, model, id).is_some_and(|t| t.eq_ignore_ascii_case(ENTITY))
&& projected_system(tx, model, id) == Some(system)
})
}
fn projected_system(tx: &Transaction, model: &Model, id: EntityId) -> Option<EntityId> {
let slot = SERVICES_BUILDINGS.relating;
for edit in tx.edits().iter().rev() {
match edit {
Edit::Remove { id: removed } if *removed == id => return None,
Edit::SetAttribute {
id: edited,
slot: at,
value,
} if *edited == id && *at == slot => {
return match value {
Value::Ref(system) => Some(*system),
_ => None,
}
}
Edit::Create {
id: created,
entity,
} if *created == id => {
return match entity.attributes.get(slot) {
Some(Value::Ref(system)) => Some(*system),
_ => None,
}
}
_ => {}
}
}
match model.get(id)?.attributes.get(slot)? {
Value::Ref(system) => Some(*system),
_ => None,
}
}