use crate::{Error, Result};
const ALPHABETS: [(&str, &str); 21] = [
("qwerty", "asdfqwerzxcvjklmiuopghtybn"),
("qwerty-homerow", "asdfjklgh"),
("qwerty-left-hand", "asdfqwerzcxv"),
("qwerty-right-hand", "jkluiopmyhn"),
("azerty", "qsdfazerwxcvjklmuiopghtybn"),
("azerty-homerow", "qsdfjkmgh"),
("azerty-left-hand", "qsdfazerwxcv"),
("azerty-right-hand", "jklmuiophyn"),
("qwertz", "asdfqweryxcvjkluiopmghtzbn"),
("qwertz-homerow", "asdfghjkl"),
("qwertz-left-hand", "asdfqweryxcv"),
("qwertz-right-hand", "jkluiopmhzn"),
("dvorak", "aoeuqjkxpyhtnsgcrlmwvzfidb"),
("dvorak-homerow", "aoeuhtnsid"),
("dvorak-left-hand", "aoeupqjkyix"),
("dvorak-right-hand", "htnsgcrlmwvz"),
("colemak", "arstqwfpzxcvneioluymdhgjbk"),
("colemak-homerow", "arstneiodh"),
("colemak-left-hand", "arstqwfpzxcv"),
("colemak-right-hand", "neioluymjhk"),
(
"longest",
"aoeuqjkxpyhtnsgcrlmwvzfidb-;,~<>'@!#$%^&*~1234567890",
),
];
pub fn parse_alphabet(src: &str) -> Result<Alphabet> {
let alphabet_pair = ALPHABETS.iter().find(|&(name, _letters)| name == &src);
match alphabet_pair {
Some((_name, letters)) => {
let letters = letters.replace(&['n', 'N', 'y', 'Y'][..], "");
Ok(Alphabet(letters))
}
None => Err(Error::UnknownAlphabet),
}
}
#[derive(Debug, Clone)]
pub struct Alphabet(pub String);
impl Alphabet {
pub fn make_hints(&self, n: usize) -> Vec<String> {
if self.0.len() >= n {
return self.0.chars().take(n).map(|c| c.to_string()).collect();
}
let letters: Vec<char> = if self.0.len().pow(2) >= n {
self.0.chars().collect()
} else {
let alt_alphabet = parse_alphabet("longest").unwrap();
alt_alphabet.0.chars().collect()
};
let mut lead = letters.clone();
let mut prev: Vec<String> = Vec::new();
loop {
if lead.len() + prev.len() >= n {
break;
}
if lead.is_empty() {
break;
}
let prefix = lead.pop().unwrap();
let gen: Vec<String> = letters
.iter()
.take(n - lead.len() - prev.len())
.map(|c| format!("{prefix}{c}"))
.collect();
prev.splice(..0, gen);
}
let lead: Vec<String> = lead.iter().map(|c| c.to_string()).collect();
let filler: Vec<String> = std::iter::repeat_n("", n - lead.len() - prev.len())
.map(|s| s.to_string())
.collect();
[lead, prev, filler].concat()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_hints() {
let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(3);
assert_eq!(hints, ["a", "b", "c"]);
}
#[test]
fn composed_hints() {
let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(6);
assert_eq!(hints, ["a", "b", "c", "da", "db", "dc"]);
}
#[test]
fn composed_hints_multiple() {
let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(8);
assert_eq!(hints, ["a", "b", "ca", "cb", "da", "db", "dc", "dd"]);
}
#[test]
fn composed_hints_max_2() {
let alphabet = Alphabet("ab".to_string());
let hints = alphabet.make_hints(4);
assert_eq!(hints, ["aa", "ab", "ba", "bb"]);
}
#[test]
fn composed_hints_max_4() {
let alphabet = Alphabet("abcd".to_string());
let hints = alphabet.make_hints(13);
assert_eq!(
hints,
["a", "ba", "bb", "bc", "bd", "ca", "cb", "cc", "cd", "da", "db", "dc", "dd"]
);
}
#[test]
fn hints_with_longest_alphabet() {
let alphabet = Alphabet("ab".to_string());
let hints = alphabet.make_hints(2500);
assert_eq!(hints.len(), 2500);
assert_eq!(&hints[..3], ["aa", "ao", "ae"]);
assert_eq!(&hints[2497..], ["08", "09", "00"]);
}
#[test]
fn hints_exceed_longest_alphabet() {
let alphabet = Alphabet("ab".to_string());
let hints = alphabet.make_hints(10000);
assert_eq!(hints.len(), 10000);
assert!(&hints[2500..].iter().all(|s| s.is_empty()));
}
}