use sim_kernel::{Cx, Error, Expr, Result, Symbol};
use crate::{EdgeId, compile_graph, reflect::TopologyRunReport, run::run_graph};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TopologyCounterfactual {
ReplaceTarget {
node: Symbol,
target: Expr,
},
DisableEdge {
edge: EdgeId,
},
ForcePredicate {
node: Symbol,
result: bool,
},
}
pub fn replay_report(report: &TopologyRunReport) -> Result<Expr> {
if report.events.is_empty() {
return Err(Error::Eval(
"topology replay requires a reflected run report with events".to_owned(),
));
}
Ok(report.output.clone())
}
pub fn counterfactual_replay(
cx: &mut Cx,
report: &TopologyRunReport,
change: TopologyCounterfactual,
) -> Result<Expr> {
let mut graph = report.source_graph().clone();
apply_counterfactual(&mut graph, change)?;
let plan = compile_graph(cx, &graph)?;
run_graph(cx, &graph, &plan, report.input.clone())
}
fn apply_counterfactual(graph: &mut crate::Graph, change: TopologyCounterfactual) -> Result<()> {
match change {
TopologyCounterfactual::ReplaceTarget { node, target } => {
let node = graph_node_mut(graph, &node)?;
node.target = Some(target);
Ok(())
}
TopologyCounterfactual::DisableEdge { edge } => {
let before = graph.edges.len();
graph.edges.retain(|candidate| candidate.id != edge);
if graph.edges.len() == before {
return Err(Error::Eval(format!(
"topology counterfactual: unknown edge {}",
edge.0
)));
}
Ok(())
}
TopologyCounterfactual::ForcePredicate { node, result } => {
let node = graph_node_mut(graph, &node)?;
upsert_option(&mut node.options, "when", Expr::Bool(result));
Ok(())
}
}
}
fn graph_node_mut<'a>(graph: &'a mut crate::Graph, id: &Symbol) -> Result<&'a mut crate::Node> {
graph
.nodes
.iter_mut()
.find(|node| node.id.as_symbol() == id)
.ok_or_else(|| Error::Eval(format!("topology counterfactual: unknown node {id}")))
}
fn upsert_option(options: &mut Vec<(Symbol, Expr)>, key: &str, value: Expr) {
if let Some((_, existing)) = options
.iter_mut()
.find(|(name, _)| name.namespace.is_none() && name.name.as_ref() == key)
{
*existing = value;
} else {
options.push((Symbol::new(key), value));
}
}