use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId, Model, Transaction, Value};
use ifc_schema::Schema;
use crate::error::{ControlError, ControlResult};
use crate::release::{bind, Release};
const PERMIT: &[&str] = &["ACCESS", "BUILDING", "WORK", "USERDEFINED", "NOTDEFINED"];
const PROJECT_ORDER: &[&str] = &[
"CHANGEORDER",
"MAINTENANCEWORKORDER",
"MOVEORDER",
"PURCHASEORDER",
"WORKORDER",
"USERDEFINED",
"NOTDEFINED",
];
const ACTION_REQUEST: &[&str] = &[
"EMAIL",
"FAX",
"PHONE",
"POST",
"VERBAL",
"USERDEFINED",
"NOTDEFINED",
];
const PERFORMANCE_HISTORY: &[&str] = &["USERDEFINED", "NOTDEFINED"];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ControlKind {
Permit,
ProjectOrder,
ActionRequest,
PerformanceHistory,
}
impl ControlKind {
pub const ALL: [Self; 4] = [
Self::Permit,
Self::ProjectOrder,
Self::ActionRequest,
Self::PerformanceHistory,
];
#[must_use]
pub const fn type_name(self) -> &'static str {
match self {
Self::Permit => "IFCPERMIT",
Self::ProjectOrder => "IFCPROJECTORDER",
Self::ActionRequest => "IFCACTIONREQUEST",
Self::PerformanceHistory => "IFCPERFORMANCEHISTORY",
}
}
#[must_use]
pub const fn members(self) -> &'static [&'static str] {
match self {
Self::Permit => PERMIT,
Self::ProjectOrder => PROJECT_ORDER,
Self::ActionRequest => ACTION_REQUEST,
Self::PerformanceHistory => PERFORMANCE_HISTORY,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ControlDraft<'a> {
pub name: Option<&'a str>,
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub identification: Option<&'a str>,
pub status: Option<&'a str>,
pub long_description: Option<&'a str>,
pub life_cycle_phase: Option<&'a str>,
}
fn invalid(
entity: &'static str,
attribute: &'static str,
value: impl Into<String>,
) -> ControlError {
ControlError::AuthoringInvalid {
entity,
attribute,
value: value.into(),
}
}
fn text(value: Option<&str>) -> Value {
value.map_or(Value::Null, |v| Value::Text(v.into()))
}
fn blank(value: Option<&str>) -> bool {
value.is_none_or(|v| v.trim().is_empty())
}
pub fn create_control(
tx: &mut Transaction,
schema: &Schema,
kind: ControlKind,
global_id: &str,
predefined_type: Option<&str>,
draft: ControlDraft<'_>,
) -> ControlResult<EntityId> {
let release = Release::of_schema(schema);
let record = control_record(
release,
kind,
global_id,
predefined_type,
draft,
Value::Null,
)?;
Ok(tx.create(record))
}
pub fn create_control_with_owner_history(
tx: &mut Transaction,
model: &Model,
kind: ControlKind,
global_id: &str,
predefined_type: Option<&str>,
draft: ControlDraft<'_>,
owner_history: EntityId,
) -> ControlResult<EntityId> {
let release = bind(model)?;
let record = control_record(
release,
kind,
global_id,
predefined_type,
draft,
Value::Ref(owner_history),
)?;
release.require_owner_history(tx, model, kind.type_name(), owner_history)?;
Ok(tx.create(record))
}
fn control_record(
release: Release<'_>,
kind: ControlKind,
global_id: &str,
predefined_type: Option<&str>,
draft: ControlDraft<'_>,
owner_history: Value,
) -> ControlResult<Entity> {
let entity = kind.type_name();
if release.schema().attributes(entity).is_empty() {
return Err(ControlError::UnsupportedEntity {
schema: release.schema().name().to_owned(),
entity,
});
}
if Guid::parse(global_id).is_none() {
return Err(invalid(entity, "GlobalId", global_id));
}
if blank(draft.name) {
return Err(invalid(entity, "Name", "required"));
}
if let Some(token) = predefined_type {
if !kind.members().contains(&token) {
return Err(invalid(entity, "PredefinedType", token));
}
if token == "USERDEFINED" && blank(draft.object_type) {
return Err(invalid(entity, "ObjectType", "required by USERDEFINED"));
}
}
let history = kind == ControlKind::PerformanceHistory;
if history {
if blank(draft.life_cycle_phase) {
return Err(invalid(entity, "LifeCyclePhase", "required"));
}
if draft.status.is_some() {
return Err(invalid(entity, "Status", "not declared"));
}
if draft.long_description.is_some() {
return Err(invalid(entity, "LongDescription", "not declared"));
}
} else if draft.life_cycle_phase.is_some() {
return Err(invalid(entity, "LifeCyclePhase", "not declared"));
}
let mut values = vec![
("GlobalId", Value::Text(global_id.into())),
("OwnerHistory", owner_history),
("Name", text(draft.name)),
("Description", text(draft.description)),
("ObjectType", text(draft.object_type)),
("Identification", text(draft.identification)),
(
"PredefinedType",
predefined_type.map_or(Value::Null, |t| Value::Enum(t.into())),
),
];
if history {
values.push(("LifeCyclePhase", text(draft.life_cycle_phase)));
} else {
values.push(("Status", text(draft.status)));
values.push(("LongDescription", text(draft.long_description)));
}
release.record(entity, values)
}