```{- |
Module      :  Luhn
Description :  An implementation of Luhn's check digit algorithm.
Copyright   :  (c) N-Sim Ltd. 2008

Maintainer  :  jhb@n-sim.com
Stability   :  provisional
Portability :  portable

An implementation of Luhn's check digit algorithm.
-}
module Luhn(
-- * Creating a check digit
-- * Validating a check digit
checkLuhnDigit,
-- * QuickCheck tests
prop_checkLuhn,
prop_checkSingleError
) where

import Test.QuickCheck

-- | Like Python's enumerate function - returns a tuple where the first
--   element is the index from 0 of the second element in the input list.
enumerate :: Integral n => [a] -> [(n, a)]
enumerate xs = enumerate' 0 xs
where
enumerate' _ [] = []
enumerate' counter (a:as) =
(counter, a) : enumerate' (counter + 1) as

-- | Returns the digits of a number as a list, in reverse order.
digitsRev :: Integral n => n -> [n]
digitsRev i = case i of
0 -> []
_ -> lastDigit : digitsRev rest
where (rest, lastDigit) = quotRem i 10

-- | Returns the digits of a number as a list.
digits :: Integral n => n -> [n]
digits = reverse . digitsRev

-- | Takes a list of digits, and converts them back into a number.
unDigits :: Integral n => [n] -> n
unDigits = foldl (\ a b -> a * 10 + b) 0

-- | Appends a Luhn check digit to the end of a number.
=> n -- ^ Number to which a Luhn check digit will be appended.
-> n -- ^ Number with the appended Luhn check digit.
addLuhnDigit num = num * 10 + checkDigit
where
checkDigit = (10 - total `mod` 10) `mod` 10
total = sum \$ concat \$ map doubleEven (enumerate \$ digitsRev num)
doubleEven :: Integral n => (Int, n) -> [n]
doubleEven (i, n) = if odd i
then [n]
else digitsRev (2 * n)

-- | Validates that the Luhn check digit (assumed to be the last/least-
--   significant digit in the number) is correct.
checkLuhnDigit :: Integral n
=> n -- ^ Number with a Luhn check digit as its last digit.
-> Bool -- ^ Whether or not the check digit is consistent.
checkLuhnDigit num = total `mod` 10 == 0
where
total = sum \$ concat \$ map doubleOdd (enumerate \$ digitsRev num)
doubleOdd :: Integral n => (Int, n) -> [n]
doubleOdd (i, n) = if odd i
then digitsRev (2 * n)
else [n]

-- | Validates that a generated check digit validates.
prop_checkLuhn
:: Integer -- ^ Number to validate a Luhn check digit for.
-> Property
prop_checkLuhn i = i > 0 ==> (checkLuhnDigit . addLuhnDigit) i

-- | Any single number transcription error should result in a failure in
--   the validation of a Luhn check digit. This property validates this.
prop_checkSingleError
:: Integer -- ^ The number to transcribe.
-> Integer -- ^ The position to introduce a transcription error.
-> Integer -- ^ The number to transcribe in place of the original.
-> Property
prop_checkSingleError i modDigit replace = i > 0 ==>