use super::word_dictionary::WordDictionary;
#[derive(Debug, Clone)]
pub struct AlternatingWordDictionary {
dictionaries: Vec<WordDictionary>,
delimiter: String,
}
impl AlternatingWordDictionary {
pub fn new(dictionaries: Vec<WordDictionary>, delimiter: String) -> Self {
if dictionaries.is_empty() {
panic!("AlternatingWordDictionary requires at least one sub-dictionary");
}
for (i, dict) in dictionaries.iter().enumerate() {
if dict.base() != 256 {
panic!(
"Dictionary at index {} has {} words, but exactly 256 words are required for full byte coverage (0-255)",
i,
dict.base()
);
}
}
Self {
dictionaries,
delimiter,
}
}
pub fn dict_index(&self, byte_position: usize) -> usize {
byte_position % self.dictionaries.len()
}
pub fn dict_at(&self, position: usize) -> &WordDictionary {
&self.dictionaries[self.dict_index(position)]
}
pub fn encode_byte(&self, byte: u8, position: usize) -> Option<&str> {
self.dict_at(position).encode_word(byte as usize)
}
pub fn decode_word(&self, word: &str, position: usize) -> Option<u8> {
self.dict_at(position)
.decode_word(word)
.map(|idx| idx as u8)
}
pub fn delimiter(&self) -> &str {
&self.delimiter
}
pub fn num_dicts(&self) -> usize {
self.dictionaries.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_dictionaries() -> Vec<WordDictionary> {
let even_words: Vec<String> = (0..256).map(|i| format!("even{}", i)).collect();
let odd_words: Vec<String> = (0..256).map(|i| format!("odd{}", i)).collect();
let even = WordDictionary::builder().words(even_words).build().unwrap();
let odd = WordDictionary::builder().words(odd_words).build().unwrap();
vec![even, odd]
}
#[test]
fn test_dict_index() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.dict_index(0), 0);
assert_eq!(alternating.dict_index(1), 1);
assert_eq!(alternating.dict_index(2), 0);
assert_eq!(alternating.dict_index(3), 1);
}
#[test]
fn test_encode_byte() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.encode_byte(0, 0), Some("even0"));
assert_eq!(alternating.encode_byte(0, 1), Some("odd0"));
assert_eq!(alternating.encode_byte(1, 2), Some("even1"));
assert_eq!(alternating.encode_byte(1, 3), Some("odd1"));
}
#[test]
fn test_decode_word() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.decode_word("even0", 0), Some(0));
assert_eq!(alternating.decode_word("even1", 0), Some(1));
assert_eq!(alternating.decode_word("odd0", 1), Some(0));
assert_eq!(alternating.decode_word("odd1", 1), Some(1));
}
#[test]
fn test_decode_word_case_insensitive() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.decode_word("EVEN0", 0), Some(0));
assert_eq!(alternating.decode_word("EvEn0", 0), Some(0));
assert_eq!(alternating.decode_word("ODD0", 1), Some(0));
}
#[test]
fn test_decode_word_case_sensitive() {
let even_words: Vec<String> = (0..256).map(|i| format!("Even{}", i)).collect();
let odd_words: Vec<String> = (0..256).map(|i| format!("Odd{}", i)).collect();
let even = WordDictionary::builder()
.words(even_words)
.case_sensitive(true)
.build()
.unwrap();
let odd = WordDictionary::builder()
.words(odd_words)
.case_sensitive(true)
.build()
.unwrap();
let alternating = AlternatingWordDictionary::new(vec![even, odd], "-".to_string());
assert_eq!(alternating.decode_word("Even0", 0), Some(0));
assert_eq!(alternating.decode_word("even0", 0), None);
assert_eq!(alternating.decode_word("EVEN0", 0), None);
}
#[test]
fn test_delimiter() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.delimiter(), "-");
}
#[test]
fn test_num_dicts() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.num_dicts(), 2);
}
#[test]
fn test_encode_byte_all_values() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.encode_byte(0, 0), Some("even0"));
assert_eq!(alternating.encode_byte(128, 0), Some("even128"));
assert_eq!(alternating.encode_byte(255, 0), Some("even255"));
}
#[test]
fn test_decode_word_not_found() {
let dicts = create_test_dictionaries();
let alternating = AlternatingWordDictionary::new(dicts, "-".to_string());
assert_eq!(alternating.decode_word("unknown", 0), None);
assert_eq!(alternating.decode_word("unknown", 1), None);
}
#[test]
#[should_panic(expected = "AlternatingWordDictionary requires at least one sub-dictionary")]
fn test_empty_dictionaries_panics() {
AlternatingWordDictionary::new(vec![], "-".to_string());
}
#[test]
#[should_panic(expected = "has 4 words, but exactly 256 words are required")]
fn test_undersized_dictionary_panics() {
let even = WordDictionary::builder()
.words(vec!["aardvark", "absurd", "accrue", "acme"])
.build()
.unwrap();
let odd = WordDictionary::builder()
.words(vec!["adroitness", "adviser", "aftermath", "aggregate"])
.build()
.unwrap();
AlternatingWordDictionary::new(vec![even, odd], "-".to_string());
}
#[test]
fn test_valid_256_word_dictionaries() {
let even_words: Vec<String> = (0..256).map(|i| format!("even{}", i)).collect();
let odd_words: Vec<String> = (0..256).map(|i| format!("odd{}", i)).collect();
let even = WordDictionary::builder().words(even_words).build().unwrap();
let odd = WordDictionary::builder().words(odd_words).build().unwrap();
let alternating = AlternatingWordDictionary::new(vec![even, odd], "-".to_string());
assert_eq!(alternating.num_dicts(), 2);
}
}