use std::fmt;
use ifc_model::{Conflict, EntityId, Model, Transaction, Value};
use ifc_properties::{ExactPropertyError, PropertyError};
use super::emit::emit;
use super::plan::Planner;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum PropertyEdit {
Set {
object: EntityId,
set: String,
name: String,
value: Value,
set_type: Option<SetType>,
},
Remove {
object: EntityId,
set: String,
name: String,
},
}
impl PropertyEdit {
pub fn set(
object: EntityId,
set: impl Into<String>,
name: impl Into<String>,
value: Value,
) -> Self {
Self::Set {
object,
set: set.into(),
name: name.into(),
value,
set_type: None,
}
}
pub fn remove(object: EntityId, set: impl Into<String>, name: impl Into<String>) -> Self {
Self::Remove {
object,
set: set.into(),
name: name.into(),
}
}
#[must_use]
pub fn with_set_type(mut self, set_type: SetType) -> Self {
if let Self::Set { set_type: slot, .. } = &mut self {
*slot = Some(set_type);
}
self
}
pub fn object(&self) -> EntityId {
match self {
Self::Set { object, .. } | Self::Remove { object, .. } => *object,
}
}
pub fn set_name(&self) -> &str {
match self {
Self::Set { set, .. } | Self::Remove { set, .. } => set,
}
}
pub fn name(&self) -> &str {
match self {
Self::Set { name, .. } | Self::Remove { name, .. } => name,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SetType {
PropertySet,
ElementQuantity,
}
impl SetType {
pub const fn entity(self) -> &'static str {
match self {
Self::PropertySet => "IFCPROPERTYSET",
Self::ElementQuantity => "IFCELEMENTQUANTITY",
}
}
pub fn from_entity(name: &str) -> Option<Self> {
if name.eq_ignore_ascii_case("IFCPROPERTYSET") {
Some(Self::PropertySet)
} else if name.eq_ignore_ascii_case("IFCELEMENTQUANTITY") {
Some(Self::ElementQuantity)
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct PropertyEditOutcome {
pub properties: Vec<Option<EntityId>>,
pub created: Vec<EntityId>,
pub removed: Vec<EntityId>,
pub revision: u64,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct StagedPropertyEdits {
pub transaction: Transaction,
pub properties: Vec<Option<EntityId>>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct PropertyEditError {
pub edit: Option<usize>,
pub failure: PropertyEditFailure,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum PropertyEditFailure {
MissingEntity(EntityId),
Resolve(ExactPropertyError),
Authoring(PropertyError),
InvalidValue(String),
Template(String),
MissingProperty {
object: EntityId,
set: String,
name: String,
inherited_from: Option<EntityId>,
},
WrongSetType(String),
Unsupported(String),
InvalidModel(String),
CatalogUnavailable(String),
Conflict(Vec<Conflict>),
}
impl PropertyEditError {
pub(super) fn at(edit: usize, failure: PropertyEditFailure) -> Self {
Self {
edit: Some(edit),
failure,
}
}
}
impl fmt::Display for PropertyEditError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(edit) = self.edit {
write!(f, "edit {edit}: ")?;
}
write!(f, "{}", self.failure)
}
}
impl fmt::Display for PropertyEditFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingEntity(id) => write!(f, "{id} is not in the model"),
Self::Resolve(error) => write!(f, "{error}"),
Self::Authoring(error) => write!(f, "{error}"),
Self::InvalidValue(detail)
| Self::Template(detail)
| Self::WrongSetType(detail)
| Self::Unsupported(detail)
| Self::InvalidModel(detail) => f.write_str(detail),
Self::MissingProperty {
object,
set,
name,
inherited_from,
} => {
write!(f, "{object} states no property {set}.{name}")?;
if let Some(type_object) = inherited_from {
write!(
f,
"; it is inherited from type {type_object}, which is where it is removed"
)?;
}
Ok(())
}
Self::CatalogUnavailable(set) => write!(
f,
"{set} is a catalog set and this build has no catalog to check it against"
),
Self::Conflict(conflicts) => write!(f, "the planned edit conflicts: {conflicts:?}"),
}
}
}
impl std::error::Error for PropertyEditError {}
pub fn stage_property_edits(
model: &Model,
edits: &[PropertyEdit],
) -> Result<StagedPropertyEdits, PropertyEditError> {
let mut planner = Planner::new(model)?;
for (index, edit) in edits.iter().enumerate() {
planner
.apply(index, edit)
.map_err(|failure| PropertyEditError::at(index, failure))?;
}
emit(planner)
}
pub fn apply_property_edits(
model: &mut Model,
edits: &[PropertyEdit],
) -> Result<PropertyEditOutcome, PropertyEditError> {
let staged = stage_property_edits(model, edits)?;
let applied = staged
.transaction
.commit(model)
.map_err(|conflicts| PropertyEditError {
edit: None,
failure: PropertyEditFailure::Conflict(conflicts),
})?;
Ok(PropertyEditOutcome {
properties: staged.properties,
created: applied.created,
removed: applied.removed.into_iter().map(|(id, _)| id).collect(),
revision: applied.revision,
})
}