use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::Schema;
use super::decode::{Need, Slots};
use super::issue::TabularIssue;
pub(crate) const REGULAR: &str = "IFCREGULARTIMESERIES";
pub(crate) const IRREGULAR: &str = "IFCIRREGULARTIMESERIES";
const VALUE: &str = "IFCTIMESERIESVALUE";
const IRREGULAR_VALUE: &str = "IFCIRREGULARTIMESERIESVALUE";
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum TimeSeriesKind {
Regular {
time_step: Option<f64>,
},
Irregular,
}
#[derive(Debug, Clone)]
pub struct TimeSeries<'m> {
id: EntityId,
kind: TimeSeriesKind,
name: Option<&'m str>,
description: Option<&'m str>,
start_time: Option<&'m str>,
end_time: Option<&'m str>,
data_type: Option<&'m str>,
data_origin: Option<&'m str>,
user_defined_data_origin: Option<&'m str>,
unit: Option<EntityId>,
values: Vec<SeriesValue<'m>>,
issues: Vec<TabularIssue>,
}
impl<'m> TimeSeries<'m> {
#[must_use]
pub const fn id(&self) -> EntityId {
self.id
}
#[must_use]
pub const fn kind(&self) -> TimeSeriesKind {
self.kind
}
#[must_use]
pub const fn name(&self) -> Option<&'m str> {
self.name
}
#[must_use]
pub const fn description(&self) -> Option<&'m str> {
self.description
}
#[must_use]
pub const fn start_time(&self) -> Option<&'m str> {
self.start_time
}
#[must_use]
pub const fn end_time(&self) -> Option<&'m str> {
self.end_time
}
#[must_use]
pub const fn data_type(&self) -> Option<&'m str> {
self.data_type
}
#[must_use]
pub const fn data_origin(&self) -> Option<&'m str> {
self.data_origin
}
#[must_use]
pub const fn user_defined_data_origin(&self) -> Option<&'m str> {
self.user_defined_data_origin
}
#[must_use]
pub const fn unit(&self) -> Option<EntityId> {
self.unit
}
#[must_use]
pub fn values(&self) -> &[SeriesValue<'m>] {
&self.values
}
#[must_use]
pub fn issues(&self) -> &[TabularIssue] {
&self.issues
}
}
#[derive(Debug, Clone, Copy)]
pub struct SeriesValue<'m> {
id: EntityId,
timestamp: Option<&'m str>,
values: Option<&'m [Value]>,
}
impl<'m> SeriesValue<'m> {
#[must_use]
pub const fn id(&self) -> EntityId {
self.id
}
#[must_use]
pub const fn timestamp(&self) -> Option<&'m str> {
self.timestamp
}
#[must_use]
pub const fn values(&self) -> Option<&'m [Value]> {
self.values
}
}
pub(crate) fn read_series<'m>(
model: &'m Model,
schema: &'m Schema,
id: EntityId,
entity: &'m Entity,
type_name: &'static str,
) -> TimeSeries<'m> {
let regular = type_name == REGULAR;
let mut issues = Vec::new();
let mut slots = Slots::open(model, schema, id, entity, type_name, &mut issues);
let name = slots.text("Name", Need::Required);
let description = slots.text("Description", Need::Optional);
let start_time = slots.text("StartTime", Need::Required);
let end_time = slots.text("EndTime", Need::Required);
let data_type = slots.enumeration("TimeSeriesDataType", Need::Required);
let data_origin = slots.enumeration("DataOrigin", Need::Required);
let user_defined_data_origin = slots.text("UserDefinedDataOrigin", Need::Optional);
let unit = slots.reference("Unit", Need::Optional);
let kind = if regular {
TimeSeriesKind::Regular {
time_step: slots.number("TimeStep", Need::Required),
}
} else {
TimeSeriesKind::Irregular
};
let value_type = if regular { VALUE } else { IRREGULAR_VALUE };
let members = slots.records("Values", Need::Required, value_type);
let values = members
.into_iter()
.flatten()
.map(|(value, record)| {
let mut slots = Slots::open(model, schema, value, record, value_type, &mut issues);
SeriesValue {
id: value,
timestamp: if regular {
None
} else {
slots.text("TimeStamp", Need::Required)
},
values: slots.values("ListValues", Need::Required),
}
})
.collect();
TimeSeries {
id,
kind,
name,
description,
start_time,
end_time,
data_type,
data_origin,
user_defined_data_origin,
unit,
values,
issues,
}
}