// Codice Fiscale, the Italian tax code. Sixteen characters: six letters from
// the name, four characters of birth date, four of birthplace, and a final
// check letter called the CIN. The CIN is computed over the first fifteen with
// two different value tables — one for the odd positions and one for the even —
// summed and taken modulo 26.
///|
/// Values for the odd positions, the first, third and so on. The table is not a
/// pattern: digits and the first ten letters share one sequence, and the rest
/// are scattered, so it has to be listed.
let cf_odd : Array[Int] = [
1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 2, 4, 18,
20, 11, 3, 6, 8, 12, 14, 16, 10, 22, 25, 24, 23,
]
///|
/// Values for the even positions, the second, fourth and so on: a digit is
/// worth its own value and a letter is worth its position in the alphabet.
let cf_even : Array[Int] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
]
///|
/// The letters a CIN can be, indexed by the remainder.
let cf_letters : Array[Char] = [
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
]
///|
/// The letters that mark the month of birth. There is no code for every letter
/// of the alphabet; the sequence skips over the ones that would spell words.
let cf_month_codes : Array[Char] = [
'A', 'B', 'C', 'D', 'E', 'H', 'L', 'M', 'P', 'R', 'S', 'T',
]
///|
/// The ten letters an omocodia code substitutes for digits, used when two
/// people would otherwise share a code.
let cf_omocodia_letters : Array[Char] = [
'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
]
///|
/// The table index of a character: digits take their own value and letters take
/// 10..35, so the two tables can be indexed the same way.
fn cf_index(c : Char) -> Int? {
let u = upper_ascii(c)
if is_digit(u) {
Some(u.to_int() - '0'.to_int())
} else if is_alpha(u) {
Some(10 + (u.to_int() - 'A'.to_int()))
} else {
None
}
}
///|
/// True for a character that may stand where a digit is expected: a digit, or
/// one of the omocodia letters.
fn cf_numeric_char(c : Char) -> Bool {
let u = upper_ascii(c)
if is_digit(u) {
return true
}
for l in cf_omocodia_letters {
if u == l {
return true
}
}
false
}
///|
/// True for one of the twelve month codes.
fn cf_month_char(c : Char) -> Bool {
let u = upper_ascii(c)
for m in cf_month_codes {
if u == m {
return true
}
}
false
}
///|
/// The CIN implied by the first fifteen characters of a codice fiscale.
fn cf_expected(cs : Array[Char]) -> Char? {
let mut sum = 0
for i = 0; i < 15; i = i + 1 {
let v = match cf_index(cs[i]) {
None => return None
Some(v) => v
}
// The first character counts as position one, which is an odd position.
if i % 2 == 0 {
sum = sum + cf_odd[v]
} else {
sum = sum + cf_even[v]
}
}
Some(cf_letters[sum % 26])
}
///|
/// The check letter that belongs after the first 15 characters of a codice
/// fiscale.
pub fn cf_check_char(first15 : String) -> String? {
let cs = chars_of(first15)
if cs.length() != 15 {
return None
}
match cf_expected(cs) {
None => None
Some(c) => Some(char_str(c))
}
}
///|
/// Validate a 16-character codice fiscale, including the layout the code is
/// required to follow: six letters, two numeric characters, a month letter, two
/// numeric characters, a letter, three numeric characters and the check letter.
/// Lowercase input is accepted.
pub fn cf_valid(s : String) -> Bool {
let cs = chars_of(s)
if cs.length() != 16 {
return false
}
for i = 0; i < 6; i = i + 1 {
if !is_alpha(upper_ascii(cs[i])) {
return false
}
}
if !cf_numeric_char(cs[6]) || !cf_numeric_char(cs[7]) {
return false
}
if !cf_month_char(cs[8]) {
return false
}
if !cf_numeric_char(cs[9]) || !cf_numeric_char(cs[10]) {
return false
}
if !is_alpha(upper_ascii(cs[11])) {
return false
}
for i = 12; i < 15; i = i + 1 {
if !cf_numeric_char(cs[i]) {
return false
}
}
if !is_alpha(upper_ascii(cs[15])) {
return false
}
match cf_expected(cs) {
None => false
Some(expected) => upper_ascii(cs[15]) == expected
}
}