use std::collections::HashMap;
use mig_assembly::assembler::{AssembledGroup, AssembledGroupInstance, AssembledTree};
use mig_bo4e::MappingEngine;
use serde_json::Value;
pub type ElementKey = (String, Option<usize>);
pub type InstancePath = Vec<(String, usize)>;
pub fn entity_element_scopes(
engine: &MappingEngine,
json: &Value,
tree: &AssembledTree,
) -> HashMap<ElementKey, InstancePath> {
let mut scopes = HashMap::new();
let Some(entities) = json.as_object() else {
return scopes;
};
for (key, value) in entities {
let indices: Vec<Option<usize>> = match value.as_array() {
Some(elements) => (0..elements.len()).map(Some).collect(),
None => vec![None],
};
for index in indices {
let mut without = json.clone();
match index {
Some(i) => {
without[key.as_str()]
.as_array_mut()
.expect("array")
.remove(i);
}
None => {
without.as_object_mut().expect("object").remove(key);
}
}
let reduced = engine.map_all_reverse(&without, None);
let mut removed = Vec::new();
removed_instances(&tree.groups, &reduced.groups, &mut Vec::new(), &mut removed);
if let [path] = &removed[..] {
scopes.insert((key.clone(), index), path.clone());
}
}
}
scopes
}
fn removed_instances(
full: &[AssembledGroup],
reduced: &[AssembledGroup],
prefix: &mut InstancePath,
out: &mut Vec<InstancePath>,
) {
for group in full {
let remaining: &[AssembledGroupInstance] = reduced
.iter()
.find(|g| g.group_id == group.group_id)
.map_or(&[], |g| &g.repetitions);
let repetitions = &group.repetitions;
if repetitions.len() == remaining.len() {
for (i, (a, b)) in repetitions.iter().zip(remaining).enumerate() {
if a != b {
prefix.push((group.group_id.clone(), i));
removed_instances(&a.child_groups, &b.child_groups, prefix, out);
prefix.pop();
}
}
continue;
}
let mut next = 0;
for (i, repetition) in repetitions.iter().enumerate() {
if remaining.get(next) == Some(repetition) {
next += 1;
} else {
let mut path = prefix.clone();
path.push((group.group_id.clone(), i));
out.push(path);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use mig_assembly::assembler::AssembledSegment;
fn rep(seq: &str) -> AssembledGroupInstance {
AssembledGroupInstance {
segments: vec![AssembledSegment {
tag: "SEQ".to_string(),
elements: vec![vec![seq.to_string()]],
mig_number: None,
segment_number: None,
}],
child_groups: vec![],
entry_mig_number: None,
variant_mig_numbers: vec![],
skipped_segments: vec![],
skipped_positions: Vec::new(),
}
}
fn sg8(seqs: &[&str]) -> Vec<AssembledGroup> {
vec![AssembledGroup {
group_id: "SG8".to_string(),
repetitions: seqs.iter().map(|s| rep(s)).collect(),
}]
}
fn removed(full: &[&str], reduced: &[&str]) -> Vec<InstancePath> {
let mut out = Vec::new();
removed_instances(&sg8(full), &sg8(reduced), &mut Vec::new(), &mut out);
out
}
#[test]
fn the_missing_repetition_is_found_by_order() {
assert_eq!(
removed(&["Z01", "Z03", "Z20"], &["Z01", "Z20"]),
[vec![("SG8".to_string(), 1)]]
);
}
#[test]
fn a_reduced_tree_that_does_not_line_up_names_no_single_instance() {
let out = removed(&["Z01", "Z03", "Z20"], &["Z01", "Z99"]);
assert_ne!(out.len(), 1, "{out:?}");
}
}