use rand::Rng;
use super::IdResult;
const ALPHABET: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
fn alpha_index(c: u8) -> u32 {
match c {
b'0'..=b'9' => (c - b'0') as u32,
b'A'..=b'Z' => (c - b'A' + 10) as u32,
_ => 0,
}
}
fn calc_check_digit(number: &[u8]) -> u8 {
let sum: u32 = number
.iter()
.enumerate()
.map(|(i, &c)| (14 - i as u32) * alpha_index(c))
.sum();
ALPHABET[((17u32.wrapping_sub(sum % 17)) % 17) as usize]
}
pub fn generate(_opts: &super::GenOptions, rng: &mut rand::rngs::ThreadRng) -> String {
let first = (b'A' + rng.gen_range(0..26u8)) as char;
let day: u8 = rng.gen_range(1..=28);
let month: u8 = rng.gen_range(1..=12);
let year: u8 = rng.gen_range(0..=99);
let serial: String = (0..6)
.map(|_| (b'0' + rng.gen_range(0..=9u8)) as char)
.collect();
let partial = format!("{}{:02}{:02}{:02}{}", first, day, month, year, serial);
let check = calc_check_digit(partial.as_bytes());
format!("{}{}", partial, check as char)
}
pub fn validate(code: &str) -> bool {
let clean: String = code
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>()
.to_uppercase();
if clean.len() != 14 {
return false;
}
let bytes = clean.as_bytes();
if !bytes[0].is_ascii_uppercase() {
return false;
}
if !bytes[1..13].iter().all(|b| b.is_ascii_digit()) {
return false;
}
if !bytes[13].is_ascii_alphanumeric() {
return false;
}
let day = clean[1..3].parse::<u8>().unwrap_or(0);
let month = clean[3..5].parse::<u8>().unwrap_or(0);
if !(1..=31).contains(&day) || !(1..=12).contains(&month) {
return false;
}
calc_check_digit(&bytes[..13]) == bytes[13]
}
pub fn parse(code: &str) -> IdResult {
let clean: String = code
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>()
.to_uppercase();
let valid = validate(code);
let dob = if valid && clean.len() == 14 {
let day = clean[1..3].parse::<u8>().unwrap_or(0);
let month = clean[3..5].parse::<u8>().unwrap_or(0);
let year = clean[5..7].parse::<u16>().unwrap_or(0);
let full_year = if year > 25 { 1900 + year } else { 2000 + year };
Some(format!("{:04}-{:02}-{:02}", full_year, month, day))
} else {
None
};
IdResult {
country_code: "".to_string(),
code: clean,
gender: None,
dob,
valid,
}
}