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