use std::cell::Cell;
use energyml_core::value::{FieldValue, Reflect};
use crate::error::{Error, Result};
use crate::points::Point;
use crate::source::ValueSource;
use crate::values::scalar_i64;
const DEPTH: usize = 8;
thread_local! {
static BORROWED: Cell<usize> = const { Cell::new(0) };
}
pub(crate) fn indices_on(lattice: &dyn Reflect, shape: &[usize], at: &str) -> Result<Vec<usize>> {
let selection = Selection::read(lattice, at)?;
if selection.steps.len() > shape.len() {
return Err(Error::malformed(
at,
format!(
"a selection over {} axis/axes of a support that states {}",
selection.steps.len(),
shape.len()
),
));
}
let available: usize = shape.iter().product();
let mut strides = vec![1usize; shape.len()];
for axis in (0..shape.len().saturating_sub(1)).rev() {
strides[axis] = strides[axis + 1] * shape[axis + 1];
}
let mut out = Vec::with_capacity(selection.total());
let mut counters = vec![0usize; selection.steps.len()];
loop {
let mut index = selection.start;
for (axis, position) in counters.iter().enumerate() {
index += *position as i64 * selection.steps[axis].value * strides[axis] as i64;
}
if index < 0 || index as usize >= available {
return Err(Error::malformed(
at,
format!(
"the selection reaches {index} of a support with {available} - read as a \
step along each axis of the support, which is what the standard's own \
business rules describe; a file meaning a step in the flat order would \
need a different reading and this one has not guessed at it"
),
));
}
out.push(index as usize);
let mut axis = counters.len();
loop {
if axis == 0 {
return Ok(out);
}
axis -= 1;
counters[axis] += 1;
if counters[axis] <= selection.steps[axis].count {
break;
}
counters[axis] = 0;
}
}
}
pub fn read(array: &dyn Reflect, source: &dyn ValueSource, at: &str) -> Result<Vec<Point>> {
let depth = BORROWED.with(|d| d.get());
if depth >= DEPTH {
return Err(Error::malformed(
at,
format!("geometry borrowed through more than {DEPTH} representations"),
));
}
let Some(support_uri) = crate::well::reference_of(array, "SupportingRepresentation") else {
return Err(Error::malformed(
at,
"a lattice over a representation that it does not name",
));
};
let Some(support) = source.object_at(&support_uri) else {
return Err(Error::Domain(energyml_domain::Error::Missing(format!(
"the supporting representation {support_uri}"
))));
};
let support = support.as_reflect();
let stated = shape_of(support);
let points = BORROWED.with(|d| {
d.set(depth + 1);
let result = crate::geometry::of(support, source);
d.set(depth);
result
})?;
let (points, _) = points.merged();
let shape = match stated {
Some(shape) => {
let declared: usize = shape.iter().product();
if points.len() != declared {
return Err(Error::malformed(
at,
format!(
"the support {support_uri} states {shape:?} - {declared} node(s) - and \
evaluates to {}",
points.len()
),
));
}
shape
}
None => vec![points.len()],
};
let Some(lattice) = field_object(array, "NodeIndicesOnSupportingRepresentation") else {
return Err(Error::malformed(
at,
"a lattice over a representation with no `NodeIndicesOnSupportingRepresentation`",
));
};
Ok(indices_on(lattice, &shape, at)?
.into_iter()
.map(|index| points[index])
.collect())
}
pub fn count(array: &dyn Reflect, at: &str) -> Result<usize> {
Ok(selection_of(array, at)?.total())
}
pub fn selection_dims(array: &dyn Reflect) -> Result<Vec<usize>> {
Ok(selection_of(array, "selection")?
.steps
.iter()
.map(|step| step.count + 1)
.collect())
}
fn selection_of(array: &dyn Reflect, at: &str) -> Result<Selection> {
let Some(lattice) = field_object(array, "NodeIndicesOnSupportingRepresentation") else {
return Err(Error::malformed(
at,
"a lattice over a representation with no `NodeIndicesOnSupportingRepresentation`",
));
};
Selection::read(lattice, at)
}
struct Step {
value: i64,
count: usize,
}
struct Selection {
start: i64,
steps: Vec<Step>,
}
impl Selection {
fn read(lattice: &dyn Reflect, at: &str) -> Result<Self> {
let start = scalar_i64(lattice, "StartValue").unwrap_or(0);
let offsets = match lattice.field("Offset") {
Some(FieldValue::List(items)) => items
.iter()
.filter_map(|item| item.as_object())
.collect::<Vec<_>>(),
Some(FieldValue::Object(one)) => vec![one],
_ => Vec::new(),
};
if offsets.is_empty() {
return Err(Error::malformed(at, "a node selection with no `Offset`"));
}
let mut steps = Vec::with_capacity(offsets.len());
for offset in offsets {
let value = scalar_i64(offset, "Value").unwrap_or(1);
let count = scalar_i64(offset, "Count").unwrap_or(0);
if count < 0 {
return Err(Error::malformed(
at,
format!("an offset repeated {count} time(s)"),
));
}
steps.push(Step {
value,
count: count as usize,
});
}
Ok(Selection { start, steps })
}
fn total(&self) -> usize {
self.steps.iter().map(|step| step.count + 1).product()
}
}
fn shape_of(support: &dyn Reflect) -> Option<Vec<usize>> {
let holders = [Some(support), field_object(support, "Grid2dPatch")];
for holder in holders.into_iter().flatten() {
let axis = |name: &str| scalar_i64(holder, name).filter(|n| *n > 0).map(|n| n as usize);
match (axis("SlowestAxisCount"), axis("FastestAxisCount")) {
(Some(slowest), Some(fastest)) => return Some(vec![slowest, fastest]),
(Some(one), None) | (None, Some(one)) => return Some(vec![one]),
(None, None) => {}
}
}
None
}
fn field_object<'a>(object: &'a dyn Reflect, field: &str) -> Option<&'a dyn Reflect> {
match object.field(field)? {
FieldValue::Object(child) => Some(child),
_ => None,
}
}