use std::iter;
use itertools::Itertools;
use ordered_float::OrderedFloat;
use rayon::prelude::*;
use super::CharIndex;
use super::enigma::Enigma;
use constants::MAX_PLUGS;
use fitness::{IoC, Bigram, Quadgram, FitnessFn};
lazy_static! {
static ref ALPHAS: Vec<String> = {
iproduct!('A'..='Z', 'A'..='Z', 'A'..='Z')
.map(|(a, b, c)| [a, b, c].iter().collect())
.collect()
};
static ref ROTORS: Vec<String> = {
('1'..='5').permutations(3)
.map(|p| p.iter().collect())
.collect()
};
}
pub fn decrypt(msg: &str) -> (String, Enigma) {
let mut enigma;
enigma = guess_rotor_and_first_key(msg, IoC::score);
enigma = guess_key_and_ring(msg, Bigram::score, enigma);
enigma = guess_plugboard(msg, Quadgram::score, enigma);
(enigma.encrypt(msg), enigma)
}
type ScoreFn = fn(&str) -> f64;
fn guess_rotor_and_first_key(msg: &str, score_fn: ScoreFn) -> Enigma {
let (rotor, key) = iproduct!(ROTORS.iter(), ALPHAS.iter())
.collect::<Vec<_>>()
.into_par_iter()
.max_by_key(|&(rotor, key)| {
let mut enigma = Enigma::new(rotor, key, "AAA", 'B', "");
OrderedFloat(score_fn(&enigma.encrypt(msg)))
}).unwrap();
Enigma::new(rotor, key, "AAA", 'B', "")
}
fn guess_key_and_ring(msg: &str, score_fn: ScoreFn, enigma: Enigma) -> Enigma {
let rotor = enigma.rotor_list();
let first_key = enigma.key_settings().chars().nth(0).unwrap();
let key_offset = first_key.index() * 676;
let (key, ring) = iproduct!(key_offset..(key_offset + 676), 0..676)
.collect::<Vec<_>>()
.into_par_iter()
.map(|(key_idx, ring_idx)| (&ALPHAS[key_idx], &ALPHAS[ring_idx]))
.max_by_key(|&(key, ring)| {
let mut enigma = Enigma::new(&rotor, key, ring, 'B', "");
OrderedFloat(score_fn(&enigma.encrypt(msg)))
}).unwrap();
Enigma::new(&rotor, key, ring, 'B', "")
}
fn guess_plugboard(msg: &str, score_fn: ScoreFn, enigma: Enigma) -> Enigma {
let rotor = enigma.rotor_list();
let key = enigma.key_settings();
let ring = enigma.ring_settings();
let mut curr_plugboard = Vec::new();
let mut plug_pool: Vec<char> = ('A'..='Z').collect();
let mut best_score = score_fn(&msg);
for _ in 0..MAX_PLUGS {
let pool = plug_pool.clone();
let combos: Vec<String> = pool
.into_iter()
.combinations(2)
.map(|p| p.iter().collect())
.collect();
let (score, plug) = combos.par_iter()
.map(|plug| {
let plugboard = curr_plugboard
.iter()
.chain(iter::once(plug))
.join(" ");
let mut enigma = Enigma::new(&rotor, &key, &ring, 'B', &plugboard);
(OrderedFloat(score_fn(&enigma.encrypt(&msg))), plug)
}).max().unwrap();
if *score > best_score {
best_score = *score;
curr_plugboard.push(plug.to_string());
plug_pool.retain(|&c| !plug.chars().any(|p| p == c));
} else {
break;
}
}
Enigma::new(&rotor, &key, &ring, 'B', &curr_plugboard.iter().join(" "))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn static_vars() {
assert_eq!(ALPHAS.len(), 26 * 26 * 26);
assert_eq!(ROTORS.len(), 120 / 2); }
}