1use crate::types::Position;
12
13type Dimensions = (usize, usize, usize);
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Direction {
18 Positive,
19 Negative,
20}
21
22#[derive(Debug, Clone, PartialEq)]
24pub enum PatternElement {
25 Wildcard,
27 Skip,
29 Exclude,
31 Exact(i32),
33 DirectionExclusive(Direction),
35 DirectionInclusive(Direction),
37 Offset(i32),
39 Range(i32, i32),
41}
42
43impl PatternElement {
44 pub fn from_value(value: &str) -> Self {
46 match value {
47 "*" => PatternElement::Wildcard,
48 "?" => PatternElement::Skip,
49 "!" => PatternElement::Exclude,
50 "?+" => PatternElement::DirectionExclusive(Direction::Positive),
51 "?-" => PatternElement::DirectionExclusive(Direction::Negative),
52 "?+=" => PatternElement::DirectionInclusive(Direction::Positive),
53 "?-=" => PatternElement::DirectionInclusive(Direction::Negative),
54 _ => {
55 if let Some(range_str) = Self::try_parse_range(value) {
56 return range_str;
57 }
58 if let Some(offset) = Self::try_parse_offset(value) {
59 return offset;
60 }
61 if let Ok(num) = value.parse::<i32>() {
62 PatternElement::Exact(num)
63 } else {
64 PatternElement::Wildcard
65 }
66 }
67 }
68 }
69
70 pub fn from_int(value: i32) -> Self {
72 match value {
73 -1 => PatternElement::Wildcard,
74 -2 => PatternElement::Skip,
75 -3 => PatternElement::Exclude,
76 -10 => PatternElement::DirectionExclusive(Direction::Positive),
77 -11 => PatternElement::DirectionExclusive(Direction::Negative),
78 -12 => PatternElement::DirectionInclusive(Direction::Positive),
79 -13 => PatternElement::DirectionInclusive(Direction::Negative),
80 _ => PatternElement::Exact(value),
81 }
82 }
83
84 fn try_parse_range(value: &str) -> Option<PatternElement> {
87 let parts: Vec<&str> = value.split(':').collect();
88 if parts.len() != 2 {
89 return None;
90 }
91 let lo = Self::extract_relative_offset(parts[0])?;
92 let hi = Self::extract_relative_offset(parts[1])?;
93 Some(PatternElement::Range(lo, hi))
94 }
95
96 fn try_parse_offset(value: &str) -> Option<PatternElement> {
98 let offset = Self::extract_relative_offset(value)?;
99 Some(PatternElement::Offset(offset))
100 }
101
102 fn extract_relative_offset(s: &str) -> Option<i32> {
104 if !s.starts_with('?') {
105 return None;
106 }
107 let rest = &s[1..];
108 if rest.is_empty() {
109 return None;
110 }
111 if rest == "+" || rest == "-" || rest == "+=" || rest == "-=" {
112 return None;
113 }
114 rest.parse::<i32>().ok()
115 }
116}
117
118pub type Pattern3D = (PatternElement, PatternElement, PatternElement);
120
121pub fn match_pattern_element(element: &PatternElement, coordinate: i32, src_coord: i32) -> bool {
123 match element {
124 PatternElement::Wildcard => true,
125 PatternElement::Skip => coordinate == src_coord,
126 PatternElement::Exclude => coordinate != src_coord,
127 PatternElement::Exact(val) => coordinate == *val,
128 PatternElement::DirectionExclusive(Direction::Positive) => coordinate > src_coord,
129 PatternElement::DirectionExclusive(Direction::Negative) => coordinate < src_coord,
130 PatternElement::DirectionInclusive(Direction::Positive) => coordinate >= src_coord,
131 PatternElement::DirectionInclusive(Direction::Negative) => coordinate <= src_coord,
132 PatternElement::Offset(off) => coordinate == src_coord + off,
133 PatternElement::Range(lo, hi) => {
134 coordinate >= src_coord + lo && coordinate <= src_coord + hi
135 }
136 }
137}
138
139fn expand_axis(element: &PatternElement, src_coord: u32, dim: usize) -> Vec<u32> {
141 match element {
142 PatternElement::Wildcard => (0..dim as u32).collect(),
143 PatternElement::Skip => {
144 if (src_coord as usize) < dim {
145 vec![src_coord]
146 } else {
147 vec![]
148 }
149 }
150 PatternElement::Exclude => (0..dim as u32).filter(|&c| c != src_coord).collect(),
151 PatternElement::Exact(val) => {
152 if *val >= 0 && (*val as usize) < dim {
153 vec![*val as u32]
154 } else {
155 vec![]
156 }
157 }
158 PatternElement::DirectionExclusive(Direction::Positive) => {
159 ((src_coord + 1)..dim as u32).collect()
160 }
161 PatternElement::DirectionExclusive(Direction::Negative) => (0..src_coord).collect(),
162 PatternElement::DirectionInclusive(Direction::Positive) => {
163 (src_coord..dim as u32).collect()
164 }
165 PatternElement::DirectionInclusive(Direction::Negative) => {
166 (0..=src_coord).filter(|&c| (c as usize) < dim).collect()
167 }
168 PatternElement::Offset(off) => {
169 let target = src_coord as i32 + off;
170 if target >= 0 && (target as usize) < dim {
171 vec![target as u32]
172 } else {
173 vec![]
174 }
175 }
176 PatternElement::Range(lo, hi) => {
177 let start = (src_coord as i32 + lo).max(0) as u32;
178 let end_exclusive = ((src_coord as i32 + hi) + 1).min(dim as i32) as u32;
179 if start >= end_exclusive {
180 vec![]
181 } else {
182 (start..end_exclusive).collect()
183 }
184 }
185 }
186}
187
188pub fn find_destination_coordinates(
190 dst_dimensions: Dimensions,
191 src_coordinate: Position,
192 _src_pattern: &Pattern3D,
193 dst_pattern: &Pattern3D,
194) -> Vec<Position> {
195 let (dst_width, dst_height, dst_depth) = dst_dimensions;
196 let (src_x, src_y, src_z) = src_coordinate;
197
198 let x_range = expand_axis(&dst_pattern.0, src_x, dst_width);
199 let y_range = expand_axis(&dst_pattern.1, src_y, dst_height);
200 let z_range = expand_axis(&dst_pattern.2, src_z, dst_depth);
201
202 let mut results = Vec::with_capacity(x_range.len() * y_range.len() * z_range.len());
203 for x in &x_range {
204 for y in &y_range {
205 for z in &z_range {
206 results.push((*x, *y, *z));
207 }
208 }
209 }
210
211 results
212}
213
214pub fn find_source_coordinates(
218 src_pattern: &Pattern3D,
219 src_dimensions: Dimensions,
220) -> Vec<Position> {
221 let (src_width, src_height, src_depth) = src_dimensions;
222
223 let x_range: Vec<u32> = match &src_pattern.0 {
224 PatternElement::Wildcard => (0..src_width as u32).collect(),
225 PatternElement::Exact(val) => {
226 if *val >= 0 && (*val as usize) < src_width {
227 vec![*val as u32]
228 } else {
229 vec![]
230 }
231 }
232 _ => (0..src_width as u32).collect(),
233 };
234
235 let y_range: Vec<u32> = match &src_pattern.1 {
236 PatternElement::Wildcard => (0..src_height as u32).collect(),
237 PatternElement::Exact(val) => {
238 if *val >= 0 && (*val as usize) < src_height {
239 vec![*val as u32]
240 } else {
241 vec![]
242 }
243 }
244 _ => (0..src_height as u32).collect(),
245 };
246
247 let z_range: Vec<u32> = match &src_pattern.2 {
248 PatternElement::Wildcard => (0..src_depth as u32).collect(),
249 PatternElement::Exact(val) => {
250 if *val >= 0 && (*val as usize) < src_depth {
251 vec![*val as u32]
252 } else {
253 vec![]
254 }
255 }
256 _ => (0..src_depth as u32).collect(),
257 };
258
259 let mut results = Vec::with_capacity(x_range.len() * y_range.len() * z_range.len());
260 for x in &x_range {
261 for y in &y_range {
262 for z in &z_range {
263 results.push((*x, *y, *z));
264 }
265 }
266 }
267
268 results
269}
270
271pub fn match_patterns_batch(
273 src_coordinate: Position,
274 patterns: &[(Pattern3D, Pattern3D)],
275 _src_dimensions: Dimensions,
276 dst_dimensions: Dimensions,
277) -> Vec<Position> {
278 let mut all_results = Vec::new();
279
280 for (src_pattern, dst_pattern) in patterns {
281 let (src_x, src_y, src_z) = src_coordinate;
282
283 let x_match = match &src_pattern.0 {
284 PatternElement::Wildcard => true,
285 PatternElement::Exact(val) => src_x == (*val as u32),
286 _ => true,
287 };
288
289 let y_match = match &src_pattern.1 {
290 PatternElement::Wildcard => true,
291 PatternElement::Exact(val) => src_y == (*val as u32),
292 _ => true,
293 };
294
295 let z_match = match &src_pattern.2 {
296 PatternElement::Wildcard => true,
297 PatternElement::Exact(val) => src_z == (*val as u32),
298 _ => true,
299 };
300
301 if x_match && y_match && z_match {
302 let mut results = find_destination_coordinates(
303 dst_dimensions,
304 src_coordinate,
305 src_pattern,
306 dst_pattern,
307 );
308 all_results.append(&mut results);
309 }
310 }
311
312 all_results.sort_unstable();
313 all_results.dedup();
314
315 all_results
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_wildcard_pattern() {
324 let src_pattern = (
325 PatternElement::Wildcard,
326 PatternElement::Wildcard,
327 PatternElement::Exact(0),
328 );
329 let dst_pattern = (
330 PatternElement::Skip,
331 PatternElement::Skip,
332 PatternElement::Exact(1),
333 );
334
335 let results =
336 find_destination_coordinates((10, 10, 10), (5, 5, 0), &src_pattern, &dst_pattern);
337
338 assert_eq!(results.len(), 1);
339 assert_eq!(results[0], (5, 5, 1));
340 }
341
342 #[test]
343 fn test_exact_pattern() {
344 let src_pattern = (
345 PatternElement::Exact(0),
346 PatternElement::Exact(0),
347 PatternElement::Exact(0),
348 );
349 let dst_pattern = (
350 PatternElement::Exact(1),
351 PatternElement::Exact(2),
352 PatternElement::Exact(3),
353 );
354
355 let results =
356 find_destination_coordinates((10, 10, 10), (0, 0, 0), &src_pattern, &dst_pattern);
357
358 assert_eq!(results.len(), 1);
359 assert_eq!(results[0], (1, 2, 3));
360 }
361
362 #[test]
363 fn test_exclude_pattern() {
364 let src_pattern = (
365 PatternElement::Wildcard,
366 PatternElement::Wildcard,
367 PatternElement::Wildcard,
368 );
369 let dst_pattern = (
370 PatternElement::Exclude,
371 PatternElement::Exact(0),
372 PatternElement::Exact(0),
373 );
374
375 let results =
376 find_destination_coordinates((3, 1, 1), (1, 0, 0), &src_pattern, &dst_pattern);
377
378 assert_eq!(results.len(), 2);
379 assert!(results.contains(&(0, 0, 0)));
380 assert!(results.contains(&(2, 0, 0)));
381 }
382
383 #[test]
384 fn test_direction_positive_exclusive() {
385 let src_pattern = (
386 PatternElement::Wildcard,
387 PatternElement::Wildcard,
388 PatternElement::Wildcard,
389 );
390 let dst_pattern = (
391 PatternElement::DirectionExclusive(Direction::Positive),
392 PatternElement::Skip,
393 PatternElement::Skip,
394 );
395
396 let results =
397 find_destination_coordinates((8, 4, 2), (3, 1, 0), &src_pattern, &dst_pattern);
398
399 assert_eq!(results, vec![(4, 1, 0), (5, 1, 0), (6, 1, 0), (7, 1, 0)]);
400 }
401
402 #[test]
403 fn test_direction_negative_exclusive() {
404 let src_pattern = (
405 PatternElement::Wildcard,
406 PatternElement::Wildcard,
407 PatternElement::Wildcard,
408 );
409 let dst_pattern = (
410 PatternElement::DirectionExclusive(Direction::Negative),
411 PatternElement::Skip,
412 PatternElement::Skip,
413 );
414
415 let results =
416 find_destination_coordinates((8, 4, 2), (3, 1, 0), &src_pattern, &dst_pattern);
417
418 assert_eq!(results, vec![(0, 1, 0), (1, 1, 0), (2, 1, 0)]);
419 }
420
421 #[test]
422 fn test_direction_positive_inclusive() {
423 let dst_pattern = (
424 PatternElement::DirectionInclusive(Direction::Positive),
425 PatternElement::Skip,
426 PatternElement::Skip,
427 );
428 let src_pattern = (
429 PatternElement::Wildcard,
430 PatternElement::Wildcard,
431 PatternElement::Wildcard,
432 );
433
434 let results =
435 find_destination_coordinates((6, 3, 1), (2, 1, 0), &src_pattern, &dst_pattern);
436
437 assert_eq!(results, vec![(2, 1, 0), (3, 1, 0), (4, 1, 0), (5, 1, 0)]);
438 }
439
440 #[test]
441 fn test_direction_negative_inclusive() {
442 let dst_pattern = (
443 PatternElement::DirectionInclusive(Direction::Negative),
444 PatternElement::Skip,
445 PatternElement::Skip,
446 );
447 let src_pattern = (
448 PatternElement::Wildcard,
449 PatternElement::Wildcard,
450 PatternElement::Wildcard,
451 );
452
453 let results =
454 find_destination_coordinates((6, 3, 1), (2, 1, 0), &src_pattern, &dst_pattern);
455
456 assert_eq!(results, vec![(0, 1, 0), (1, 1, 0), (2, 1, 0)]);
457 }
458
459 #[test]
460 fn test_offset_positive() {
461 let dst_pattern = (
462 PatternElement::Offset(2),
463 PatternElement::Skip,
464 PatternElement::Skip,
465 );
466 let src_pattern = (
467 PatternElement::Wildcard,
468 PatternElement::Wildcard,
469 PatternElement::Wildcard,
470 );
471
472 let results =
473 find_destination_coordinates((10, 10, 10), (3, 5, 7), &src_pattern, &dst_pattern);
474
475 assert_eq!(results, vec![(5, 5, 7)]);
476 }
477
478 #[test]
479 fn test_offset_negative() {
480 let dst_pattern = (
481 PatternElement::Offset(-2),
482 PatternElement::Skip,
483 PatternElement::Skip,
484 );
485 let src_pattern = (
486 PatternElement::Wildcard,
487 PatternElement::Wildcard,
488 PatternElement::Wildcard,
489 );
490
491 let results =
492 find_destination_coordinates((10, 10, 10), (3, 5, 7), &src_pattern, &dst_pattern);
493
494 assert_eq!(results, vec![(1, 5, 7)]);
495 }
496
497 #[test]
498 fn test_offset_out_of_bounds() {
499 let dst_pattern = (
500 PatternElement::Offset(5),
501 PatternElement::Skip,
502 PatternElement::Skip,
503 );
504 let src_pattern = (
505 PatternElement::Wildcard,
506 PatternElement::Wildcard,
507 PatternElement::Wildcard,
508 );
509
510 let results =
511 find_destination_coordinates((6, 5, 5), (4, 2, 2), &src_pattern, &dst_pattern);
512
513 assert!(results.is_empty());
514 }
515
516 #[test]
517 fn test_range_symmetric() {
518 let dst_pattern = (
519 PatternElement::Range(-1, 1),
520 PatternElement::Range(-1, 1),
521 PatternElement::Skip,
522 );
523 let src_pattern = (
524 PatternElement::Wildcard,
525 PatternElement::Wildcard,
526 PatternElement::Wildcard,
527 );
528
529 let results =
530 find_destination_coordinates((10, 10, 5), (5, 5, 2), &src_pattern, &dst_pattern);
531
532 assert_eq!(results.len(), 9); assert!(results.contains(&(4, 4, 2)));
534 assert!(results.contains(&(5, 5, 2)));
535 assert!(results.contains(&(6, 6, 2)));
536 }
537
538 #[test]
539 fn test_range_clamped_at_boundary() {
540 let dst_pattern = (
541 PatternElement::Range(-3, 3),
542 PatternElement::Skip,
543 PatternElement::Skip,
544 );
545 let src_pattern = (
546 PatternElement::Wildcard,
547 PatternElement::Wildcard,
548 PatternElement::Wildcard,
549 );
550
551 let results =
553 find_destination_coordinates((8, 1, 1), (1, 0, 0), &src_pattern, &dst_pattern);
554
555 assert_eq!(
556 results,
557 vec![(0, 0, 0), (1, 0, 0), (2, 0, 0), (3, 0, 0), (4, 0, 0)]
558 );
559 }
560
561 #[test]
562 fn test_range_forward_only() {
563 let dst_pattern = (
564 PatternElement::Range(1, 3),
565 PatternElement::Skip,
566 PatternElement::Skip,
567 );
568 let src_pattern = (
569 PatternElement::Wildcard,
570 PatternElement::Wildcard,
571 PatternElement::Wildcard,
572 );
573
574 let results =
575 find_destination_coordinates((10, 1, 1), (2, 0, 0), &src_pattern, &dst_pattern);
576
577 assert_eq!(results, vec![(3, 0, 0), (4, 0, 0), (5, 0, 0)]);
578 }
579
580 #[test]
581 fn test_direction_at_edge() {
582 let dst_pattern = (
583 PatternElement::DirectionExclusive(Direction::Negative),
584 PatternElement::Skip,
585 PatternElement::Skip,
586 );
587 let src_pattern = (
588 PatternElement::Wildcard,
589 PatternElement::Wildcard,
590 PatternElement::Wildcard,
591 );
592
593 let results =
595 find_destination_coordinates((10, 1, 1), (0, 0, 0), &src_pattern, &dst_pattern);
596
597 assert!(results.is_empty());
598 }
599
600 #[test]
601 fn test_from_value_new_patterns() {
602 assert_eq!(
603 PatternElement::from_value("?+"),
604 PatternElement::DirectionExclusive(Direction::Positive)
605 );
606 assert_eq!(
607 PatternElement::from_value("?-"),
608 PatternElement::DirectionExclusive(Direction::Negative)
609 );
610 assert_eq!(
611 PatternElement::from_value("?+="),
612 PatternElement::DirectionInclusive(Direction::Positive)
613 );
614 assert_eq!(
615 PatternElement::from_value("?-="),
616 PatternElement::DirectionInclusive(Direction::Negative)
617 );
618 assert_eq!(PatternElement::from_value("?+3"), PatternElement::Offset(3));
619 assert_eq!(
620 PatternElement::from_value("?-2"),
621 PatternElement::Offset(-2)
622 );
623 assert_eq!(
624 PatternElement::from_value("?-1:?+1"),
625 PatternElement::Range(-1, 1)
626 );
627 assert_eq!(
628 PatternElement::from_value("?+2:?+5"),
629 PatternElement::Range(2, 5)
630 );
631 }
632
633 #[test]
634 fn test_from_value_backward_compat() {
635 assert_eq!(PatternElement::from_value("*"), PatternElement::Wildcard);
636 assert_eq!(PatternElement::from_value("?"), PatternElement::Skip);
637 assert_eq!(PatternElement::from_value("!"), PatternElement::Exclude);
638 assert_eq!(PatternElement::from_value("7"), PatternElement::Exact(7));
639 }
640
641 #[test]
642 fn test_from_int_new_encodings() {
643 assert_eq!(
644 PatternElement::from_int(-10),
645 PatternElement::DirectionExclusive(Direction::Positive)
646 );
647 assert_eq!(
648 PatternElement::from_int(-11),
649 PatternElement::DirectionExclusive(Direction::Negative)
650 );
651 assert_eq!(
652 PatternElement::from_int(-12),
653 PatternElement::DirectionInclusive(Direction::Positive)
654 );
655 assert_eq!(
656 PatternElement::from_int(-13),
657 PatternElement::DirectionInclusive(Direction::Negative)
658 );
659 }
660
661 #[test]
662 fn test_batch_with_directional() {
663 let patterns = vec![(
664 (
665 PatternElement::Wildcard,
666 PatternElement::Wildcard,
667 PatternElement::Wildcard,
668 ),
669 (
670 PatternElement::DirectionExclusive(Direction::Positive),
671 PatternElement::Skip,
672 PatternElement::Skip,
673 ),
674 )];
675
676 let results = match_patterns_batch((3, 0, 0), &patterns, (8, 1, 1), (8, 1, 1));
677
678 assert_eq!(results, vec![(4, 0, 0), (5, 0, 0), (6, 0, 0), (7, 0, 0)]);
679 }
680}