extern crate alloc;
use alloc::vec::Vec;
use rand_core::RngCore;
use crate::{VDChar, VDString};
use crate::vdchar::VDS_ALLOWED;
#[cfg_attr(docsrs, doc(cfg(feature = "generate")))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VDGeneratorError {
LengthExceedsUniqueSet {
requested: usize,
available: usize,
},
}
#[cfg_attr(docsrs, doc(cfg(feature = "generate")))]
pub struct VDGenerator {
len: usize,
no_adjacent_repeats: bool,
no_repeats: bool,
}
#[cfg_attr(docsrs, doc(cfg(feature = "generate")))]
impl VDGenerator {
pub fn new() -> Self {
Self {
len: 6,
no_adjacent_repeats: false,
no_repeats: false,
}
}
pub fn length(mut self, len: usize) -> Self {
self.len = len;
self
}
pub fn no_adjacent_repeats(mut self) -> Self {
self.no_adjacent_repeats = true;
self
}
pub fn no_repeats(mut self) -> Self {
self.no_repeats = true;
self
}
pub fn generate<R: RngCore + ?Sized>(
&self,
rng: &mut R,
) -> Result<VDString, VDGeneratorError> {
if self.no_repeats && self.len > VDS_ALLOWED.len() {
return Err(VDGeneratorError::LengthExceedsUniqueSet {
requested: self.len,
available: VDS_ALLOWED.len(),
});
}
let mut result = Vec::with_capacity(self.len);
if self.no_repeats {
let mut pool: Vec<VDChar> = (0..VDS_ALLOWED.len())
.map(|i| VDChar(i as u8))
.collect();
for i in 0..self.len {
let j = i + (rng.next_u32() as usize % (pool.len() - i));
pool.swap(i, j);
}
result.extend_from_slice(&pool[..self.len]);
if self.no_adjacent_repeats {
for _ in 0..self.len {
if result.windows(2).any(|w| w[0] == w[1]) {
result.rotate_left(1);
} else {
break;
}
}
}
return Ok(VDString::new(result));
}
let mut last: Option<VDChar> = None;
while result.len() < self.len {
let idx = (rng.next_u32() as usize) % VDS_ALLOWED.len();
let ch = VDChar(idx as u8);
if self.no_adjacent_repeats && last == Some(ch) {
continue;
}
result.push(ch);
last = Some(ch);
}
Ok(VDString::new(result))
}
}
#[cfg(test)]
#[cfg(feature = "generate")]
mod tests {
extern crate alloc;
use super::*;
use alloc::vec;
use rand::SeedableRng;
use rand::rngs::SmallRng;
fn seeded_rng() -> SmallRng {
SmallRng::seed_from_u64(42)
}
#[test]
fn generates_expected_length() {
let mut rng = seeded_rng();
let code = VDGenerator::new().length(8).generate(&mut rng).unwrap();
assert_eq!(code.len(), 8);
}
#[test]
fn no_adjacent_repeats_enabled() {
let mut rng = seeded_rng();
let code = VDGenerator::new()
.length(16)
.no_adjacent_repeats()
.generate(&mut rng)
.unwrap();
for pair in code.as_vdchars().windows(2) {
assert_ne!(pair[0], pair[1], "adjacent repeat found");
}
}
#[test]
fn no_repeats_enabled() {
let mut rng = seeded_rng();
let code = VDGenerator::new()
.length(16)
.no_repeats()
.generate(&mut rng)
.unwrap();
let mut seen = vec![];
for ch in code.as_vdchars() {
assert!(!seen.contains(ch), "repeat found: {:?}", ch);
seen.push(*ch);
}
}
#[test]
fn no_repeats_exceeds_allowed_panics() {
let mut rng = seeded_rng();
let result = VDGenerator::new()
.length(VDS_ALLOWED.len() + 1)
.no_repeats()
.generate(&mut rng);
assert!(matches!(
result,
Err(VDGeneratorError::LengthExceedsUniqueSet { .. })
));
}
#[test]
fn combined_flags_hold() {
let mut rng = seeded_rng();
let code = VDGenerator::new()
.length(12)
.no_adjacent_repeats()
.no_repeats()
.generate(&mut rng)
.unwrap();
let mut seen = vec![];
for (i, ch) in code.as_vdchars().iter().enumerate() {
if i > 0 {
assert_ne!(code[i - 1], *ch, "adjacent repeat");
}
assert!(!seen.contains(ch), "repeat");
seen.push(*ch);
}
}
}