''' mod26.py
Modular group, mod 26.
Includes arithmetic operations, including mixed operations with integers.
The complications of mixing types are described below.
The simpler version is mod26A.py.

Python infix operator implementation:

All the infix operators have a method name with double underscores at
either end, like __add__ for +.

The final situation is a bit more complicated if the two operands do not
have the same type and the __add__ method for x does not know what to do
with y, but y may know what to do with x:

Here is more complete pseudocode to return a value for x + y:

if x defines the __add__ method:
    calculate ans = x.__add__(y)
    if ans is not NotImplemented:  # single purpose object in Python
        return ans
if y defines the __radd__ method:  # add from the Right operand side
    calculate ans = y.__radd__(x)
    if ans is not NotImplemented:
        return ans
throw an Exception:  the operation is not defined.

Similarly for __rmul__ and __rsub__.

Like with C++, the operators retain their regular precedence, no matter
what types are being evaluated.

Most built-in functions that may work with arbitrary types
also have a method with double underscores associated.
For instance: __str__ for str __abs__ for abs (absolute value).

'''

class Mod26:
    ''' Class for mod 26 arithmetic

    >>> x = Mod26(4)
    >>> y = Mod26(11)
    >>> z = Mod26(39)
    >>> print x+y
    15 mod 26
    >>> print z
    13 mod 26
    >>> print -x
    22 mod 26
    >>> print z - x
    9 mod 26
    >>> print x*8
    6 mod 26
    >>> print x*z
    0 mod 26
    >>> print x**3
    12 mod 26
    >>> print x == y
    False
    >>> print x*y == -8
    True
    '''

    def __init__(self, n=0):
        'construct Mod26 object from integer or other Mod26 object'
        if isinstance(n, Mod26): # isinstance
            n = n.value
        assert isinstance(n, (int, long)) # assert syntax:  assert booleanExpr
        self.value = n % 26  # instance variable not declared: just assigned to

    def __str__(self):   # used by str built-in function, which is used by print
        'Return an informal string representation of object'
        return "{0} mod {1}".format(self.value, 26)

    def __repr__(self):  # used by repr built-in function
        'Return a formal string representation, usable in the Shell'
        return "Mod{0}({1})".format(26, self.value)

    def _toSameType(self, other): # leading underscore: convention for private
        'Try to convert to same type, return None otherwise.'
        if type(self) == type(other): # avoid conversion overhead if possible
            return other
        try:  # exception syntax
            return Mod26(other)
        except: # catches all exceptions
            return None       

    def __add__(self, other): # used by + infix operand
        'self + other, if defined'
        other = self._toSameType(other)
        if other is None:
            return NotImplemented
        return Mod26(self.value + other.value)

    def __sub__(self, other): # used by - infix operand
        'self - other, if defined'
        other = self._toSameType(other)
        if other is None:
            return NotImplemented
        return Mod26(self.value - other.value)

    def __neg__(self):# used by - unary operand
        '-self'
        return Mod26(-self.value)

    def __mul__(self, other): # used by * infix operand
        'self * other, if defined'
        other = self._toSameType(other)
        if other is None:
            return NotImplemented
        return Mod26(self.value * other.value)

    def __eq__(self, other): # used by == infix operand
        '''self == other, if defined
        Allow conversion of other to Mod26 before test.  Choice!!!!'''
        other = self._toSameType(other)
        if other is None:
            return False  
        return self.value == other.value

    def __ne__(self, other): # used by != infix operand
        'self != other, if defined'
        return not self == other

    def __pow__(self, p): # used by ** infix operand
        'self ** p'
        return Mod26(self.value**p)

    # operations where only the second operand is a Mod26 (prefix r)  
    def __radd__(self, other):
        'other + self, when other is not a Mod26'
        return self + other # commutative, and now Mod26 first

    def __rsub__(self, other):
        'other - self, when other is not a Mod26'
        return -self + other # almost commutative, and now Mod26 first

    def __rmul__(self, other):
        'other * self, when other is not a Mod26'
        return self * other # commutative, and now Mod26 first

def doctests():
    '''
    More complete tests, including where Exceptions are expected.
    Notice that the middle lines of tracebacks are replaced by ...
    
    >>> x = Mod26(4)
    >>> y = Mod26(11)
    >>> z = Mod26(39)
    >>> x != y
    True
    >>> x != Mod26(4)
    False
    >>> x != 4
    False
    >>> 5 - x
    Mod26(1)
    >>> y - 15
    Mod26(22)
    >>> 3*x
    Mod26(12)
    >>> 3+x
    Mod26(7)
    >>> Mod26(2**100)  # tests long operand
    Mod26(16)
    >>> 3 ** x
    Traceback (most recent call last):
    ...
    TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'instance'
    >>> Mod26(2.3)
    Traceback (most recent call last):
    ...
    AssertionError
    '''
    
if __name__ == '__main__': 
    import doctest
    doctest.testmod() #verbose=True) 
