# Password.py
# Charles Van Loan
# February 14, 2015

""" Assesses passwords for their strength.
"""
import math
import string

UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
LOWER = 'abcdefghijklmnopqrstuvwxyz'
DIGITS = '1234567890'
SPECIAL = string.punctuation


def HowMany(s1,s2):
    """ Returns an int that is the number of
    characters in S1 that are also characters in S2.
    
    PreC: s1 and s2 are strings. """
    
    N = 0
    for c in s1:
        if s2.count(c)>0:
           # c is a character in s2
           N = N+1
    return N

def HowManyTriplets(s1,s2):
    """ Returns an int that is the number of length-3
    substrings in S1 that are length-3 substrings in S2
    
    PreC: s1 and s2 are strings that have length 3 or greater. """
    
    N = 0
    m = len(s1)
    for k in range(m-2):
        t = s1[k:k+3]
        if s2.find(t)>=0:
            # t is a substring of s2
            N = N + 1
    return N
        

def B1(s):
    """ Returns an int that is a score associated
    with password length.
    
    PreC: s is a valid password string."""
    
    return 3*min(len(s),15)

def B2(s):
    """ Returns an int that is a score associated
    with the mix of upper and lower case letters.
    
    PreC: s is a valid password string."""
    
    L = HowMany(s,LOWER)
    U = HowMany(s,UPPER)
    N = L+U
    if N==0:
       return 0
    else:
       return math.floor(40*(1-float(L)/float(N))*(1-float(U)/float(N)))

def B3(s):
    """ Returns an int that is the score associated with
    the number of digits in s.
    
    PreC: s is a valid password string."""
    
    return 4*HowMany(s,DIGITS)

def B4(s):
    """ Returns an int that is the score associated with
    the number of special characters in s.
    
    PreC: s is a valid password string."""
    
    return 6*HowMany(s,SPECIAL)

def B5(s):
    """ Returns an int that is the score associated with
    the number of digits  and special characters in s excepting
    the zeroth and last character.
    
    PreC: s is a valid password string."""
    
    n = len(s)
    sMiddle = s[1:n-1]
    return  (3*( HowMany(sMiddle,DIGITS) + HowMany(sMiddle,SPECIAL)))

def B6(s):
    """ Returns an int that is the score associated with
    the number of different types of characters in s.
    
    PreC: s is a valid password string."""
    
    if HowMany(s,LOWER)>0 and HowMany(s,UPPER)>0 and HowMany(s,DIGITS)>0 and HowMany(s,SPECIAL):
        return 10
    else:
        return 0

def D1(s):
    """ Returns an int that is the deduction score associated with
    having a password that is just letters.
    
    PreC: s is a valid password string."""
    
    if HowMany(s,UPPER+LOWER)==len(s):
        return len(s)
    else:
        return 0
    

def D2(s):
    """ Returns an int that is the deduction score associated with
    having a password that is just digits.
    
    PreC: s is a valid password string."""
    
    if HowMany(s,DIGITS)==len(s):
        return len(s)
    else:
        return 0

def D3(s):
    """ Returns an int that is the deduction score associated with
    with repeat characters.
    
    PreC: s is a valid password string."""
    
    N = len(s)
    M = 0
    for c in s:
        if s.count(c)==1:
            # The character c appears exactly once in s
            M = M+1
    return 3*(N-M)

def D4(s):
    """ Returns an int that is the deduction score associated with
    look-a-like sequences.
    
    PreC: s is a valid password string."""
    
    N = 0
    for k in range(len(s)-1):
        # See if S[k] and s[k+1] are the same type of character
        if UPPER.find(s[k])>=0  and UPPER.find(s[k+1])>=0:
            N = N+1
        elif LOWER.find(s[k])>=0  and LOWER.find(s[k+1])>=0:
            N = N+1
        elif DIGITS.find(s[k])>=0  and DIGITS.find(s[k+1])>=0:
            N = N+1
        elif SPECIAL.find(s[k])>=0  and SPECIAL.find(s[k+1])>=0:
            N = N+1
    return 2*N

def D5(s):
    """ Returns an int that is the deduction score associated with
    having row-1 triplets
    
    PreC: s is a valid password string."""
    
    return 3*HowManyTriplets(s,'1234567890')

def D6(s):
    """ Returns an int that is the deduction score associated with
    having row-2 triplets
    
    PreC: s is a valid password string."""
    return 3*HowManyTriplets(s,'qwertyuiop')

def D7(s):
    """ Returns an int that is the deduction score associated with
    having row-3 triplets
    
    PreC: s is a valid password string."""
    return 3*HowManyTriplets(s,'asdfghjkl')

def D8(s):
    """ Returns an int that is the deduction score associated with
    having row-4 triplets
    
    PreC: s is a valid password string."""
    return 3*HowManyTriplets(s,'zxcvbnm')
    
def PWS(s):
    """ Returns an int that is the password strength of s.
    
    PreC: s is a valid password string."""
    
    TheBonuses = B1(s)+B2(s)+B3(s)+B4(s)+B5(s)+B6(s)
    TheDeductions =  D1(s)+D2(s)+D3(s)+D4(s)+D5(s)+D6(s)+D7(s)+D8(s)
    return (TheBonuses-TheDeductions)   

    
if __name__ == '__main__':
    s = raw_input('Enter a string: ')
    nGoodChars = HowMany(s,UPPER)+HowMany(s,LOWER)+HowMany(s,DIGITS)+HowMany(s,SPECIAL)
    if len(s)>nGoodChars or len(s)==0:
        print '\n %s is an invalid password string' % s
    else:
        print '\n\n  Password:  %s' % s
        print '  Strength:  %1d' %(PWS(s))
        print '--------------------------------------------'
        print '   Bonuses:  %2d  %2d  %2d  %2d  %2d  %2d' % (B1(s),B2(s),B3(s),B4(s),B5(s),B6(s))
        print 'Deductions:  %2d  %2d  %2d  %2d  %2d  %2d  %2d  %2d' % (D1(s),D2(s),D3(s),D4(s),D5(s),D6(s),D7(s),D8(s))