use std::collections::BTreeMap;
use std::collections::BTreeSet;
use crate::bmm3::core::entity::bmm_class::BmmClass;
use crate::bmm3::core::entity::bmm_effective_type::BmmEffectiveType;
use crate::bmm3::core::entity::bmm_generic_class::BmmGenericClass;
use crate::bmm3::core::entity::bmm_generic_type::BmmGenericType;
use crate::bmm3::core::entity::bmm_model_type::BmmModelType;
use crate::bmm3::core::entity::bmm_parameter_type::BmmParameterType;
use crate::bmm3::core::entity::bmm_simple_class::BmmSimpleClass;
use crate::bmm3::core::entity::bmm_simple_type::BmmSimpleType;
use crate::bmm3::core::entity::bmm_type_impl::ANY_TYPE_NAME;
use crate::bmm3::core::entity::bmm_unitary_type::BmmUnitaryType;
use crate::bmm3::core::entity::range_constrained::bmm_enumeration::BmmEnumeration;
use crate::bmm3::core::feature::bmm_feature::BmmFeature;
use crate::bmm3::core::feature::bmm_function::BmmFunction;
use crate::bmm3::core::feature::bmm_procedure::BmmProcedure;
use crate::bmm3::core::feature::bmm_property::BmmProperty;
use crate::bmm3::core::feature::bmm_static::BmmStatic;
use crate::bmm3::core::literal_value::bmm_primitive_value::BmmPrimitiveValue;
use crate::bmm3::core::model::bmm_package::BmmPackage;
use crate::bmm3::statement::bmm_assertion::BmmAssertion;
struct ClassCommon<'a> {
name: &'a str,
ancestors: Option<&'a BTreeMap<String, BmmModelType>>,
package: &'a BmmPackage,
features: &'a [BmmFeature],
properties: Option<&'a BTreeMap<String, BmmProperty>>,
static_properties: Option<&'a BTreeMap<String, BmmStatic>>,
functions: Option<&'a BTreeMap<String, BmmFunction>>,
procedures: Option<&'a BTreeMap<String, BmmProcedure>>,
creators: Option<&'a BTreeMap<String, BmmProcedure>>,
converters: Option<&'a BTreeMap<String, BmmProcedure>>,
invariants: &'a [BmmAssertion],
is_abstract: Option<bool>,
is_primitive: Option<bool>,
source_schema_id: &'a str,
}
macro_rules! class_common {
($c:expr) => {
ClassCommon {
name: $c.name.as_str(),
ancestors: $c.ancestors.as_ref(),
package: &$c.package,
features: $c.features.as_deref().unwrap_or_default(),
properties: $c.properties.as_ref(),
static_properties: $c.static_properties.as_ref(),
functions: $c.functions.as_ref(),
procedures: $c.procedures.as_ref(),
creators: $c.creators.as_ref(),
converters: $c.converters.as_ref(),
invariants: $c.invariants.as_deref().unwrap_or_default(),
is_abstract: $c.is_abstract,
is_primitive: $c.is_primitive,
source_schema_id: $c.source_schema_id.as_str(),
}
};
}
fn enumeration_common(enumeration: &BmmEnumeration) -> ClassCommon<'_> {
match enumeration {
BmmEnumeration::BmmEnumerationInteger(c) => class_common!(c),
BmmEnumeration::BmmEnumerationString(c) => class_common!(c),
BmmEnumeration::BmmEnumeration(c) => class_common!(c),
}
}
fn simple_class_common(class: &BmmSimpleClass) -> ClassCommon<'_> {
match class {
BmmSimpleClass::BmmEnumeration(e) => enumeration_common(e),
BmmSimpleClass::BmmSimpleClass(c) => class_common!(c),
}
}
fn model_type_base_class_common(model_type: &BmmModelType) -> ClassCommon<'_> {
match model_type {
BmmModelType::BmmGenericType(generic) => class_common!(generic.base_class),
BmmModelType::BmmSimpleType(simple) => simple_class_common(&simple.base_class),
}
}
fn walk_has_ancestor(class: &ClassCommon<'_>, name: &str, seen: &mut BTreeSet<String>) -> bool {
let Some(ancestors) = class.ancestors else {
return false;
};
for ancestor in ancestors.values() {
let base = model_type_base_class_common(ancestor);
if base.name.eq_ignore_ascii_case(name) {
return true;
}
if !seen.insert(base.name.to_uppercase()) {
continue;
}
if walk_has_ancestor(&base, name, seen) {
return true;
}
}
false
}
fn walk_ancestor_names(
class: &ClassCommon<'_>,
out: &mut Vec<String>,
seen: &mut BTreeSet<String>,
) {
let Some(ancestors) = class.ancestors else {
return;
};
for ancestor in ancestors.values() {
let base = model_type_base_class_common(ancestor);
if !seen.insert(base.name.to_uppercase()) {
continue;
}
out.push(base.name.to_owned());
walk_ancestor_names(&base, out, seen);
}
}
fn walk_flat_features(
class: &ClassCommon<'_>,
by_name: &mut BTreeMap<String, BmmFeature>,
seen: &mut BTreeSet<String>,
) {
if let Some(ancestors) = class.ancestors {
for ancestor in ancestors.values() {
let base = model_type_base_class_common(ancestor);
if !seen.insert(base.name.to_uppercase()) {
continue;
}
walk_flat_features(&base, by_name, seen);
}
}
for feature in class.features {
by_name.insert(feature.name().to_owned(), feature.clone());
}
}
impl BmmEnumeration {
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::BmmEnumerationInteger(e) => e.name.as_str(),
Self::BmmEnumerationString(e) => e.name.as_str(),
Self::BmmEnumeration(e) => e.name.as_str(),
}
}
#[must_use]
pub fn item_names(&self) -> &[String] {
match self {
Self::BmmEnumerationInteger(e) => e.item_names.as_deref().unwrap_or_default(),
Self::BmmEnumerationString(e) => e.item_names.as_deref().unwrap_or_default(),
Self::BmmEnumeration(e) => e.item_names.as_deref().unwrap_or_default(),
}
}
#[must_use]
pub fn item_value_literals(&self) -> Vec<&str> {
match self {
Self::BmmEnumerationInteger(e) => e
.item_values
.iter()
.flatten()
.map(|v| v.value_literal.as_str())
.collect(),
Self::BmmEnumerationString(e) => e
.item_values
.iter()
.flatten()
.map(|v| v.value_literal.as_str())
.collect(),
Self::BmmEnumeration(e) => e
.item_values
.iter()
.flatten()
.map(BmmPrimitiveValue::value_literal)
.collect(),
}
}
#[must_use]
pub fn name_map(&self) -> BTreeMap<String, String> {
let values = self.item_value_literals();
self.item_names()
.iter()
.enumerate()
.map(|(index, name)| {
let value = values
.get(index)
.map_or_else(|| index.to_string(), |literal| (*literal).to_owned());
(name.clone(), value)
})
.collect()
}
}
impl BmmSimpleClass {
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::BmmEnumeration(e) => e.name(),
Self::BmmSimpleClass(c) => c.name.as_str(),
}
}
#[must_use]
pub fn r#type(&self) -> BmmSimpleType {
BmmSimpleType {
value_constraint: None,
base_class: self.clone(),
}
}
}
impl BmmGenericClass {
#[must_use]
#[expect(
clippy::expect_used,
reason = "unreachable by the model: this method exists only on BMM_GENERIC_CLASS, whose `generic_parameters` is a mandatory Hash the spec describes as the class's formal generic parameters (`docs/specs/openehr/LANG/docs/UML/classes/org.openehr.lang.bmm3.bmm_generic_class.adoc` §Attributes) — a generic class with none is not a generic class, so the empty map cannot arise from a valid schema"
)]
pub fn r#type(&self) -> BmmGenericType {
BmmGenericType {
value_constraint: None,
base_class: self.clone(),
generic_parameters: openehr_base::containers::NonEmptyVec::new(
self.generic_parameters
.values()
.map(|p| BmmUnitaryType::BmmParameterType(Box::new(p.clone())))
.collect(),
)
.expect("a BMM_GENERIC_CLASS should declare at least one formal generic parameter"),
}
}
#[must_use]
pub fn generic_parameter(&self, name: &str) -> Option<&BmmParameterType> {
self.generic_parameters.get(name).or_else(|| {
self.generic_parameters
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, parameter)| parameter)
})
}
#[must_use]
pub fn generic_parameter_conformance_type(&self, name: &str) -> Option<String> {
self.generic_parameter(name).map(|parameter| {
parameter
.flattened_conforms_to_type()
.map_or_else(|| ANY_TYPE_NAME.to_owned(), BmmEffectiveType::type_name)
})
}
}
impl BmmClass {
fn common(&self) -> ClassCommon<'_> {
match self {
Self::BmmGenericClass(c) => class_common!(c),
Self::BmmSimpleClass(c) => simple_class_common(c),
}
}
#[must_use]
pub fn name(&self) -> &str {
self.common().name
}
#[must_use]
pub fn ancestors(&self) -> Option<&BTreeMap<String, BmmModelType>> {
self.common().ancestors
}
#[must_use]
pub fn package(&self) -> &BmmPackage {
self.common().package
}
#[must_use]
pub fn features(&self) -> &[BmmFeature] {
self.common().features
}
#[must_use]
pub fn properties(&self) -> Option<&BTreeMap<String, BmmProperty>> {
self.common().properties
}
#[must_use]
pub fn static_properties(&self) -> Option<&BTreeMap<String, BmmStatic>> {
self.common().static_properties
}
#[must_use]
pub fn functions(&self) -> Option<&BTreeMap<String, BmmFunction>> {
self.common().functions
}
#[must_use]
pub fn procedures(&self) -> Option<&BTreeMap<String, BmmProcedure>> {
self.common().procedures
}
#[must_use]
pub fn creators(&self) -> Option<&BTreeMap<String, BmmProcedure>> {
self.common().creators
}
#[must_use]
pub fn converters(&self) -> Option<&BTreeMap<String, BmmProcedure>> {
self.common().converters
}
#[must_use]
pub fn invariants(&self) -> &[BmmAssertion] {
self.common().invariants
}
#[must_use]
pub fn source_schema_id(&self) -> &str {
self.common().source_schema_id
}
#[must_use]
pub fn is_abstract(&self) -> bool {
self.common().is_abstract.unwrap_or(false)
}
#[must_use]
pub fn is_primitive(&self) -> bool {
self.common().is_primitive.unwrap_or(false)
}
#[must_use]
pub fn r#type(&self) -> BmmModelType {
match self {
Self::BmmGenericClass(c) => BmmModelType::BmmGenericType(c.r#type()),
Self::BmmSimpleClass(c) => BmmModelType::BmmSimpleType(c.r#type()),
}
}
#[must_use]
pub fn has_ancestor_class(&self, name: &str) -> bool {
walk_has_ancestor(&self.common(), name, &mut BTreeSet::new())
}
#[must_use]
pub fn all_ancestors(&self) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
walk_ancestor_names(&self.common(), &mut out, &mut BTreeSet::new());
out
}
#[must_use]
pub fn flat_features(&self) -> Vec<BmmFeature> {
let mut by_name: BTreeMap<String, BmmFeature> = BTreeMap::new();
walk_flat_features(&self.common(), &mut by_name, &mut BTreeSet::new());
by_name.into_values().collect()
}
}