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
use core::str::FromStr;

use crate::utils::{ConversionError, UNIT_LETTER_MAP};

use super::utils::{Letter, LetterMapping};

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
/// The Enigma Plugboard or Steckerbrett
pub struct Plugboard {
    steckerverbindungen: LetterMapping,
}

impl Default for Plugboard {
    fn default() -> Self {
        Plugboard {
            steckerverbindungen: UNIT_LETTER_MAP,
        }
    }
}

impl Plugboard {
    /// Create a new `Plugboard` from an array of `Letter` pairs that represent cable connections
    #[must_use]
    pub fn new(pairs: &[(Letter, Letter)]) -> Self {
        let mut new = Plugboard::default();
        pairs.iter().for_each(|&pair| new.add_cable(pair));
        new
    }

    /// Resets the value of each `Letter` in the pair.
    /// Equivalent to the logical removal of a cable iff the pair is connected
    pub fn remove_cable(&mut self, pair: (Letter, Letter)) {
        self.steckerverbindungen[pair.0] = pair.0;
        self.steckerverbindungen[pair.1] = pair.1;
    }

    /// Set the value of each `Letter` in the pair to the other.
    /// Equivalent to the logical addition of a cable iff neither value
    /// is already connected to another value.
    pub fn add_cable(&mut self, pair: (Letter, Letter)) {
        debug!("adding {:?} and {:?}", pair.0, pair.1);
        self.steckerverbindungen[pair.0] = pair.1;
        self.steckerverbindungen[pair.1] = pair.0;
    }
}

impl FromStr for Plugboard {
    type Err = ConversionError;
    /// Convert a string of characters (optionally delimited with `,`, `-` or ` `) into a Plugboard
    fn from_str(string: &str) -> Result<Plugboard, Self::Err> {
        let mut plugboard = Plugboard::new(&[]);
        let mut a = string
            .chars()
            .filter(|x| ![',', '-', ' '].contains(x))
            .map(Letter::try_from);
        while let Some(x) = a.next() {
            if let Some(y) = a.next() {
                plugboard.add_cable((x?, y?));
            }
        }
        debug!("{plugboard:?}");
        Ok(plugboard)
    }
}

impl core::ops::Index<Letter> for Plugboard {
    type Output = Letter;

    fn index(&self, index: Letter) -> &Self::Output {
        &self.steckerverbindungen[index]
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new() {
        let plugboard = Plugboard::new(&[(Letter::A, Letter::B), (Letter::X, Letter::Y)]);
        assert_eq!(plugboard[Letter::A], Letter::B);
        assert_eq!(plugboard[Letter::B], Letter::A);
        assert_eq!(plugboard[Letter::X], Letter::Y);
        assert_eq!(plugboard[Letter::G], Letter::G);
    }

    #[test]
    fn test_string_to_plugboard() {
        let plugboard = Plugboard::from_str("EN,IG,MA").unwrap();
        assert_eq!(plugboard[Letter::N], Letter::E);
        assert_eq!(plugboard[Letter::E], Letter::N);
    }
}