# RomanSol.py
# the 2016sp cs 1110 profs (cs-1110profs-l@cornell.edu)
# Feb 25, 2016
""" A framework for checking Roman numeral strings and their
value."""


def AllCharsOK(R):
    """Returns True if every character in R is in
    'MDCLXVI' and returns False if not.

    PreC: R is a string of length > 0."""
    NumGoodChars = (R.count('I') + R.count('V') + R.count('X') +
                    R.count('L') + R.count('C') + R.count('D') +
                    R.count('M'))
    return NumGoodChars==len(R)


# Alternate implementation
def AllCharsOK2(R):
    """Returns True if every character in R is in
    'MDCLXVI' and returns False if not.

    PreC: R is a string of length > 0"""

    roman_chars = 'MDCLXVI'
    for c in R:
        if c not in roman_chars:
            return False
    return True


def AllFreqsOK(R):
    """ Returns True if R is a frequency-legal string"

    PreC: R is a string of length > 0
    """

    B = ( R.count('M')<=3 and R.count('D')<=1 and R.count('C')<=3 and
          R.count('L')<=1 and R.count('X')<=3 and R.count('V')<=1 and
          R.count('I')<=3)
    return B

# alternate implementation
def AllFreqsOK2(R):
    """ Returns True if R is a frequency-legal string"

    PreC: R is a string of length > 0
   """
    at_most_three='MCXI'
    at_most_one='DLV'

    for c in at_most_three:
        if R.count(c) > 3:
            return False
    for c in at_most_one:
        if R.count(c) > 1:
            return False
    # all conditions above passed
    return True


def SingleOK(c,s,R):
    """ Returns True if c is not in R or if c is in R but
    not preceded by any character in s. Otherwise, False is returned,

    PreC: c is a character, s is a nonempty string, R is a nonempty string,
    and c is not in s.
    """
    for x in s:
        if (x in R) and (R.rfind(c) > R.find(x)):
            return False
    # if here, there isn't any x in R that occurs before the last c.
    return True
    # Note: rfind is just like find except it looks for the location
    # of the last occurrence.

# alternate solution more like what's given in the hints
def SingleOK2(c,s,R):
    """ Returns True if c is not in R or if c is in R but
    not preceded by any character in s. Otherwise, False is returned,

    PreC: c is a character, s is a nonempty string, R is a nonempty string,
    and c is not in s.
    """
    if c not in R:
        return True
    else:
        for character in s:
            if character in R and R.rfind(c) > R.find(character):
                return False
    return True

def AllSinglesOK(R):
    """Returns True if and only if R is an single-legal string.

    PreC: R is a non-null string.
    """
    M_ok = SingleOK('M','DLXVI',R)
    D_ok = SingleOK('D','LXVI',R)
    C_ok = SingleOK('C','LVI',R)
    L_ok = SingleOK('L','VI',R)
    X_ok = SingleOK('X','V',R)
    return M_ok and D_ok and C_ok and L_ok and X_ok


def DoubleOK(s,R):
    """ Returns True if s is not in R or if s occurs once in R
    and the first occurrence of s[0] is the first occurrence of s.

    PreC: s is a length-2 string and R is a nonempty string.
    """
    if R.count(s)==0:
        return True
    else:
        return R.find(s)==R.find(s[0]) and R.count(s)==1


def AllDoublesOK(R):
    """Returns True if and only if R is a double-legal string.

    PreC: R is a non-null string.
    """
    C_ok = DoubleOK('CD',R) and DoubleOK('CM',R)
    X_ok = DoubleOK('XL',R) and DoubleOK('XC',R)
    I_ok = DoubleOK('IV',R) and DoubleOK('IX',R)
    return C_ok and X_ok and I_ok


def Value(R):
    """ Returns an int that is the value of R

    PreC: R is a Roman numeral string.
    """
    # The preliminary value
    s = (R.count('M')*1000 + R.count('D')*500 + R.count('C')*100 + R.count('L')*50+
         R.count('X')*10 + R.count('V')*5 + R.count('I'))
    # The adjustments...
    # in acceptable strings, there can't be both 'CM' and 'CD'
    if R.find('CM')>=0 or R.find('CD')>=0:
        s = s-200
    # in acceptable strings, there can't be both 'XL' and 'XC'
    if R.find('XL')>=0 or R.find('XC')>=0:
        s = s - 20
    # in acceptable strings, there can't be both 'IX' and 'IV'
    if R.find('IX')>=0 or R.find('IV')>=0:
        s = s-2
    return s

if __name__ == '__main__':

    # an alternate testing scheme using lists of lists and lists of functions
    list_debug=True

    if list_debug:

        print '(partially) testing AllChars functions'
        answers = [['IXIIIDDCM', True],['3X', False],['XXX', True]]
        for answer in answers:
            for fn in [AllCharsOK, AllCharsOK2]:
                response = fn(answer[0])
                if response != answer[1]:
                    print "function", fn, ", input was", answer[0], "but output was",str(response)

        print '(partially) testing AllFreqs functions'
        answers = [ ['IXIILDMVX', True],
                    ['MM1984XVI', True],
                    ['XXXX', False],
                    ['D3D', False]]
        for answer in answers:
            for fn in [AllFreqsOK, AllFreqsOK2]:
                response = fn(answer[0])
                if response != answer[1]:
                    print "function", fn, ", input was", answer[0], "but output was",str(response)

        print '(partially) testing SingleOK functions'
        answers = [ ['C', 'LVI', 'MDV', True],
                    ['C', 'LVI', 'MCCV', True],
                    ['C', 'LVI', 'MCVCV', False],
                    ['M', 'DLXVI', 'CIMVM', False],
                    ['M', 'DLXVI', 'MCMXIV', True]]
        for answer in answers:
            for fn in [SingleOK, SingleOK2]:
                response = fn(answer[0], answer[1], answer[2])
                if response != answer[3]:
                    print "function", fn, ", input was", answer[0], answer[1], answer[2],  "but output was",str(response)

        print '(partially) testing AllSinglesOK functions'
        answers = [ ['MCMXIV', True],
                    ['CIMVM', False]]
        for answer in answers:
            for fn in [AllSinglesOK]:
                response = fn(answer[0])
                if response != answer[1]:
                    print "function", fn, ", input was", answer[0], "but output was",str(response)

        print '(partially) testing DoubleOK functions'
        answers = [ ['CM','MCMXC', True],
                    ['CM','MCX', True],
                    ['CM', 'MCXCM', False]]
        for answer in answers:
            for fn in [DoubleOK]:
                response = fn(answer[0], answer[1])
                if response != answer[2]:
                    print "function", fn, ", input was", answer[0],answer[1], "but output was",str(response)



    print 'all tests done'


    # the regular "test on the command-line" testing scheme

    R = raw_input('Enter a Roman Numeral String  (do not surround with quotes): ')
    print 'AllCharsOK(R)   is ',AllCharsOK(R)
    print 'AllFreqsOK(R)   is ',AllFreqsOK(R)
    print 'AllSinglesOK(R) is ',AllSinglesOK(R)
    print 'AllDoublesOK(R) is ',AllDoublesOK(R)
    OK = AllCharsOK(R) and AllFreqsOK(R) and AllSinglesOK(R) and AllDoublesOK(R)
    if OK:
        print 'Value = %1d' % Value(R)
    else:
        print 'Not a valid Roman numeral string.'