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 instance_at_has_mig_number(&self, path: &[(&str, usize)], mig_number: &str) -> bool {
128 let Some(inst) = resolve_instance_path(&self.tree.groups, path) else {
129 return true;
130 };
131 if !inst.variant_mig_numbers.is_empty() {
132 return inst.variant_mig_numbers.iter().any(|n| n == mig_number);
133 }
134 has_mig_in_instance(inst, mig_number)
135 }
136
137 fn child_group_instance_count(
138 &self,
139 parent_path: &[&str],
140 parent_instance: usize,
141 child_group_id: &str,
142 ) -> usize {
143 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
144 return 0;
145 };
146 parent
147 .child_groups
148 .iter()
149 .find(|g| g.group_id == child_group_id)
150 .map(|g| g.repetitions.len())
151 .unwrap_or(0)
152 }
153
154 fn find_segments_in_child_group(
155 &self,
156 segment_id: &str,
157 parent_path: &[&str],
158 parent_instance: usize,
159 child_group_id: &str,
160 child_instance: usize,
161 ) -> Vec<OwnedSegment> {
162 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
163 return Vec::new();
164 };
165 let Some(child_group) = parent
166 .child_groups
167 .iter()
168 .find(|g| g.group_id == child_group_id)
169 else {
170 return Vec::new();
171 };
172 let Some(instance) = child_group.repetitions.get(child_instance) else {
173 return Vec::new();
174 };
175 instance
176 .segments
177 .iter()
178 .enumerate()
179 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
180 .map(|(i, s)| to_owned(s, i as u32))
181 .collect()
182 }
183
184 fn extract_value_in_group(
185 &self,
186 segment_id: &str,
187 element_index: usize,
188 component_index: usize,
189 group_path: &[&str],
190 instance_index: usize,
191 ) -> Option<String> {
192 let instance = resolve_instance(&self.tree.groups, group_path, instance_index)?;
193 let seg = instance
194 .segments
195 .iter()
196 .find(|s| s.tag.eq_ignore_ascii_case(segment_id))?;
197 seg.elements
198 .get(element_index)?
199 .get(component_index)
200 .cloned()
201 }
202}
203
204fn resolve_group<'a>(groups: &'a [AssembledGroup], path: &[&str]) -> Option<&'a AssembledGroup> {
206 if path.is_empty() {
207 return None;
208 }
209 let group = groups.iter().find(|g| g.group_id == path[0])?;
210 if path.len() == 1 {
211 return Some(group);
212 }
213 let instance = group.repetitions.first()?;
215 resolve_group(&instance.child_groups, &path[1..])
216}
217
218fn resolve_instance_path<'a>(
220 groups: &'a [AssembledGroup],
221 path: &[(&str, usize)],
222) -> Option<&'a AssembledGroupInstance> {
223 let (&(group_id, index), rest) = path.split_first()?;
224 let instance = groups
225 .iter()
226 .find(|g| g.group_id == group_id)?
227 .repetitions
228 .get(index)?;
229 if rest.is_empty() {
230 Some(instance)
231 } else {
232 resolve_instance_path(&instance.child_groups, rest)
233 }
234}
235
236fn collect_subtree(
239 instance: &AssembledGroupInstance,
240 segment_id: &str,
241 out: &mut Vec<OwnedSegment>,
242) {
243 for (i, s) in instance.segments.iter().enumerate() {
244 if s.tag.eq_ignore_ascii_case(segment_id) {
245 out.push(to_owned(s, i as u32));
246 }
247 }
248 for group in &instance.child_groups {
249 for repetition in &group.repetitions {
250 collect_subtree(repetition, segment_id, out);
251 }
252 }
253}
254
255fn resolve_instance<'a>(
257 groups: &'a [AssembledGroup],
258 path: &[&str],
259 instance_index: usize,
260) -> Option<&'a AssembledGroupInstance> {
261 let group = resolve_group(groups, path)?;
262 group.repetitions.get(instance_index)
263}
264
265fn has_mig_in_instance(inst: &AssembledGroupInstance, mig_number: &str) -> bool {
268 if inst
269 .segments
270 .iter()
271 .any(|s| s.mig_number.as_deref() == Some(mig_number))
272 {
273 return true;
274 }
275 inst.child_groups.iter().any(|g| {
276 g.repetitions
277 .iter()
278 .any(|r| has_mig_in_instance(r, mig_number))
279 })
280}
281
282fn to_owned(seg: &AssembledSegment, index: u32) -> OwnedSegment {
285 OwnedSegment {
286 id: seg.tag.clone(),
287 elements: seg.elements.clone(),
288 segment_number: seg.segment_number.unwrap_or(index),
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::assembler::{
296 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
297 };
298 use mig_types::navigator::GroupNavigator;
299
300 fn make_seg(tag: &str, elements: Vec<Vec<&str>>) -> AssembledSegment {
301 AssembledSegment {
302 tag: tag.to_string(),
303 elements: elements
304 .into_iter()
305 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
306 .collect(),
307 mig_number: None,
308 segment_number: None,
309 }
310 }
311
312 fn tree_with_sg4_sg8() -> AssembledTree {
313 AssembledTree {
320 segments: vec![make_seg("UNH", vec![vec!["001"]])],
321 groups: vec![AssembledGroup {
322 group_id: "SG4".to_string(),
323 repetitions: vec![AssembledGroupInstance {
324 segments: vec![
325 make_seg("IDE", vec![vec!["24", "TX001"]]),
326 make_seg("STS", vec![vec!["E01"], vec![], vec!["A05"]]),
327 ],
328 child_groups: vec![AssembledGroup {
329 group_id: "SG8".to_string(),
330 repetitions: vec![
331 AssembledGroupInstance {
332 segments: vec![
333 make_seg("SEQ", vec![vec!["Z98"]]),
334 make_seg("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
335 ],
336 child_groups: vec![AssembledGroup {
337 group_id: "SG10".to_string(),
338 repetitions: vec![
339 AssembledGroupInstance {
340 segments: vec![
341 make_seg("CCI", vec![vec!["Z23"]]),
342 make_seg("CAV", vec![vec!["Z91", "value1"]]),
343 ],
344 child_groups: vec![],
345 entry_mig_number: None,
346 variant_mig_numbers: vec![],
347 skipped_segments: vec![],
348 skipped_positions: Vec::new(),
349 },
350 AssembledGroupInstance {
351 segments: vec![
352 make_seg("CCI", vec![vec![""]]),
353 make_seg("CAV", vec![vec!["", "value2"]]),
354 ],
355 child_groups: vec![],
356 entry_mig_number: None,
357 variant_mig_numbers: vec![],
358 skipped_segments: vec![],
359 skipped_positions: Vec::new(),
360 },
361 ],
362 }],
363 entry_mig_number: None,
364 variant_mig_numbers: vec![],
365 skipped_segments: vec![],
366 skipped_positions: Vec::new(),
367 },
368 AssembledGroupInstance {
369 segments: vec![
370 make_seg("SEQ", vec![vec!["Z01"]]),
371 make_seg("CCI", vec![vec![""], vec![], vec!["ZC0"]]),
372 ],
373 child_groups: vec![],
374 entry_mig_number: None,
375 variant_mig_numbers: vec![],
376 skipped_segments: vec![],
377 skipped_positions: Vec::new(),
378 },
379 ],
380 }],
381 entry_mig_number: None,
382 variant_mig_numbers: vec![],
383 skipped_segments: vec![],
384 skipped_positions: Vec::new(),
385 }],
386 }],
387 post_group_start: 1,
388 inter_group_segments: std::collections::BTreeMap::new(),
389 }
390 }
391
392 fn tree_with_two_sg4() -> AssembledTree {
395 fn instance(
396 segments: Vec<AssembledSegment>,
397 child_groups: Vec<AssembledGroup>,
398 ) -> AssembledGroupInstance {
399 AssembledGroupInstance {
400 segments,
401 child_groups,
402 entry_mig_number: None,
403 variant_mig_numbers: vec![],
404 skipped_segments: vec![],
405 skipped_positions: Vec::new(),
406 }
407 }
408 fn group(id: &str, repetitions: Vec<AssembledGroupInstance>) -> AssembledGroup {
409 AssembledGroup {
410 group_id: id.to_string(),
411 repetitions,
412 }
413 }
414 let sg10 = |code: &str| {
415 group(
416 "SG10",
417 vec![instance(
418 vec![make_seg("CCI", vec![vec![""], vec![], vec![code]])],
419 vec![],
420 )],
421 )
422 };
423 AssembledTree {
424 segments: vec![],
425 groups: vec![group(
426 "SG4",
427 vec![
428 instance(
429 vec![make_seg("IDE", vec![vec!["24", "TX0"]])],
430 vec![group(
431 "SG8",
432 vec![instance(
433 vec![make_seg("SEQ", vec![vec!["Z98"]])],
434 vec![sg10("Z23")],
435 )],
436 )],
437 ),
438 instance(
439 vec![make_seg("IDE", vec![vec!["24", "TX1"]])],
440 vec![group(
441 "SG8",
442 vec![
443 instance(vec![make_seg("SEQ", vec![vec!["Z01"]])], vec![]),
444 instance(
445 vec![make_seg("SEQ", vec![vec!["Z03"]])],
446 vec![sg10("E13")],
447 ),
448 ],
449 )],
450 ),
451 ],
452 )],
453 post_group_start: 0,
454 inter_group_segments: std::collections::BTreeMap::new(),
455 }
456 }
457
458 #[test]
459 fn an_instance_is_addressed_at_every_level() {
460 let tree = tree_with_two_sg4();
461 let nav = AssembledTreeNavigator::new(&tree);
462 let seq = |path: &[(&str, usize)]| -> Vec<String> {
463 nav.find_segments_in_instance("SEQ", path)
464 .iter()
465 .map(|s| s.get_element(0).to_string())
466 .collect()
467 };
468 assert_eq!(seq(&[("SG4", 0), ("SG8", 0)]), ["Z98"]);
469 assert_eq!(seq(&[("SG4", 1), ("SG8", 0)]), ["Z01"]);
470 assert_eq!(seq(&[("SG4", 1), ("SG8", 1)]), ["Z03"]);
471 assert!(seq(&[("SG4", 0), ("SG8", 1)]).is_empty());
472 }
473
474 #[test]
475 fn a_subtree_includes_the_nested_groups() {
476 let tree = tree_with_two_sg4();
477 let nav = AssembledTreeNavigator::new(&tree);
478 let cci = |path: &[(&str, usize)]| -> Vec<String> {
479 nav.find_segments_in_subtree("CCI", path)
480 .iter()
481 .map(|s| s.get_component(2, 0).to_string())
482 .collect()
483 };
484 assert_eq!(cci(&[("SG4", 1), ("SG8", 1)]), ["E13"]);
485 assert!(cci(&[("SG4", 1), ("SG8", 0)]).is_empty());
486 assert_eq!(cci(&[("SG4", 1)]), ["E13"], "the whole SG4");
487 assert!(
488 nav.find_segments_in_instance("CCI", &[("SG4", 1), ("SG8", 1)])
489 .is_empty(),
490 "the instance alone holds no CCI"
491 );
492 }
493
494 #[test]
495 fn test_find_in_sg8_instance_0() {
496 let tree = tree_with_sg4_sg8();
497 let nav = AssembledTreeNavigator::new(&tree);
498 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
499 assert_eq!(segs.len(), 1);
500 assert_eq!(segs[0].get_element(0), "Z98");
501 }
502
503 #[test]
504 fn test_find_in_sg8_instance_1() {
505 let tree = tree_with_sg4_sg8();
506 let nav = AssembledTreeNavigator::new(&tree);
507 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 1);
508 assert_eq!(segs.len(), 1);
509 assert_eq!(segs[0].get_element(0), "Z01");
510 }
511
512 #[test]
513 fn test_qualifier_in_group_scoped() {
514 let tree = tree_with_sg4_sg8();
515 let nav = AssembledTreeNavigator::new(&tree);
516 let segs = nav.find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 1);
517 assert_eq!(segs.len(), 1);
518 assert!(nav
520 .find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 0)
521 .is_empty());
522 }
523
524 #[test]
525 fn test_group_instance_count() {
526 let tree = tree_with_sg4_sg8();
527 let nav = AssembledTreeNavigator::new(&tree);
528 assert_eq!(nav.group_instance_count(&["SG4"]), 1);
529 assert_eq!(nav.group_instance_count(&["SG4", "SG8"]), 2);
530 assert_eq!(nav.group_instance_count(&["SG4", "SG5"]), 0);
531 }
532
533 #[test]
534 fn test_find_in_sg4_directly() {
535 let tree = tree_with_sg4_sg8();
536 let nav = AssembledTreeNavigator::new(&tree);
537 let segs = nav.find_segments_in_group("STS", &["SG4"], 0);
538 assert_eq!(segs.len(), 1);
539 }
540
541 #[test]
542 fn test_invalid_path_returns_empty() {
543 let tree = tree_with_sg4_sg8();
544 let nav = AssembledTreeNavigator::new(&tree);
545 assert!(nav.find_segments_in_group("SEQ", &["SG99"], 0).is_empty());
546 assert!(nav
547 .find_segments_in_group("SEQ", &["SG4", "SG8"], 99)
548 .is_empty());
549 assert!(nav.find_segments_in_group("SEQ", &[], 0).is_empty());
550 }
551
552 #[test]
553 fn test_child_group_instance_count() {
554 let tree = tree_with_sg4_sg8();
555 let nav = AssembledTreeNavigator::new(&tree);
556 assert_eq!(
558 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
559 2
560 );
561 assert_eq!(
563 nav.child_group_instance_count(&["SG4", "SG8"], 1, "SG10"),
564 0
565 );
566 assert_eq!(
568 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG12"),
569 0
570 );
571 }
572
573 #[test]
574 fn test_find_segments_in_child_group() {
575 let tree = tree_with_sg4_sg8();
576 let nav = AssembledTreeNavigator::new(&tree);
577 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0);
579 assert_eq!(segs.len(), 1);
580 assert_eq!(segs[0].get_element(0), "Z23");
581 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 1);
583 assert_eq!(segs.len(), 1);
584 assert_eq!(segs[0].get_element(0), "");
585 let segs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], 0, "SG10", 0);
587 assert_eq!(segs.len(), 1);
588 }
589
590 #[test]
591 fn test_child_group_invalid_path() {
592 let tree = tree_with_sg4_sg8();
593 let nav = AssembledTreeNavigator::new(&tree);
594 assert_eq!(nav.child_group_instance_count(&["SG99"], 0, "SG10"), 0);
596 assert!(nav
598 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 99, "SG10", 0)
599 .is_empty());
600 assert!(nav
602 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 99)
603 .is_empty());
604 }
605
606 #[test]
607 fn test_extract_value_in_group() {
608 let tree = tree_with_sg4_sg8();
609 let nav = AssembledTreeNavigator::new(&tree);
610 assert_eq!(
612 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
613 Some("Z98".to_string()),
614 );
615 assert_eq!(
617 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 1),
618 Some("Z01".to_string()),
619 );
620 }
621
622 #[test]
623 fn test_extract_value_missing() {
624 let tree = tree_with_sg4_sg8();
625 let nav = AssembledTreeNavigator::new(&tree);
626 assert_eq!(
628 nav.extract_value_in_group("LOC", 0, 0, &["SG4", "SG8"], 0),
629 None
630 );
631 assert_eq!(
633 nav.extract_value_in_group("SEQ", 5, 0, &["SG4", "SG8"], 0),
634 None
635 );
636 assert_eq!(
638 nav.extract_value_in_group("SEQ", 0, 5, &["SG4", "SG8"], 0),
639 None
640 );
641 assert_eq!(nav.extract_value_in_group("SEQ", 0, 0, &["SG99"], 0), None);
643 }
644}