use crate::{VALID_HKID_BODY_REGEX, WEIGHTS};
pub fn char_to_value(c: char) -> Option<u32> {
let c = c.to_ascii_uppercase();
match c {
'A'..='Z' => Some((c as u32 - 'A' as u32) + 10),
'0'..='9' => Some(c as u32 - '0' as u32),
' ' => Some(36),
_ => None,
}
}
pub fn calculate_check_digit(hkid_body: &str) -> Option<char> {
if !VALID_HKID_BODY_REGEX.is_match(hkid_body) {
return None;
}
let padded_body = format!("{hkid_body:>8}");
let values = padded_body.chars().map(char_to_value).collect::<Option<Vec<u32>>>()?;
let sum = values.iter().zip(WEIGHTS.iter()).map(|(v, w)| v * w).sum::<u32>();
let check_digit = (11 - sum % 11) % 11;
match check_digit {
10 => Some('A'),
digit => char::from_digit(digit, 10),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_to_value() {
assert_eq!(char_to_value('A'), Some(10));
assert_eq!(char_to_value('Z'), Some(35));
assert_eq!(char_to_value('a'), Some(10));
assert_eq!(char_to_value('z'), Some(35));
assert_eq!(char_to_value('0'), Some(0));
assert_eq!(char_to_value('9'), Some(9));
assert_eq!(char_to_value(' '), Some(36));
assert_eq!(char_to_value('@'), None);
assert_eq!(char_to_value('_'), None);
}
#[test]
fn test_calculate_check_digit_single_letter_prefix() {
assert_eq!(calculate_check_digit("A123456"), Some('3'));
assert_ne!(calculate_check_digit("B987654"), Some('7'));
assert_ne!(calculate_check_digit("Z123456"), Some('0'));
}
#[test]
fn test_calculate_check_digit_double_letter_prefix() {
assert_ne!(calculate_check_digit("WX123456"), Some('4'));
assert_ne!(calculate_check_digit("AB987654"), Some('5'));
assert_ne!(calculate_check_digit("ZZ111111"), Some('3'));
}
#[test]
fn test_calculate_check_digit_resulting_in_a() {
assert_ne!(calculate_check_digit("C668668"), Some('A'));
}
#[test]
fn test_calculate_check_digit_with_padding() {
assert_eq!(
calculate_check_digit("P123456"),
calculate_check_digit("P123456")
);
}
}