#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Adapted from:
    - http://www.djangosnippets.org/snippets/764/
    - http://www.satchmoproject.com/trac/browser/satchmo/trunk/satchmo/apps/satchmo_utils/views.py
    - http://tinyurl.com/shoppify-credit-cards
"""
from __future__ import unicode_literals

import re

# Well known card regular expressions.
CARDS = {
    'Visa': re.compile(r"^4\d{12}(\d{3})?$"),
    'Mastercard': re.compile(r"^(5[1-5]\d{4}|677189)\d{10}$"),
    'Dinersclub': re.compile(r"^3(0[0-5]|[68]\d)\d{11}$"),
    'Amex': re.compile(r"^3[47]\d{13}$"),
    'Discover': re.compile(r"^(6011|65\d{2})\d{12}$"),
}

# Well known test numbers
TEST_NUMBERS = [
    "378282246310005", "371449635398431", "378734493671000", "30569309025904",
    "38520000023237", "6011111111111117", "6011000990139424", "555555555554444",
    "5105105105105100", "4111111111111111", "4012888888881881", "4222222222222"
]


def verify_credit_card(number):
    """Returns the card type for given card number or None if invalid."""
    return CreditCard(number).verify()


class CreditCard(object):
    def __init__(self, number):
        self.number = number

    def is_number(self):
        """True if there is at least one digit in number."""
        self.number = re.sub(r'[^\d]', '', self.number)
        return self.number.isdigit()

    def is_mod10(self):
        """Returns True if number is valid according to mod10."""
        double = 0
        total = 0
        for i in range(len(self.number) - 1, -1, -1):
            for c in str((double + 1) * int(self.number[i])):
                total = total + int(c)
            double = (double + 1) % 2
        return (total % 10) == 0

    def is_test(self):
        """Returns True if number is a test card number."""
        # Note: test numbers cannot be used in the PP Pro sandbox.
        # Instead, use the credit card number associated with a
        # sandbox account (Test Accounts -> View Details).
        return self.number in TEST_NUMBERS

    def get_type(self):
        """Return the type if it matches one of the cards."""
        for card, pattern in CARDS.items():
            if pattern.match(self.number):
                return card
        return None

    def verify(self):
        """Returns the card type if valid else None."""
        if self.is_number() and not self.is_test() and self.is_mod10():
            return self.get_type()
        return None
