use std::collections::HashMap;
use ifc_model::{Entity, EntityId, Model, Value};
use openbim_step::Span;
use crate::{parser, StepError};
pub struct Index<'a> {
source: &'a [u8],
header: ifc_model::header::Header,
ids: Vec<u64>,
spans: Vec<Span>,
type_ids: Vec<u32>,
type_names: Vec<String>,
unordered: Option<HashMap<u64, usize>>,
}
impl<'a> Index<'a> {
pub fn scan(source: &'a [u8]) -> Result<Self, StepError> {
let scanned = openbim_step::scan(source)?;
let mut header = ifc_model::header::Header::default();
parser::apply_header(&mut header, scanned.header().standard());
let mut ids = Vec::new();
let mut spans = Vec::new();
let mut type_ids = Vec::new();
let mut type_names: Vec<String> = Vec::new();
let mut seen: HashMap<String, u32> = HashMap::new();
for record in scanned.records() {
let record = record?;
let id = record.id.as_str().parse::<u64>().map_err(|_| {
unrepresentable(
record.span,
"instance id exceeds the IFC record model range",
)
})?;
let Some(name) = record.name else {
return Err(unrepresentable(
record.span,
"complex STEP instances are not representable in the IFC record model",
));
};
let upper = name.to_ascii_uppercase();
let next = u32::try_from(type_names.len()).expect("fewer than 2^32 distinct types");
let type_id = *seen.entry(upper.clone()).or_insert(next);
if type_id == next {
type_names.push(upper);
}
ids.push(id);
spans.push(record.span);
type_ids.push(type_id);
}
let unordered = (!ids.windows(2).all(|pair| pair[0] < pair[1])).then(|| {
ids.iter()
.enumerate()
.map(|(position, id)| (*id, position))
.collect()
});
Ok(Self {
source,
header,
ids,
spans,
type_ids,
type_names,
unordered,
})
}
#[must_use]
pub fn len(&self) -> usize {
self.ids.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
#[must_use]
pub fn header(&self) -> &ifc_model::header::Header {
&self.header
}
pub fn ids(&self) -> impl Iterator<Item = EntityId> + '_ {
self.ids.iter().copied().map(EntityId)
}
#[must_use]
pub fn type_of(&self, id: EntityId) -> Option<&str> {
let position = self.position(id)?;
Some(&self.type_names[self.type_ids[position] as usize])
}
#[must_use]
pub fn count_by_type(&self) -> std::collections::BTreeMap<&str, usize> {
let mut out = std::collections::BTreeMap::new();
for type_id in &self.type_ids {
*out.entry(self.type_names[*type_id as usize].as_str())
.or_insert(0) += 1;
}
out
}
#[must_use]
pub fn ids_of_type(&self, name: &str) -> Vec<EntityId> {
let upper = name.to_ascii_uppercase();
let Some(type_id) = self.type_names.iter().position(|n| *n == upper) else {
return Vec::new();
};
let type_id = u32::try_from(type_id).expect("fewer than 2^32 distinct types");
self.type_ids
.iter()
.enumerate()
.filter(|(_, t)| **t == type_id)
.map(|(position, _)| EntityId(self.ids[position]))
.collect()
}
fn position(&self, id: EntityId) -> Option<usize> {
match &self.unordered {
Some(map) => map.get(&id.0).copied(),
None => self.ids.binary_search(&id.0).ok(),
}
}
pub fn entity(&self, id: EntityId) -> Result<Option<Entity>, StepError> {
let Some(position) = self.position(id) else {
return Ok(None);
};
let record = openbim_step::decode_record_borrowed(self.source, self.spans[position])?;
Ok(Some(parser::convert(record)?.1))
}
pub fn materialize(&self, wanted: &[EntityId]) -> Result<Model, StepError> {
self.build(wanted.iter().copied())
}
pub fn materialize_closure(&self, wanted: &[EntityId]) -> Result<Model, StepError> {
let mut needed: std::collections::BTreeSet<u64> = wanted.iter().map(|i| i.0).collect();
let mut frontier: Vec<u64> = needed.iter().copied().collect();
while let Some(id) = frontier.pop() {
let Some(entity) = self.entity(EntityId(id))? else {
continue;
};
for value in &entity.attributes {
collect_refs(value, &mut needed, &mut frontier);
}
}
self.build(needed.into_iter().map(EntityId))
}
fn build(&self, wanted: impl Iterator<Item = EntityId>) -> Result<Model, StepError> {
let mut positions: Vec<usize> = wanted.filter_map(|id| self.position(id)).collect();
positions.sort_unstable();
positions.dedup();
let mut model = Model::new();
*model.header_mut() = self.header.clone();
for position in positions {
let record = openbim_step::decode_record_borrowed(self.source, self.spans[position])?;
let (id, entity) = parser::convert(record)?;
model.insert(id, entity);
}
Ok(model)
}
}
fn unrepresentable(span: Span, detail: &str) -> StepError {
StepError::Syntax {
offset: span.start,
detail: detail.into(),
}
}
fn collect_refs(
value: &Value,
needed: &mut std::collections::BTreeSet<u64>,
frontier: &mut Vec<u64>,
) {
match value {
Value::Ref(id) => {
if needed.insert(id.0) {
frontier.push(id.0);
}
}
Value::List(items) => {
for v in items {
collect_refs(v, needed, frontier);
}
}
Value::Typed { value, .. } => collect_refs(value, needed, frontier),
_ => {}
}
}