1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use crate::common::permutation::all_permutations;
use crate::input::Input;

pub fn solve(input: &mut Input) -> Result<String, String> {
    let mut password = [b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h'];
    if input.is_part_one() {
        scramble(input.text, &mut password)?;
        Ok(password.iter().map(|&b| b as char).collect::<String>())
    } else {
        // fbgdceah
        let desired = [b'f', b'b', b'g', b'd', b'c', b'e', b'a', b'h'];
        let mut answer = None;
        all_permutations(&mut password, &mut |permutation| {
            let mut copy = [0, 0, 0, 0, 0, 0, 0, 0];
            copy.copy_from_slice(permutation);
            scramble(input.text, &mut copy)?;
            if copy == desired {
                answer = Some(permutation.iter().map(|&b| b as char).collect::<String>());
            }
            Ok(())
        })?;

        answer.ok_or_else(|| "No solution found".to_string())
    }
}

fn scramble(input: &str, password: &mut [u8]) -> Result<(), String> {
    let error_mapper = |_| "Invalid input";
    for line in input.lines() {
        let words = line.split(' ').collect::<Vec<_>>();
        match words[0] {
            "swap" => {
                if words[1] == "position" {
                    let x = words[2].parse::<usize>().map_err(error_mapper)?;
                    let y = words[5].parse::<usize>().map_err(error_mapper)?;
                    password.swap(x, y);
                } else {
                    // Swap letters
                    let x = words[2].as_bytes()[0];
                    let y = words[5].as_bytes()[0];
                    password.iter_mut().for_each(|c| {
                        let orig = *c;
                        *c = if orig == x {
                            y
                        } else if orig == y {
                            x
                        } else {
                            orig
                        };
                    });
                }
            }
            "rotate" => {
                let rotation = if words[1] == "based" {
                    let letter = words[6].as_bytes()[0];
                    if let Some((idx, _)) =
                        password.iter().enumerate().find(|&(_idx, &c)| c == letter)
                    {
                        ((1 + idx + usize::from(idx >= 4)) % password.len()) as i32
                    } else {
                        return Err(format!(
                            "Unable to find letter for rotation: '{}'",
                            letter as char
                        ));
                    }
                } else {
                    words[2].parse::<i32>().map_err(error_mapper)?
                        * if words[1] == "left" { -1 } else { 1 }
                };

                if rotation < 0 {
                    password.rotate_left((-rotation) as usize);
                } else {
                    password.rotate_right(rotation as usize);
                }
            }
            "reverse" => {
                let x = words[2].parse::<usize>().map_err(error_mapper)?;
                let y = words[4].parse::<usize>().map_err(error_mapper)?;
                password[x..(y + 1)].reverse();
            }
            "move" => {
                let x = words[2].parse::<usize>().map_err(error_mapper)?;
                let y = words[5].parse::<usize>().map_err(error_mapper)?;
                let mut buffer: Vec<u8> = password.to_vec();
                let removed_letter = buffer.remove(x);
                buffer.insert(y, removed_letter);
                password.clone_from_slice(&buffer);
            }
            _ => {
                return Err("Invalid input".to_string());
            }
        }
    }
    Ok(())
}

#[test]
pub fn tests() {
    use crate::input::{test_part_one, test_part_two};

    let real_input = include_str!("day21_input.txt");
    test_part_one!(real_input => "gcedfahb".to_string());
    test_part_two!(real_input => "hegbdcfa".to_string());
}