"""
implement class Rational
"""


class Rational:
    """ A rational number with numerator and denominator

    === Attributes ===
    @type num: int
        numerator
    @type denom: int
        denominator
    """

    def __init__(self, num, denom=1):
        """ Rational self with numerator num and denominator denom.

        denom must not be 0.

        @type self: Rational
        @type num: int
        @type denom: int
        @rtype: None

        >>> r = Rational(1, 2)
        >>> (r.num, r.denom) == (1, 2)
        True
        """
        self.num = num
        self.denom = int(denom)

    def __eq__(self, other):
        """ Return whether Rational self is equivalent to other.

        @type self: Rational
        @type other: Rational | Any
        @rtype: bool

        >>> r1 = Rational(3, 5)
        >>> r2 = Rational(6, 10)
        >>> r3 = Rational(4, 7)
        >>> r1 == r2
        True
        >>> r1.__eq__(r3)
        False
        """
        return (type(self) == type(other)
                and ((self.num * other.denom) == (other.num * self.denom)))

    def __str__(self):
        """ User-friendly string representation of Rational self.

        @type self: Rational
        @rtype: str

        >>> print(Rational(3, 5))
        3 / 5
        """
        return "{} / {}".format(self.num, self.denom)

    def __lt__(self, other):
        """ Return whether Rational self is less than other.

        @type self: Rational
        @type other: Rational | Any
        @rtype: bool

        >>> Rational(3, 5).__lt__(Rational(4, 7))
        False
        >>> Rational(3, 5).__lt__(Rational(5, 7))
        True
        """
        # n1/d1 < n2/d2
        # <=> n1/d1 - n2/d2 < 0
        # <=>(n1*d2 - n2*d1)/(d1*d2) < 0
        return ((((self.num * other.denom) - (self.denom * other.num))
                 * (self.denom * other.denom)) < 0)

    def __mul__(self, other):
        """ Return the product of Rational self and Rational other.

        @type self: Rational
        @type other: Rational
        @rtype: Rational

        >>> print(Rational(3, 5).__mul__(Rational(4, 7)))
        12 / 35
        """
        return Rational(self.num * other.num, self.denom * other.denom)

    def __add__(self, other):
        """ Return the sum of Rational self and Rational other.

        @type self: Rational
        @type other: Rational
        @rtype: Rational

        >>> print(Rational(3, 5).__add__(Rational(4, 7)))
        41 / 35
        """
        return Rational(self.num * other.denom + self.denom * other.num,
                        self.denom * other.denom)

    def _set_num(self, num):
        # set _num to num
        self._num = int(num)

    def _get_num(self):
        # return value of _num
        return self._num

    # property delegates access to num through _set_num and _get_num
    num = property(_get_num, _set_num)

    def _set_denom(self, denom):
        # set _denom to denom
        # but check for zero!
        d = int(denom)
        if d == 0:
            raise Exception("no zero denominators, please!!!!!!")
        else:
            self._denom = d

    def _get_denom(self):
        # return value of _denom
        return self._denom

    # property delegates access to denom through _set_denom and _get_denom
    denom = property(_get_denom, _set_denom)


if __name__ == "__main__":
    import doctest
    doctest.testmod()
    # make a list of Rationals.  The trailing "_" means no
    # name collision with built-in list
    list_ = [Rational(3, 4), Rational(1, 2), Rational(2, 3),
             Rational(6, 11), Rational(5, 2), Rational(4, 8)]
    # What is the difference between the built-in sorted() function
    # and the list method .sort()?  Why is the next statement not
    # very useful?
    print(sorted(list_))
    # To build a new list from an old one, we use a list comprehension...
    # we'll talk more about these later
    print([str(r) for r in sorted(list_)])
    print([str(r) for r in list_])
    list_.sort()
    print([str(r) for r in list_])
    import python_ta
    # python_ta configuration file "rational_pyta.txt"
    # to suppress warnings about using type() instead of
    # isinstance()
    python_ta.check_all(config="rational_pyta.txt")
    # python_ta.check_all()
