#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Oct 28 18:24:15 2024

"""

import numbers


class Polynom:

    def __init__(self, dp, Alg):

        # Überprüfe, ob Exponenten gültige Werte haben:
        assert all(isinstance(x, int) and x >= 0 for x in dp.keys()), \
            "Die Exponenten (keys) müssen natürliche Zahlen (einschl. 0) sein."
        self.dp = dp
        self.Alg = Alg
        self.degree = max(self.dp.keys())
        # Teste ob Koeffizienten im Koerper der Polynomgruppe liegen
        Alg.validate(self)

#__str__ stellt nun das Polynom mit auf drei Nackkommastellen gerundeten
#   Koeffizienten dar, sofern es sich um Floats handelt, sonst sind die
#   Koeffizienten Integer. Unnötige Zeichen(folgen) werden gestrichen und
#   um den Exponenten werden geschweifte Klammern gesetzt
    def __str__(self):
        polystr = ''
        for k, pk in reversed(sorted(self.dp.items())):
            if isinstance(pk, complex):  # Klammern um komplexe Zahl
                polystr = polystr + f'+({pk:.3f})*X^{{{k}}}'
            elif round(pk) == pk:
                polystr = polystr + f'+{pk:g}*X^{{{k}}}'
            else:
                polystr = polystr + f'+{pk:.3f}*X^{{{k}}}'
        replacements = {"*X^{0}": "", "X^{1}": "X",
                        "+-1*": "-", "+1*": "+", "+-": "-"}
        for k, v in replacements.items():
            polystr = polystr.replace(k, v)
        polystr = polystr.lstrip('+')

        return f'{__class__.__name__} {polystr}'

    __repr__ = __str__

    def __add__(self, other):
        return self.__class__(self.Alg.add(self, other), self.Alg)

# hier werden auch noch - , * und == definiert 
    def __sub__(self, other):
        return self.__class__(self.Alg.minus(self, other), self.Alg)

    def __mul__(self, other):
        return self.__class__(self.Alg.mul(self, other), self.Alg)

    def __neg__(self):
        return self.__class__(self.Alg.neg(self), self.Alg)

    def __eq__(self, other):
        return self.dp == other.dp and self.Alg == other.Alg


class PolyGruppe:

    def __init__(self, ring_str):
        self.ring_str = ring_str
        if ring_str == 'C':
            self.ring = numbers.Complex  # komplexe Zahlen
        elif ring_str == 'R':
            self.ring = numbers.Real  # reelle Zahlen
        elif ring_str == 'Z':  # ganze Zahlen
            self.ring = numbers.Integral
        else:
            raise ValueError(
                f"{ring_str} nicht erlaubt 'ring' muss 'C' or 'R' or 'Z' sein")

    def __repr__(self):
        return f" ({self.ring_str}[X],+) "

    def __call__(self, dp):
        return Polynom(dp, self)

    def __eq__(self, other):
        return self.ring == other.ring

    def add(self, p, q):
        erg = {n: p.dp.get(n, 0) + q.dp.get(n, 0)
               for n in set(p.dp) | set(q.dp)}
        return erg

# diese drei methoden sind ebenfalls neu:
    def minus(self, p, q):
        erg = {n: p.dp.get(n, 0) - q.dp.get(n, 0)
               for n in set(p.dp) | set(q.dp)}
        return erg

    def neg(self, p):
        erg = {k: -v for k, v in p.dp.items()}
        return erg

    def validate(self, p):
        assert all((isinstance(v, self.ring) for v in p.dp.values())), \
            f"Alle Koeffizienten von {p} in {
                self} müssen in '{self.ring_str}' sein"


class PolyRing(PolyGruppe):

    def __call__(self, dp):
        return Polynom(dp, self)

    def __repr__(self):
        return f'Polynome als Ring über {self.ring.__name__}'

    def mul(self, p, q):
        # TODO
        pass


if __name__ == '__main__':
    print('\n   xxx POLYGRUPPE xxx \n')
    
    #poly_gr_r = PolyGruppe('N')
    
    poly_gr_c = PolyGruppe('C')

    # Im Vergleich zur Version der Vorlesung
    # werden Objekte der Klasse Polynom jetzt durch 
    # einen Aufruf 'call' eines Objekts der PolyGruppe erzeugt.
    
    pg = poly_gr_c({0: 1, 1: 1})

    print(f'p {pg}')

    qg = poly_gr_c({0: -1, 1: 1})
    print(f'q {qg}')

    print(f'Test für Gleichheit \n{pg} {qg} {pg == qg}')

    
    try:
        poly_gr_r = PolyGruppe('Z')
        pg = poly_gr_r({0: 1j, 1: 1})
        print(f' p {pg}')
    except BaseException as e:
        print(f'{e}')

    print(f'Summe p+q {pg+qg}')
    print(f'Subtraktion p-q {pg-qg}')
    print(f'Inverses von {pg} : {-pg}')
    try:
        print(f'Produkt p*q {pg*qg}')
    except BaseException as e:
        print(
            f'{e} \np*q ist daher in der {poly_gr_r.__class__.__name__} nicht definiert')

    print('\n   xxx POLYRING  xxx \n')
    poly_ring = PolyRing('C')
    pr = poly_ring({0: 1, 1: 1})
    print(f'p {pr}')
    qr = poly_ring({0: -1, 1: 1})
    print(f'q {qr}')

    print(f'Summe p+q {pr+qr}')
    print(f'Subtraktion p-q {pr-qr}')
    print(f'Additives Inverses {-pr} von {pr}')
    # Das sollte dann funktionieren.
    # print(f'Produkt p*q {pr*qr}')
