use ifc_model::guid::Guid;
use ifc_model::{Entity, EntityId, Model, Transaction, Value};
use ifc_schema::Schema;
use super::release::bind;
use super::{invalid, SystemAuthoringResult};
const BUILDING: &[&str] = &[
"FENESTRATION",
"FOUNDATION",
"LOADBEARING",
"OUTERSHELL",
"SHADING",
"TRANSPORT",
"USERDEFINED",
"NOTDEFINED",
];
const BUILT: &[&str] = &[
"EROSIONPREVENTION",
"FENESTRATION",
"FOUNDATION",
"LOADBEARING",
"MOORING",
"OUTERSHELL",
"PRESTRESSING",
"RAILWAYLINE",
"RAILWAYTRACK",
"REINFORCING",
"SHADING",
"TRACKCIRCUIT",
"TRANSPORT",
"USERDEFINED",
"NOTDEFINED",
];
const DISTRIBUTION_X3: &[&str] = &[
"AIRCONDITIONING",
"AUDIOVISUAL",
"CATENARY_SYSTEM",
"CHEMICAL",
"CHILLEDWATER",
"COMMUNICATION",
"COMPRESSEDAIR",
"CONDENSERWATER",
"CONTROL",
"CONVEYING",
"DATA",
"DISPOSAL",
"DOMESTICCOLDWATER",
"DOMESTICHOTWATER",
"DRAINAGE",
"EARTHING",
"ELECTRICAL",
"ELECTROACOUSTIC",
"EXHAUST",
"FIREPROTECTION",
"FIXEDTRANSMISSIONNETWORK",
"FUEL",
"GAS",
"HAZARDOUS",
"HEATING",
"LIGHTING",
"LIGHTNINGPROTECTION",
"MOBILENETWORK",
"MONITORINGSYSTEM",
"MUNICIPALSOLIDWASTE",
"OIL",
"OPERATIONAL",
"OPERATIONALTELEPHONYSYSTEM",
"OVERHEAD_CONTACTLINE_SYSTEM",
"POWERGENERATION",
"RAINWATER",
"REFRIGERATION",
"RETURN_CIRCUIT",
"SECURITY",
"SEWAGE",
"SIGNAL",
"STORMWATER",
"TELEPHONE",
"TV",
"VACUUM",
"VENT",
"VENTILATION",
"WASTEWATER",
"WATERSUPPLY",
"USERDEFINED",
"NOTDEFINED",
];
const DISTRIBUTION_IFC4: &[&str] = &[
"AIRCONDITIONING",
"AUDIOVISUAL",
"CHEMICAL",
"CHILLEDWATER",
"COMMUNICATION",
"COMPRESSEDAIR",
"CONDENSERWATER",
"CONTROL",
"CONVEYING",
"DATA",
"DISPOSAL",
"DOMESTICCOLDWATER",
"DOMESTICHOTWATER",
"DRAINAGE",
"EARTHING",
"ELECTRICAL",
"ELECTROACOUSTIC",
"EXHAUST",
"FIREPROTECTION",
"FUEL",
"GAS",
"HAZARDOUS",
"HEATING",
"LIGHTING",
"LIGHTNINGPROTECTION",
"MUNICIPALSOLIDWASTE",
"OIL",
"OPERATIONAL",
"POWERGENERATION",
"RAINWATER",
"REFRIGERATION",
"SECURITY",
"SEWAGE",
"SIGNAL",
"STORMWATER",
"TELEPHONE",
"TV",
"VACUUM",
"VENT",
"VENTILATION",
"WASTEWATER",
"WATERSUPPLY",
"USERDEFINED",
"NOTDEFINED",
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SystemKind {
Building,
Built,
DistributionCircuit,
DistributionSystem,
}
impl SystemKind {
const fn type_name(self) -> &'static str {
match self {
Self::Building => "IFCBUILDINGSYSTEM",
Self::Built => "IFCBUILTSYSTEM",
Self::DistributionCircuit => "IFCDISTRIBUTIONCIRCUIT",
Self::DistributionSystem => "IFCDISTRIBUTIONSYSTEM",
}
}
fn members(self, schema: &Schema) -> &'static [&'static str] {
match self {
Self::Building => BUILDING,
Self::Built => BUILT,
Self::DistributionCircuit | Self::DistributionSystem => {
if schema.name().eq_ignore_ascii_case("IFC4") {
DISTRIBUTION_IFC4
} else {
DISTRIBUTION_X3
}
}
}
}
const fn predefined_slot(self) -> usize {
match self {
Self::Building | Self::Built => 5,
Self::DistributionCircuit | Self::DistributionSystem => 6,
}
}
const fn long_name_slot(self) -> usize {
match self {
Self::Building | Self::Built => 6,
Self::DistributionCircuit | Self::DistributionSystem => 5,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ClassifiedSystemDraft<'a> {
pub description: Option<&'a str>,
pub object_type: Option<&'a str>,
pub predefined_type: Option<&'a str>,
pub long_name: Option<&'a str>,
}
pub fn create_classified_system(
tx: &mut Transaction,
schema: &Schema,
kind: SystemKind,
global_id: &str,
name: Option<&str>,
draft: ClassifiedSystemDraft<'_>,
) -> SystemAuthoringResult<EntityId> {
let entity = kind.type_name();
if Guid::parse(global_id).is_none() {
return Err(invalid(entity, "GlobalId", global_id));
}
let declared = schema.attributes(entity);
if declared.is_empty() {
return Err(invalid(entity, "Entity", schema.name().to_owned()));
}
check_predefined(schema, kind, draft)?;
let mut attributes = vec![Value::Null; declared.len()];
attributes[0] = Value::Text(global_id.into());
attributes[2] = text(name);
attributes[3] = text(draft.description);
attributes[4] = text(draft.object_type);
attributes[kind.predefined_slot()] = draft
.predefined_type
.map_or(Value::Null, |t| Value::Enum(t.into()));
attributes[kind.long_name_slot()] = text(draft.long_name);
Ok(tx.create(Entity::new(entity, attributes)))
}
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())
}
fn check_predefined(
schema: &Schema,
kind: SystemKind,
draft: ClassifiedSystemDraft<'_>,
) -> SystemAuthoringResult<()> {
let entity = kind.type_name();
if let Some(token) = draft.predefined_type {
if !kind.members(schema).contains(&token) {
return Err(invalid(entity, "PredefinedType", token));
}
if token == "USERDEFINED" && blank(draft.object_type) {
return Err(invalid(entity, "ObjectType", "required by USERDEFINED"));
}
}
Ok(())
}
pub fn create_classified_system_with_owner_history(
tx: &mut Transaction,
model: &Model,
kind: SystemKind,
global_id: &str,
name: Option<&str>,
draft: ClassifiedSystemDraft<'_>,
owner_history: EntityId,
) -> SystemAuthoringResult<EntityId> {
let release = bind(model)?;
let entity = kind.type_name();
if Guid::parse(global_id).is_none() {
return Err(invalid(entity, "GlobalId", global_id));
}
release.require_entity(entity)?;
check_predefined(release.schema(), kind, draft)?;
let values = vec![
("GlobalId", Value::Text(global_id.into())),
("Name", text(name)),
("Description", text(draft.description)),
("ObjectType", text(draft.object_type)),
(
"PredefinedType",
draft
.predefined_type
.map_or(Value::Null, |t| Value::Enum(t.into())),
),
("LongName", text(draft.long_name)),
];
release.stage(tx, model, entity, values, owner_history)
}