use crate::error::ObfuskeyError;
#[derive(Clone)]
pub struct Alphabet {
chars: Vec<char>,
lookup: [u8; 256],
all_ascii: bool,
}
impl Alphabet {
pub fn new(alphabet: &str) -> Result<Self, ObfuskeyError> {
let chars: Vec<char> = alphabet.chars().collect();
if chars.len() < 2 {
return Err(ObfuskeyError::ValueError(
"Alphabet must contain at least 2 characters.".to_string(),
));
}
let mut seen = [false; 256];
let mut seen_set = std::collections::HashSet::new();
let all_ascii = chars.iter().all(|c| c.is_ascii());
for &c in &chars {
if all_ascii {
let b = c as u8;
if seen[b as usize] {
return Err(ObfuskeyError::DuplicateError);
}
seen[b as usize] = true;
} else if !seen_set.insert(c) {
return Err(ObfuskeyError::DuplicateError);
}
}
let mut lookup = [0xFFu8; 256];
if all_ascii && chars.len() <= 256 {
for (i, &c) in chars.iter().enumerate() {
lookup[c as usize] = i as u8;
}
}
Ok(Alphabet {
chars,
lookup,
all_ascii,
})
}
#[inline]
pub fn base(&self) -> usize {
self.chars.len()
}
#[inline]
pub fn char_at(&self, index: usize) -> char {
self.chars[index]
}
#[inline]
pub fn first_char(&self) -> char {
self.chars[0]
}
#[inline]
pub fn index_of(&self, c: char) -> Option<usize> {
if self.all_ascii && c.is_ascii() {
let idx = self.lookup[c as usize];
if idx == 0xFF {
None
} else {
Some(idx as usize)
}
} else {
self.chars.iter().position(|&x| x == c)
}
}
pub fn chars(&self) -> &[char] {
&self.chars
}
pub fn as_str(&self) -> String {
self.chars.iter().collect()
}
}
impl std::fmt::Debug for Alphabet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = self.as_str();
if s.len() > 20 {
write!(f, "Alphabet('{}...{}')", &s[..10], &s[s.len() - 5..])
} else {
write!(f, "Alphabet('{}')", s)
}
}
}