# bestfraction.py
# Walker M. White (wmw2)
# October 7, 2012
"""Module with a simple Fraction class.

This module shows off operator overloading.  This allows
us to define complex mathematical objects.

This version of Fraction has getters and setters, which allow 
us to enforce our invariants.  This is the prefered way to
make a class in Python"""
import math

def gcd(a,b):
    """Returns: Greatest common divisor of x and y.
    
    Precondition: x and y are integers."""
    assert type(a) == int,`x`+' is not an int'
    assert type(b) == int,`y`+' is not an int'
    while b != 0:
       t = b
       b = a % b
       a = t
    return a


class Fraction(object):
    """Instance is a fraction n/d
    
    INSTANCE ATTRIBUTES:
        _numerator   [int]:  top
        _denominator [int > 0]: bottom"""
    
    # GETTERS AND SETTERS
    def getNumerator(self):
        """Returns: fraction numerator."""
        return self._numerator # returns the field
    
    def setNumerator(self,value):
        """Sets the numerator as value
        
        Preccndition: value is an int."""
        # enforce invariant
        assert type(value) == int, `value`+' is not an int'
        # assign to field
        self._numerator = value
    
    def getDenominator(self):
        """Returns: fraction denominator."""
        return self._denominator # returns the field
    
    def setDenominator(self,value):
        """Sets the denominator as value.
        
        Precondition: value is an int > 0"""
        # enforce invariant
        assert type(value) == int, `value`+' is not an int'
        assert value > 0, `value`+' is not positive'
        # assign to field
        self._denominator = value
    
    # INITIALIZER
    def __init__(self,n=0,d=1): 
        """Initializer: Creates a new Fraction n/d
        
        Precondition: n an int, d > 0 an int."""
        # No need for asserts; setters handle everything
        self.setNumerator(n)
        self.setDenominator(d)
    
    # OPERATORS
    def __str__(self):
        """Returns: this Fraction as a string 'n/d'"""
        return str(self.getNumerator())+'/'+str(self.getDenominator())
 
    def __repr__(self):
        """Returns: unambiguous representation of Fraction"""
        return str(self.__class__)+'['+str(self)+']'
    
    def __mul__(self,other):
        """Returns: Product of self and other as a new Fraction
        
        Does not modify contents of self or other

        Precondition: other is a Fraction"""
        assert isinstance(other, Fraction)
        top = self.getNumerator()*other.getNumerator()
        bot = self.getDenominator()*other.getDenominator()
        return Fraction(top,bot)

    def __add__(self,other):
        """Returns: Sum of self and other as a new Fraction
        
        Does not modify contents of self or other

        Precondition: other is a Fraction"""
        assert isinstance(other, Fraction)
        bot = self.getDenominator()*other.getDenominator()
        top = (self.getNumerator()*other.getDenominator()+
               self.getDenominator()*other.getNumerator())
        return Fraction(top,bot)

    def __eq__(self,other):
        """Returns: True if self, other are equal Fractions.
        False if not equal, or other not a Fraction"""
        if not isinstance(other, Fraction):
            return False
        
        # Cross multiply
        left = self.getNumerator()*other.getDenominator()
        rght = self.getDenominator()*other.getNumerator()
        return left == rght

    def __ne__(self,other):
        """Returns: False if self, other are equal Fractions.
        True if not equal, or other not a Fraction"""
        return not self == other

    def __cmp__(self,other):
        """Returns: a negative integer if self < other,
        zero if self == other,
        a positive integer if self > other
        
        Used to implement comparison operations.
        
        Precondition: other is a Fraction."""
        assert isinstance(other, Fraction)

        # Cross multiply
        left = self.getNumerator()*other.getDenominator()
        rght = self.getDenominator()*other.getNumerator()
        return left - rght
    
    # NORMAL METHODS
    def reduce(self):
        """Normalizes this fraction so that numerator
        and denominator have no common divisors."""
        g = gcd(self.getNumerator(),self.getDenominator())
        self.setNumerator(self.getNumerator()/g)
        self.setDenominator(self.getDenominator()/g)