use rand::{rng, Rng, seq::IndexedRandom};
use strum::IntoEnumIterator;
use crate::{hkid_check_digit::calculate_check_digit, hkid_prefix::HKIDPrefix};
fn random_uppercase_letter<R: Rng + ?Sized>(rng: &mut R) -> char {
rng.random_range(b'A'..=b'Z') as char
}
fn random_known_prefix<R: Rng>(rng: &mut R) -> Option<String> {
let valid_prefixes = HKIDPrefix::iter()
.filter(HKIDPrefix::is_known)
.map(|variant| variant.as_str())
.collect::<Vec<String>>();
valid_prefixes.choose(rng).cloned()
}
fn random_prefix<R: Rng>(rng: &mut R) -> String {
let len = if rng.random_bool(0.5) { 1 } else { 2 };
(0..len).map(|_| random_uppercase_letter(rng))
.collect()
}
pub fn generate_hkid(prefix: Option<&str>, must_exist_in_enum: bool) -> Result<String, String> {
let mut rng = rng();
let prefix_str = match (prefix, must_exist_in_enum) {
(Some(px), true) => {
let parsed_prefix = HKIDPrefix::parse(px);
if !parsed_prefix.is_known() {
return Err(format!("Prefix '{px}' is not recognized"));
}
parsed_prefix.as_str()
}
(Some(px), false) => {
HKIDPrefix::parse(px).as_str()
}
(None, true) => {
random_known_prefix(&mut rng)
.ok_or_else(|| "No valid prefixes in HKIDPrefix enum".to_string())?
.to_string()
}
(None, false) => random_prefix(&mut rng),
};
let digits = (0..6).map(|_| rng.random_range(0..10).to_string()).collect::<String>();
let hkid_body = format!("{prefix_str}{digits}");
let check_digit = calculate_check_digit(&hkid_body).ok_or("Failed to calculate check digit")?;
Ok(format!("{hkid_body}({check_digit})"))
}
#[cfg(test)]
mod tests {
use rand::rng;
use super::{generate_hkid, random_uppercase_letter, random_prefix, random_known_prefix, HKIDPrefix};
#[test]
fn test_random_uppercase_letter_range() {
let mut rng = rng();
for _ in 0..100 {
let c = random_uppercase_letter(&mut rng);
assert!(
c.is_ascii_uppercase(),
"Generated char '{}' is not ASCII uppercase", c
);
assert!(
('A'..='Z').contains(&c),
"Generated char '{}' is not in 'A'..='Z'", c
);
}
}
#[test]
fn test_random_prefix_length_and_case() {
let mut rng = rng();
for _ in 0..100 {
let prefix = random_prefix(&mut rng);
assert!(
prefix.len() == 1 || prefix.len() == 2,
"Prefix length should be 1 or 2, got '{}'", prefix
);
assert!(
prefix.chars().all(|c| c.is_ascii_uppercase()),
"Prefix '{}' should be all uppercase", prefix
);
}
}
#[test]
fn test_random_known_prefix_is_known() {
let mut rng = rng();
for _ in 0..20 {
if let Some(prefix) = random_known_prefix(&mut rng) {
let parsed = HKIDPrefix::parse(&prefix);
assert!(parsed.is_known(), "random_known_prefix produced unknown prefix: {}", prefix);
} else {
}
}
}
fn is_valid_format(hkid: &str) -> bool {
let parts: Vec<_> = hkid.split(['(', ')']).collect();
if parts.len() != 3 {
return false;
}
let prefix_digits = parts[0];
let check_digit = parts[1];
if check_digit.len() != 1 {
return false;
}
let prefix_len = prefix_digits.len();
if prefix_len < 7 || prefix_len > 8 {
return false;
}
prefix_digits
.chars()
.rev()
.take(6)
.all(|c| c.is_ascii_digit())
}
#[test]
fn test_generate_hkid_with_known_prefix() {
let prefix = "A";
let result = generate_hkid(Some(prefix), true);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(hkid.starts_with(prefix));
assert!(is_valid_format(&hkid));
}
#[test]
fn test_generate_hkid_with_two_letter_known_prefix() {
let prefix = "WX";
let result = generate_hkid(Some(prefix), true);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(hkid.starts_with(prefix));
assert!(is_valid_format(&hkid));
}
#[test]
fn test_generate_hkid_with_custom_prefix_allowed() {
let prefix = "ZZ";
let result = generate_hkid(Some(prefix), false);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(hkid.starts_with(prefix));
assert!(is_valid_format(&hkid));
}
#[test]
fn test_generate_hkid_with_random_known_prefix() {
let result = generate_hkid(None, true);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(is_valid_format(&hkid));
let prefix_len = hkid.find(|c: char| c.is_ascii_digit()).unwrap();
let prefix = &hkid[..prefix_len];
assert!(HKIDPrefix::parse(prefix).is_known());
}
#[test]
fn test_generate_hkid_with_random_any_prefix() {
let result = generate_hkid(None, false);
assert!(result.is_ok());
let hkid = result.unwrap();
assert!(is_valid_format(&hkid));
let prefix_len = hkid.find(|c: char| c.is_ascii_digit()).unwrap();
assert!(prefix_len == 1 || prefix_len == 2);
let prefix = &hkid[..prefix_len];
for c in prefix.chars() {
assert!(c.is_ascii_uppercase());
}
}
#[test]
fn test_generate_hkid_with_empty_prefix() {
let result = generate_hkid(Some(""), true);
assert!(result.is_err());
}
#[test]
fn test_generate_hkid_with_lowercase_prefix() {
let result = generate_hkid(Some("a"), true);
assert!(result.is_err());
}
#[test]
fn test_generate_hkid_with_unknown_prefix_not_allowed() {
let result = generate_hkid(Some("ZZ"), true);
assert!(result.is_err());
}
}