#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Stand: Donnerstag, 19.12.

"""

# In diesem Modul finden Sie die verbesserte und erweiterte Musterlösung zur
# Bruchklasse aus Aufgabe 15.



# Sie können die Datei als Skript ausführen, oder als Modul z.B. mit
# import bruch as beliebigerName
# oder nur die Klasse Bruch mit
# from bruch import Bruch
# laden.


class Bruch():
    '''Bruchklasse'''

    from math import gcd

    def __init__(self, zaehler=0, nenner=1):
        '''Initialisieren, Zähler und Nenner können als Integer oder Objekte
        der Klasse Bruch übergeben werden'''
        if type(nenner) is int:
            if type(zaehler) is int:
                self.zaehler = zaehler
                self.nenner = nenner
            elif isinstance(zaehler, Bruch):
                self.zaehler = zaehler.zaehler
                self.nenner = zaehler.nenner * nenner
            else:
                raise NotImplementedError
        elif isinstance(nenner, Bruch):
            if type(zaehler) is int:
                self.zaehler = zaehler * nenner.nenner
                self.nenner = nenner.zaehler
            elif isinstance(zaehler, Bruch):
                self.zaehler = zaehler.zaehler * nenner.nenner
                self.nenner = zaehler.nenner * nenner.zaehler
            else:
                raise NotImplementedError
        elif nenner == 0:
            raise ZeroDivisionError
        else:
            raise NotImplementedError

        if self.nenner < 0:
            self.zaehler, self.nenner = self.__kuerzen__(
                -self.zaehler, -self.nenner)
        else:
            self.zaehler, self.nenner = self.__kuerzen__(
                self.zaehler, self.nenner)

    def __kuerzen__(self, zaehler, nenner):
        '''Zähler und Nenner kürzen'''
        # Die Unterstriche in der Benennung sorgen dafür, dass die Funktion
        # versteckt ist. D.h. definiert man außerhalb einen Bruch a, zeigt die
        # Eingabe von a. und (TAB) nur nicht-versteckte Methoden, wie z.B.
        # tofloat an, aber nicht die Methode kuerzen. Das ist hier sinnvoll,
        # weil intern immer ein gekürzter Bruch gespeichert werden soll und wir
        # außerhalb die Methode kuerzen niemals ausführen werden.
        ggT = self.gcd(zaehler, nenner)
        # ganzzahlige Division
        return zaehler//ggT, nenner//ggT

    def __add__(self, other):
        '''Methode zum Addieren (a+b), wobei a ein Bruch und b ein Integer 
        oder Bruch ist'''
        if isinstance(other, int):
            other = Bruch(other)
        zaehler = self.zaehler*other.nenner + other.zaehler*self.nenner
        nenner = self.nenner*other.nenner
        return Bruch(zaehler, nenner)

    def __sub__(self, other):
        '''Methode zum Subtrahieren (a-b), wobei a ein Bruch und b ein Integer 
        oder Bruch ist'''
        if isinstance(other, int):
            other = Bruch(other)
        return self + Bruch(-other.zaehler, other.nenner)

    def __truediv__(self, other):
        '''Methode zum Dividieren (a/b), wobei a ein Bruch und b ein Integer 
        oder Bruch ist'''
        if isinstance(other, int):
            other = Bruch(other)
        zaehler = self.zaehler * other.nenner
        nenner = self.nenner * other.zaehler
        return Bruch(zaehler, nenner)

    def __mul__(self, other):
        '''Methode zum Multiplizieren (a*b), wobei a ein Bruch und b ein Integer 
        oder Bruch ist'''
        if isinstance(other, int):
            other = Bruch(other)
        zaehler = self.zaehler * other.zaehler
        nenner = self.nenner * other.nenner
        return Bruch(zaehler, nenner)

    # Umgekehrte Multiplikation, so dass auch Skalar*Bruch funktioniert:
    __rmul__ = __mul__

    # Umgekehrte Addition, so dass auch Skalar+Bruch funktioniert:
    __radd__ = __add__

    def __rsub__(self, other):
        '''Methode für umgekehrte Subtraktion (b-a), wobei a ein Bruch und b
        ein Integer oder Bruch ist'''
        if isinstance(other, int):
            other = Bruch(other)
        return other - self

    def __rtruediv__(self, other):
        '''Methode für umgekehrte Divison b/a, wobei a ein Bruch und b
        ein Integer oder Bruch ist'''
        if isinstance(other, int):
            other = Bruch(other)
        return other / self

    def __eq__(self, other):
        '''Methode zum Prüfen von Gleichheit (a==b), wobei a und b Brüche, 
        Floats oder Integer sein können'''
        if isinstance(other, (int, float)):
            return self.zaehler == self.nenner * other
        else:
            return self.zaehler*other.nenner == other.zaehler*self.nenner

    def __pow__(self, skalar):
        '''Methode zum Potenzieren (a**n) für Bruch a und ganze Zahl n'''
        if isinstance(skalar, int) and skalar >= 0:
            return Bruch(self.zaehler**skalar, self.nenner**skalar)
        elif isinstance(skalar, int) and skalar < 0:
            return Bruch(self.nenner**(-skalar), self.zaehler**(-skalar))
        else:
            raise NotImplementedError

    def tofloat(self):
        '''Methode zum Umwandeln des Bruchs in einen Float '''
        return self.zaehler/self.nenner

    ''' Die folgenden beiden Methoden dienen dazu, dass Sie den Bruch sehen 
    wenn Sie ein Bruch-Objekt in die Konsole tippen oder print() übergeben.
    '''

    def __str__(self):
        return '{:d}/{:d}'.format(self.zaehler, self.nenner)

    def __repr__(self):
        return '{:d}/{:d}'.format(self.zaehler, self.nenner)


if __name__ == '__main__':

    a = Bruch(5, -2)
    b = Bruch(8, 4)
    c = Bruch(0, 9)
    print('\n---Ein paar Tests---\n')
    print('gekürztes b:', b)
    print('Multiplikation a*c:', a*c)
    print('Addition a+b:', a+b)
    print('Addition a+3:', a+3)
    print('Division:', a/b)
    print('a gleich b?', a == b)
    print('b gleich 2?', b == 2)
    print('a hoch -3:', a**(-3))
