module Faker.CreditCard
(
  visa
, mastercard
, discover
, americanExpress
, dinersClub
, jcb
, switch
, solo
, dankort
, maestro
, forbrugsforeningen
, laser
)
where

import Data.Char (digitToInt, isDigit)
import Faker.Utils

visa :: IO String
visa = randomCardNumber "visa"

mastercard :: IO String
mastercard = randomCardNumber "mastercard"

discover :: IO String
discover = randomCardNumber "discover"

americanExpress :: IO String
americanExpress = randomCardNumber "american_express"

dinersClub :: IO String
dinersClub = randomCardNumber "dinersClub"

jcb :: IO String
jcb = randomCardNumber "jcb"

switch :: IO String
switch = randomCardNumber "switch"

solo :: IO String
solo = randomCardNumber "solo"

dankort :: IO String
dankort = randomCardNumber "dankort"

maestro :: IO String
maestro = randomCardNumber "maestro"

forbrugsforeningen :: IO String
forbrugsforeningen = randomCardNumber "forbrugsforeningen"

laser :: IO String
laser = randomCardNumber "laser"

randomCardNumber :: String -> IO String
randomCardNumber attr = randomValue "credit_card" attr >>= evalRegex >>= addLuhnSum

addLuhnSum :: String -> IO String
addLuhnSum numberString = do
    let numbers = collectNumbers numberString
        luhnSum = countLuhnSum numbers 2
        luhnDigit = (10 - (luhnSum `mod` 10)) `mod` 10
    return $ init numberString ++ show luhnDigit

countLuhnSum :: [Int] -> Int -> Int
countLuhnSum [] _ = 0
countLuhnSum (x:xs) m = let nextM = if m == 2 then 1 else 2 in
                            luhnStep x m + countLuhnSum xs nextM

luhnStep :: Int -> Int -> Int
luhnStep x m = sum $ map digitToInt (show (x * m))

collectNumbers :: String -> [Int]
collectNumbers [] = []
collectNumbers str = foldl (\a x -> if isDigit x then digitToInt x : a else a) [] str