1use crate::assembler::{AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree};
4use mig_types::navigator::GroupNavigator;
5use mig_types::segment::OwnedSegment;
6
7pub struct AssembledTreeNavigator<'a> {
9 tree: &'a AssembledTree,
10}
11
12impl<'a> AssembledTreeNavigator<'a> {
13 pub fn new(tree: &'a AssembledTree) -> Self {
14 Self { tree }
15 }
16}
17
18impl GroupNavigator for AssembledTreeNavigator<'_> {
19 fn find_segments_in_group(
20 &self,
21 segment_id: &str,
22 group_path: &[&str],
23 instance_index: usize,
24 ) -> Vec<OwnedSegment> {
25 let Some(instance) = resolve_instance(&self.tree.groups, group_path, instance_index) else {
26 return Vec::new();
27 };
28 instance
29 .segments
30 .iter()
31 .enumerate()
32 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
33 .map(|(i, s)| to_owned(s, i as u32))
34 .collect()
35 }
36
37 fn find_segments_with_qualifier_in_group(
38 &self,
39 segment_id: &str,
40 element_index: usize,
41 qualifier: &str,
42 group_path: &[&str],
43 instance_index: usize,
44 ) -> Vec<OwnedSegment> {
45 self.find_segments_in_group(segment_id, group_path, instance_index)
46 .into_iter()
47 .filter(|s| {
48 s.elements
49 .get(element_index)
50 .and_then(|e| e.first())
51 .is_some_and(|v| v == qualifier)
52 })
53 .collect()
54 }
55
56 fn group_instance_count(&self, group_path: &[&str]) -> usize {
57 resolve_group(&self.tree.groups, group_path)
58 .map(|g| g.repetitions.len())
59 .unwrap_or(0)
60 }
61
62 fn find_segments_in_instance(
63 &self,
64 segment_id: &str,
65 path: &[(&str, usize)],
66 ) -> Vec<OwnedSegment> {
67 let Some(instance) = resolve_instance_path(&self.tree.groups, path) else {
68 return Vec::new();
69 };
70 instance
71 .segments
72 .iter()
73 .enumerate()
74 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
75 .map(|(i, s)| to_owned(s, i as u32))
76 .collect()
77 }
78
79 fn child_instance_count(&self, parent: &[(&str, usize)], child_group_id: &str) -> usize {
80 let groups = if parent.is_empty() {
81 &self.tree.groups
82 } else {
83 match resolve_instance_path(&self.tree.groups, parent) {
84 Some(instance) => &instance.child_groups,
85 None => return 0,
86 }
87 };
88 groups
89 .iter()
90 .find(|g| g.group_id == child_group_id)
91 .map_or(0, |g| g.repetitions.len())
92 }
93
94 fn find_segments_in_subtree(
95 &self,
96 segment_id: &str,
97 path: &[(&str, usize)],
98 ) -> Vec<OwnedSegment> {
99 let Some(instance) = resolve_instance_path(&self.tree.groups, path) else {
100 return Vec::new();
101 };
102 let mut out = Vec::new();
103 collect_subtree(instance, segment_id, &mut out);
104 out
105 }
106
107 fn has_any_segment_in_group(&self, group_path: &[&str], instance_index: usize) -> bool {
108 resolve_instance(&self.tree.groups, group_path, instance_index)
109 .is_some_and(|inst| !inst.segments.is_empty())
110 }
111
112 fn instance_has_mig_number(
113 &self,
114 group_path: &[&str],
115 instance_index: usize,
116 mig_number: &str,
117 ) -> bool {
118 let Some(inst) = resolve_instance(&self.tree.groups, group_path, instance_index) else {
119 return true;
120 };
121 if !inst.variant_mig_numbers.is_empty() {
122 return inst.variant_mig_numbers.iter().any(|n| n == mig_number);
123 }
124 has_mig_in_instance(inst, mig_number)
125 }
126
127 fn child_group_instance_count(
128 &self,
129 parent_path: &[&str],
130 parent_instance: usize,
131 child_group_id: &str,
132 ) -> usize {
133 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
134 return 0;
135 };
136 parent
137 .child_groups
138 .iter()
139 .find(|g| g.group_id == child_group_id)
140 .map(|g| g.repetitions.len())
141 .unwrap_or(0)
142 }
143
144 fn find_segments_in_child_group(
145 &self,
146 segment_id: &str,
147 parent_path: &[&str],
148 parent_instance: usize,
149 child_group_id: &str,
150 child_instance: usize,
151 ) -> Vec<OwnedSegment> {
152 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
153 return Vec::new();
154 };
155 let Some(child_group) = parent
156 .child_groups
157 .iter()
158 .find(|g| g.group_id == child_group_id)
159 else {
160 return Vec::new();
161 };
162 let Some(instance) = child_group.repetitions.get(child_instance) else {
163 return Vec::new();
164 };
165 instance
166 .segments
167 .iter()
168 .enumerate()
169 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
170 .map(|(i, s)| to_owned(s, i as u32))
171 .collect()
172 }
173
174 fn extract_value_in_group(
175 &self,
176 segment_id: &str,
177 element_index: usize,
178 component_index: usize,
179 group_path: &[&str],
180 instance_index: usize,
181 ) -> Option<String> {
182 let instance = resolve_instance(&self.tree.groups, group_path, instance_index)?;
183 let seg = instance
184 .segments
185 .iter()
186 .find(|s| s.tag.eq_ignore_ascii_case(segment_id))?;
187 seg.elements
188 .get(element_index)?
189 .get(component_index)
190 .cloned()
191 }
192}
193
194fn resolve_group<'a>(groups: &'a [AssembledGroup], path: &[&str]) -> Option<&'a AssembledGroup> {
196 if path.is_empty() {
197 return None;
198 }
199 let group = groups.iter().find(|g| g.group_id == path[0])?;
200 if path.len() == 1 {
201 return Some(group);
202 }
203 let instance = group.repetitions.first()?;
205 resolve_group(&instance.child_groups, &path[1..])
206}
207
208fn resolve_instance_path<'a>(
210 groups: &'a [AssembledGroup],
211 path: &[(&str, usize)],
212) -> Option<&'a AssembledGroupInstance> {
213 let (&(group_id, index), rest) = path.split_first()?;
214 let instance = groups
215 .iter()
216 .find(|g| g.group_id == group_id)?
217 .repetitions
218 .get(index)?;
219 if rest.is_empty() {
220 Some(instance)
221 } else {
222 resolve_instance_path(&instance.child_groups, rest)
223 }
224}
225
226fn collect_subtree(
229 instance: &AssembledGroupInstance,
230 segment_id: &str,
231 out: &mut Vec<OwnedSegment>,
232) {
233 for (i, s) in instance.segments.iter().enumerate() {
234 if s.tag.eq_ignore_ascii_case(segment_id) {
235 out.push(to_owned(s, i as u32));
236 }
237 }
238 for group in &instance.child_groups {
239 for repetition in &group.repetitions {
240 collect_subtree(repetition, segment_id, out);
241 }
242 }
243}
244
245fn resolve_instance<'a>(
247 groups: &'a [AssembledGroup],
248 path: &[&str],
249 instance_index: usize,
250) -> Option<&'a AssembledGroupInstance> {
251 let group = resolve_group(groups, path)?;
252 group.repetitions.get(instance_index)
253}
254
255fn has_mig_in_instance(inst: &AssembledGroupInstance, mig_number: &str) -> bool {
258 if inst
259 .segments
260 .iter()
261 .any(|s| s.mig_number.as_deref() == Some(mig_number))
262 {
263 return true;
264 }
265 inst.child_groups.iter().any(|g| {
266 g.repetitions
267 .iter()
268 .any(|r| has_mig_in_instance(r, mig_number))
269 })
270}
271
272fn to_owned(seg: &AssembledSegment, segment_number: u32) -> OwnedSegment {
273 OwnedSegment {
274 id: seg.tag.clone(),
275 elements: seg.elements.clone(),
276 segment_number,
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::assembler::{
284 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
285 };
286 use mig_types::navigator::GroupNavigator;
287
288 fn make_seg(tag: &str, elements: Vec<Vec<&str>>) -> AssembledSegment {
289 AssembledSegment {
290 tag: tag.to_string(),
291 elements: elements
292 .into_iter()
293 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
294 .collect(),
295 mig_number: None,
296 segment_number: None,
297 }
298 }
299
300 fn tree_with_sg4_sg8() -> AssembledTree {
301 AssembledTree {
308 segments: vec![make_seg("UNH", vec![vec!["001"]])],
309 groups: vec![AssembledGroup {
310 group_id: "SG4".to_string(),
311 repetitions: vec![AssembledGroupInstance {
312 segments: vec![
313 make_seg("IDE", vec![vec!["24", "TX001"]]),
314 make_seg("STS", vec![vec!["E01"], vec![], vec!["A05"]]),
315 ],
316 child_groups: vec![AssembledGroup {
317 group_id: "SG8".to_string(),
318 repetitions: vec![
319 AssembledGroupInstance {
320 segments: vec![
321 make_seg("SEQ", vec![vec!["Z98"]]),
322 make_seg("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
323 ],
324 child_groups: vec![AssembledGroup {
325 group_id: "SG10".to_string(),
326 repetitions: vec![
327 AssembledGroupInstance {
328 segments: vec![
329 make_seg("CCI", vec![vec!["Z23"]]),
330 make_seg("CAV", vec![vec!["Z91", "value1"]]),
331 ],
332 child_groups: vec![],
333 entry_mig_number: None,
334 variant_mig_numbers: vec![],
335 skipped_segments: vec![],
336 skipped_positions: Vec::new(),
337 },
338 AssembledGroupInstance {
339 segments: vec![
340 make_seg("CCI", vec![vec![""]]),
341 make_seg("CAV", vec![vec!["", "value2"]]),
342 ],
343 child_groups: vec![],
344 entry_mig_number: None,
345 variant_mig_numbers: vec![],
346 skipped_segments: vec![],
347 skipped_positions: Vec::new(),
348 },
349 ],
350 }],
351 entry_mig_number: None,
352 variant_mig_numbers: vec![],
353 skipped_segments: vec![],
354 skipped_positions: Vec::new(),
355 },
356 AssembledGroupInstance {
357 segments: vec![
358 make_seg("SEQ", vec![vec!["Z01"]]),
359 make_seg("CCI", vec![vec![""], vec![], vec!["ZC0"]]),
360 ],
361 child_groups: vec![],
362 entry_mig_number: None,
363 variant_mig_numbers: vec![],
364 skipped_segments: vec![],
365 skipped_positions: Vec::new(),
366 },
367 ],
368 }],
369 entry_mig_number: None,
370 variant_mig_numbers: vec![],
371 skipped_segments: vec![],
372 skipped_positions: Vec::new(),
373 }],
374 }],
375 post_group_start: 1,
376 inter_group_segments: std::collections::BTreeMap::new(),
377 }
378 }
379
380 fn tree_with_two_sg4() -> AssembledTree {
383 fn instance(
384 segments: Vec<AssembledSegment>,
385 child_groups: Vec<AssembledGroup>,
386 ) -> AssembledGroupInstance {
387 AssembledGroupInstance {
388 segments,
389 child_groups,
390 entry_mig_number: None,
391 variant_mig_numbers: vec![],
392 skipped_segments: vec![],
393 skipped_positions: Vec::new(),
394 }
395 }
396 fn group(id: &str, repetitions: Vec<AssembledGroupInstance>) -> AssembledGroup {
397 AssembledGroup {
398 group_id: id.to_string(),
399 repetitions,
400 }
401 }
402 let sg10 = |code: &str| {
403 group(
404 "SG10",
405 vec![instance(
406 vec![make_seg("CCI", vec![vec![""], vec![], vec![code]])],
407 vec![],
408 )],
409 )
410 };
411 AssembledTree {
412 segments: vec![],
413 groups: vec![group(
414 "SG4",
415 vec![
416 instance(
417 vec![make_seg("IDE", vec![vec!["24", "TX0"]])],
418 vec![group(
419 "SG8",
420 vec![instance(
421 vec![make_seg("SEQ", vec![vec!["Z98"]])],
422 vec![sg10("Z23")],
423 )],
424 )],
425 ),
426 instance(
427 vec![make_seg("IDE", vec![vec!["24", "TX1"]])],
428 vec![group(
429 "SG8",
430 vec![
431 instance(vec![make_seg("SEQ", vec![vec!["Z01"]])], vec![]),
432 instance(
433 vec![make_seg("SEQ", vec![vec!["Z03"]])],
434 vec![sg10("E13")],
435 ),
436 ],
437 )],
438 ),
439 ],
440 )],
441 post_group_start: 0,
442 inter_group_segments: std::collections::BTreeMap::new(),
443 }
444 }
445
446 #[test]
447 fn an_instance_is_addressed_at_every_level() {
448 let tree = tree_with_two_sg4();
449 let nav = AssembledTreeNavigator::new(&tree);
450 let seq = |path: &[(&str, usize)]| -> Vec<String> {
451 nav.find_segments_in_instance("SEQ", path)
452 .iter()
453 .map(|s| s.get_element(0).to_string())
454 .collect()
455 };
456 assert_eq!(seq(&[("SG4", 0), ("SG8", 0)]), ["Z98"]);
457 assert_eq!(seq(&[("SG4", 1), ("SG8", 0)]), ["Z01"]);
458 assert_eq!(seq(&[("SG4", 1), ("SG8", 1)]), ["Z03"]);
459 assert!(seq(&[("SG4", 0), ("SG8", 1)]).is_empty());
460 }
461
462 #[test]
463 fn a_subtree_includes_the_nested_groups() {
464 let tree = tree_with_two_sg4();
465 let nav = AssembledTreeNavigator::new(&tree);
466 let cci = |path: &[(&str, usize)]| -> Vec<String> {
467 nav.find_segments_in_subtree("CCI", path)
468 .iter()
469 .map(|s| s.get_component(2, 0).to_string())
470 .collect()
471 };
472 assert_eq!(cci(&[("SG4", 1), ("SG8", 1)]), ["E13"]);
473 assert!(cci(&[("SG4", 1), ("SG8", 0)]).is_empty());
474 assert_eq!(cci(&[("SG4", 1)]), ["E13"], "the whole SG4");
475 assert!(
476 nav.find_segments_in_instance("CCI", &[("SG4", 1), ("SG8", 1)])
477 .is_empty(),
478 "the instance alone holds no CCI"
479 );
480 }
481
482 #[test]
483 fn test_find_in_sg8_instance_0() {
484 let tree = tree_with_sg4_sg8();
485 let nav = AssembledTreeNavigator::new(&tree);
486 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
487 assert_eq!(segs.len(), 1);
488 assert_eq!(segs[0].get_element(0), "Z98");
489 }
490
491 #[test]
492 fn test_find_in_sg8_instance_1() {
493 let tree = tree_with_sg4_sg8();
494 let nav = AssembledTreeNavigator::new(&tree);
495 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 1);
496 assert_eq!(segs.len(), 1);
497 assert_eq!(segs[0].get_element(0), "Z01");
498 }
499
500 #[test]
501 fn test_qualifier_in_group_scoped() {
502 let tree = tree_with_sg4_sg8();
503 let nav = AssembledTreeNavigator::new(&tree);
504 let segs = nav.find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 1);
505 assert_eq!(segs.len(), 1);
506 assert!(nav
508 .find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 0)
509 .is_empty());
510 }
511
512 #[test]
513 fn test_group_instance_count() {
514 let tree = tree_with_sg4_sg8();
515 let nav = AssembledTreeNavigator::new(&tree);
516 assert_eq!(nav.group_instance_count(&["SG4"]), 1);
517 assert_eq!(nav.group_instance_count(&["SG4", "SG8"]), 2);
518 assert_eq!(nav.group_instance_count(&["SG4", "SG5"]), 0);
519 }
520
521 #[test]
522 fn test_find_in_sg4_directly() {
523 let tree = tree_with_sg4_sg8();
524 let nav = AssembledTreeNavigator::new(&tree);
525 let segs = nav.find_segments_in_group("STS", &["SG4"], 0);
526 assert_eq!(segs.len(), 1);
527 }
528
529 #[test]
530 fn test_invalid_path_returns_empty() {
531 let tree = tree_with_sg4_sg8();
532 let nav = AssembledTreeNavigator::new(&tree);
533 assert!(nav.find_segments_in_group("SEQ", &["SG99"], 0).is_empty());
534 assert!(nav
535 .find_segments_in_group("SEQ", &["SG4", "SG8"], 99)
536 .is_empty());
537 assert!(nav.find_segments_in_group("SEQ", &[], 0).is_empty());
538 }
539
540 #[test]
541 fn test_child_group_instance_count() {
542 let tree = tree_with_sg4_sg8();
543 let nav = AssembledTreeNavigator::new(&tree);
544 assert_eq!(
546 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
547 2
548 );
549 assert_eq!(
551 nav.child_group_instance_count(&["SG4", "SG8"], 1, "SG10"),
552 0
553 );
554 assert_eq!(
556 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG12"),
557 0
558 );
559 }
560
561 #[test]
562 fn test_find_segments_in_child_group() {
563 let tree = tree_with_sg4_sg8();
564 let nav = AssembledTreeNavigator::new(&tree);
565 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0);
567 assert_eq!(segs.len(), 1);
568 assert_eq!(segs[0].get_element(0), "Z23");
569 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 1);
571 assert_eq!(segs.len(), 1);
572 assert_eq!(segs[0].get_element(0), "");
573 let segs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], 0, "SG10", 0);
575 assert_eq!(segs.len(), 1);
576 }
577
578 #[test]
579 fn test_child_group_invalid_path() {
580 let tree = tree_with_sg4_sg8();
581 let nav = AssembledTreeNavigator::new(&tree);
582 assert_eq!(nav.child_group_instance_count(&["SG99"], 0, "SG10"), 0);
584 assert!(nav
586 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 99, "SG10", 0)
587 .is_empty());
588 assert!(nav
590 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 99)
591 .is_empty());
592 }
593
594 #[test]
595 fn test_extract_value_in_group() {
596 let tree = tree_with_sg4_sg8();
597 let nav = AssembledTreeNavigator::new(&tree);
598 assert_eq!(
600 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
601 Some("Z98".to_string()),
602 );
603 assert_eq!(
605 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 1),
606 Some("Z01".to_string()),
607 );
608 }
609
610 #[test]
611 fn test_extract_value_missing() {
612 let tree = tree_with_sg4_sg8();
613 let nav = AssembledTreeNavigator::new(&tree);
614 assert_eq!(
616 nav.extract_value_in_group("LOC", 0, 0, &["SG4", "SG8"], 0),
617 None
618 );
619 assert_eq!(
621 nav.extract_value_in_group("SEQ", 5, 0, &["SG4", "SG8"], 0),
622 None
623 );
624 assert_eq!(
626 nav.extract_value_in_group("SEQ", 0, 5, &["SG4", "SG8"], 0),
627 None
628 );
629 assert_eq!(nav.extract_value_in_group("SEQ", 0, 0, &["SG99"], 0), None);
631 }
632}