use rand::Rng;
fn ci_checksum(digits: &[u8]) -> u32 {
digits
.iter()
.enumerate()
.map(|(i, &d)| {
let w = if i % 2 == 0 { 2 } else { 1 };
let v = d as u32 * w;
if v > 9 {
v - 9
} else {
v
}
})
.sum::<u32>()
% 10
}
fn weighted_checksum(digits: &[u8], weights: &[u32]) -> u32 {
digits
.iter()
.zip(weights.iter())
.map(|(&d, &w)| d as u32 * w)
.sum::<u32>()
% 11
}
pub fn generate(rng: &mut rand::rngs::ThreadRng) -> String {
loop {
let province = rng.gen_range(1u8..=24);
let kind = *[0u8, 6, 9].get(rng.gen_range(0..3)).unwrap();
if kind < 6 {
let third = rng.gen_range(0u8..=5);
let mut digits: Vec<u8> = vec![province / 10, province % 10, third];
for _ in 3..9 {
digits.push(rng.gen_range(0..=9));
}
let partial_sum = ci_checksum(&digits);
let check = (10 - partial_sum) % 10;
digits.push(check as u8);
if ci_checksum(&digits) != 0 {
continue;
}
let est = rng.gen_range(1u16..=999);
let s: String = digits.iter().map(|d| (b'0' + d) as char).collect();
return format!("{}{:03}", s, est);
} else if kind == 6 {
let mut digits: Vec<u8> = vec![province / 10, province % 10, 6];
for _ in 3..8 {
digits.push(rng.gen_range(0..=9));
}
let weights: [u32; 8] = [3, 2, 7, 6, 5, 4, 3, 2];
let sum = weighted_checksum(&digits[..8], &weights);
let check = if sum == 0 { 0 } else { 11 - sum };
if check >= 10 {
continue;
}
digits.push(check as u8);
if weighted_checksum(&digits[..9], &[3, 2, 7, 6, 5, 4, 3, 2, 1]) != 0 {
continue;
}
let est = rng.gen_range(1u16..=9999);
let s: String = digits.iter().map(|d| (b'0' + d) as char).collect();
return format!("{}{:04}", s, est);
} else {
let mut digits: Vec<u8> = vec![province / 10, province % 10, 9];
for _ in 3..9 {
digits.push(rng.gen_range(0..=9));
}
let weights: [u32; 9] = [4, 3, 2, 7, 6, 5, 4, 3, 2];
let sum = weighted_checksum(&digits[..9], &weights);
let check = if sum == 0 { 0 } else { 11 - sum };
if check >= 10 {
continue;
}
digits.push(check as u8);
if weighted_checksum(&digits[..10], &[4, 3, 2, 7, 6, 5, 4, 3, 2, 1]) != 0 {
continue;
}
let est = rng.gen_range(1u16..=999);
let s: String = digits.iter().map(|d| (b'0' + d) as char).collect();
return format!("{}{:03}", s, est);
}
}
}
pub fn validate(code: &str) -> bool {
let clean: String = code.chars().filter(|c| c.is_ascii_digit()).collect();
if clean.len() != 13 {
return false;
}
let digits: Vec<u8> = clean.bytes().map(|b| b - b'0').collect();
let province = digits[0] * 10 + digits[1];
if !((1..=24).contains(&province) || province == 30 || province == 50) {
return false;
}
let third = digits[2];
if third <= 5 {
ci_checksum(&digits[..10]) == 0 && clean[10..13] != *"000"
} else if third == 6 {
let public_ok = weighted_checksum(&digits[..9], &[3, 2, 7, 6, 5, 4, 3, 2, 1]) == 0
&& clean[9..13] != *"0000";
if public_ok {
return true;
}
ci_checksum(&digits[..10]) == 0 && clean[10..13] != *"000"
} else if third == 9 {
let public_ok = weighted_checksum(&digits[..9], &[3, 2, 7, 6, 5, 4, 3, 2, 1]) == 0
&& clean[9..13] != *"0000";
if public_ok {
return true;
}
weighted_checksum(&digits[..10], &[4, 3, 2, 7, 6, 5, 4, 3, 2, 1]) == 0
&& clean[10..13] != *"000"
} else {
false
}
}