use std::collections::{HashMap, HashSet};
use anyhow::Result;
use crate::sdf::expr;
use crate::sdf::schema::FieldKey;
use crate::sdf::{self, LayerOffset, ListOp, Path, Payload, PayloadListOp, Reference, Value};
use super::prim_graph::Node;
use super::{Error, ExpressionContext, LayerGraph, LayerId};
pub(super) fn compose_references_in(
nodes: &[Node],
graph: &LayerGraph,
expr_vars: &HashMap<String, Value>,
site: &Path,
errors: &mut Vec<Error>,
used_vars: &mut HashSet<String>,
) -> Result<Vec<Reference>> {
let mut refs = compose_list_op_in(
nodes,
FieldKey::References.as_str(),
graph,
|v| v.try_into().ok(),
|r: &mut Reference, sub, scale| {
if scale != 1.0 {
r.layer_offset = r.layer_offset.concatenate(&sdf::LayerOffset::scale_only(scale));
}
if !sub.is_identity() {
r.layer_offset = sub.concatenate(&r.layer_offset);
}
},
|r: &mut Reference, layer| {
resolve_arc_asset_path(
&mut r.asset_path,
layer,
graph,
expr_vars,
ExpressionContext::Reference,
site,
errors,
used_vars,
)
.unwrap_or(1.0)
},
)?;
refs.retain(|r| !expr::is_expression(&r.asset_path));
Ok(refs)
}
pub(super) fn collect_payloads_in(
nodes: &[Node],
graph: &LayerGraph,
expr_vars: &HashMap<String, Value>,
site: &Path,
errors: &mut Vec<Error>,
used_vars: &mut HashSet<String>,
) -> Result<Vec<Payload>> {
let mut payloads = compose_list_op_in(
nodes,
FieldKey::Payload.as_str(),
graph,
|v| match v {
Value::Payload(p) => Some(PayloadListOp {
explicit: true,
explicit_items: vec![p],
..Default::default()
}),
Value::PayloadListOp(op) => Some(op),
_ => None,
},
|p: &mut Payload, sub, scale| {
if scale != 1.0 {
let offset = p.layer_offset.unwrap_or_default();
p.layer_offset = Some(offset.concatenate(&sdf::LayerOffset::scale_only(scale)));
}
if !sub.is_identity() {
p.layer_offset = Some(sub.concatenate(&p.layer_offset.unwrap_or_default()));
}
},
|p: &mut Payload, layer| {
resolve_arc_asset_path(
&mut p.asset_path,
layer,
graph,
expr_vars,
ExpressionContext::Payload,
site,
errors,
used_vars,
)
.unwrap_or(1.0)
},
)?;
payloads.retain(|p| !expr::is_expression(&p.asset_path));
Ok(payloads)
}
pub(super) fn compose_arc_list_in<T: Default + Clone + PartialEq>(
nodes: &[Node],
field: FieldKey,
graph: &LayerGraph,
) -> Result<Vec<T>>
where
Value: TryInto<ListOp<T>>,
{
compose_list_op_in(
nodes,
field.as_str(),
graph,
|v| v.try_into().ok(),
|_, _, _| {},
|_, _| 1.0,
)
}
fn compose_list_op_in<T, D, R, A>(
nodes: &[Node],
field: &str,
graph: &LayerGraph,
decode: D,
mut retime: R,
mut anchor: A,
) -> Result<Vec<T>>
where
T: Default + Clone + PartialEq,
D: Fn(Value) -> Option<ListOp<T>>,
R: FnMut(&mut T, LayerOffset, f64),
A: FnMut(&mut T, LayerId) -> f64,
{
let mut combined: Option<ListOp<T>> = None;
let mut folds: Vec<(T, LayerOffset, f64)> = Vec::new();
for node in nodes {
let mut seen_layers: HashSet<LayerId> = HashSet::new();
for &(layer, sub) in graph.layer_stack(node.layer_stack_id()).iter() {
if !seen_layers.insert(layer) {
continue;
}
let Some(value) = graph.layer(layer).data().try_field(&node.path, field)? else {
continue;
};
let Some(mut list_op) = decode(value.into_owned()) else {
continue;
};
for item in list_op.iter_mut() {
let scale = anchor(item, layer);
if (!sub.is_identity() || scale != 1.0) && !folds.iter().any(|(i, _, _)| i == item) {
folds.push((item.clone(), sub, scale));
}
}
combined = Some(match combined {
Some(stronger) => stronger.combined_with(&list_op),
None => list_op,
});
}
}
let mut result = combined.map(|op| op.reduced().flatten()).unwrap_or_default();
for item in &mut result {
if let Some((_, sub, scale)) = folds.iter().find(|(i, _, _)| i == item) {
retime(item, *sub, *scale);
}
}
Ok(result)
}
#[allow(clippy::too_many_arguments)]
fn resolve_arc_asset_path(
asset_path: &mut String,
authoring_layer: LayerId,
graph: &LayerGraph,
expr_vars: &HashMap<String, Value>,
context: ExpressionContext,
site: &Path,
errors: &mut Vec<Error>,
used_vars: &mut HashSet<String>,
) -> Option<f64> {
if expr::is_expression(asset_path) {
match evaluate_expression(
asset_path,
expr_vars,
context,
graph.layer(authoring_layer),
site,
Some(errors),
Some(used_vars),
) {
EvaluatedExpression::Value(resolved) => *asset_path = resolved,
EvaluatedExpression::None | EvaluatedExpression::Failed => return None,
}
}
anchor_asset_path(asset_path, graph.layer(authoring_layer), graph.layer_registry());
Some(arc_tcps_scale(graph.layer(authoring_layer), asset_path, graph))
}
pub(super) enum EvaluatedExpression {
Value(String),
None,
Failed,
}
impl EvaluatedExpression {
pub(super) fn into_selection(self) -> Option<String> {
match self {
Self::Value(selection) => Some(selection),
Self::None => Some(String::new()),
Self::Failed => None,
}
}
}
pub(super) fn evaluate_expression(
expression: &str,
expr_vars: &HashMap<String, Value>,
context: ExpressionContext,
authoring_layer: &sdf::Layer,
site_path: &Path,
errors: Option<&mut Vec<Error>>,
used_vars: Option<&mut HashSet<String>>,
) -> EvaluatedExpression {
let evaluated = expr::evaluate_string(expression, expr_vars);
if let Some(used_vars) = used_vars {
used_vars.extend(evaluated.used_variables);
}
match evaluated.value {
Some(resolved) => EvaluatedExpression::Value(resolved),
None if evaluated.errors.is_empty() => EvaluatedExpression::None,
None => {
if let Some(errors) = errors {
let error = Error::InvalidExpression {
expression: expression.to_string(),
context,
source_layer: authoring_layer.identifier().to_string(),
site_path: site_path.clone(),
message: evaluated.errors.join("; "),
};
if !errors.contains(&error) {
errors.push(error);
}
}
EvaluatedExpression::Failed
}
}
}
fn anchor_asset_path(asset_path: &mut String, authoring_layer: &sdf::Layer, registry: &sdf::LayerRegistry) {
if asset_path.is_empty() {
return;
}
*asset_path = registry.create_identifier_anchored(asset_path, authoring_layer.real_path());
}
fn arc_tcps_scale(introducing: &sdf::Layer, asset_path: &str, graph: &LayerGraph) -> f64 {
if asset_path.is_empty() {
return 1.0;
}
graph.id_of(asset_path).map_or(1.0, |target| {
super::effective_time_codes_per_second(introducing)
/ super::effective_time_codes_per_second(graph.layer(target))
})
}