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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*!
The type [CheckDigitAlgorithm] provides an implementation of the
[Luhn Algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm).

# Example

```rust
use codes_check_digits::{luhn, Calculator};

let calculator = luhn::get_algorithm_instance();
assert!(calculator.is_valid("US0378331005"));
assert!(calculator.validate("US0378331005").is_ok());
assert_eq!(calculator.calculate("US037833100"), Ok(5));
```

*/

use crate::{common::is_ascii_alphanumeric_upper, error::CheckDigitError, Calculator};

// ------------------------------------------------------------------------------------------------
// Public Types
// ------------------------------------------------------------------------------------------------
///
/// This algorithm is known by many names: the "Luhn Formula", "The IBM Check",
/// "Mod 10", and is officially specified in "Annex B to ISO/IEC 7812, Part 1"
/// and in "ANSI X4.13". It is used as the check scheme on credit cards such
/// as Visa, Master Card, and American Express.
///
/// # Issues
///
/// It catches all single digit errors, but does not catch transposition errors
/// with "0" and "9" (meaning "09" → "90" and "90" → "09" are not caught).
///
#[derive(Debug, Default)]
pub struct CheckDigitAlgorithm {}

// ------------------------------------------------------------------------------------------------
// Public Functions
// ------------------------------------------------------------------------------------------------

const SHARED_INSTANCE: CheckDigitAlgorithm = CheckDigitAlgorithm {};

pub const fn get_algorithm_instance() -> &'static CheckDigitAlgorithm {
    &SHARED_INSTANCE
}

// ------------------------------------------------------------------------------------------------
// Implementations
// ------------------------------------------------------------------------------------------------

impl Calculator<u8> for CheckDigitAlgorithm {
    fn name(&self) -> &'static str {
        "Luhn Algorithm (ISO/IEC 7812, Part 1, Annex B)"
    }

    fn calculate(&self, s: &str) -> Result<u8, CheckDigitError> {
        is_ascii_alphanumeric_upper(s)?;
        let sum: u8 = s
            .chars()
            .flat_map(luhn_alphanum_to_vec)
            .rev()
            .enumerate()
            .map(|(i, n)| luhn_double_odd(i & 1 == 1, n))
            .sum();

        Ok((10 - (sum % 10)) % 10)
    }
}

// ------------------------------------------------------------------------------------------------
// Private Functions
// ------------------------------------------------------------------------------------------------

#[inline(always)]
fn luhn_double_odd(is_odd: bool, d: u8) -> u8 {
    if is_odd {
        d
    } else {
        match d {
            0..=4 => d * 2,
            5 => 1,
            6 => 3,
            7 => 5,
            8 => 7,
            9 => 9,
            _ => panic!(),
        }
    }
}

#[inline(always)]
fn luhn_alphanum_to_vec(c: char) -> Vec<u8> {
    let d = c as u8;
    match d {
        b'0'..=b'9' => vec![d - b'0'],
        b'A'..=b'Z' => {
            let d = d - 55;
            vec![d / 10, d % 10]
        }
        _ => panic!(),
    }
}

// ------------------------------------------------------------------------------------------------
// Unit Tests
// ------------------------------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use crate::luhn::CheckDigitAlgorithm;
    use crate::Calculator;

    #[test]
    fn test_check_digits() {
        let calculator: CheckDigitAlgorithm = Default::default();
        assert!(calculator.validate("US0378331005").is_ok());
        assert!(calculator.validate("037833100").is_ok());
    }
}