import sympy as sp
from sympy.algebras.quaternion import Quaternion
import Datenbank
from itertools import combinations


class Transformationen:
    def __init__(self, pfad_datenbank):
        self.pfad_datenbank = pfad_datenbank

    @staticmethod
    def R_matrix_aus_euler(e1, e2, e3):
        return sp.Matrix([
            [
                sp.cos(e2) * sp.cos(e3),
                sp.cos(e1) * sp.sin(e3) + sp.sin(e1) * sp.sin(e2) * sp.cos(e3),
                sp.sin(e1) * sp.sin(e3) - sp.cos(e1) * sp.sin(e2) * sp.cos(e3)
            ],
            [
                -sp.cos(e2) * sp.sin(e3),
                sp.cos(e1) * sp.cos(e3) - sp.sin(e1) * sp.sin(e2) * sp.sin(e3),
                sp.sin(e1) * sp.cos(e3) + sp.cos(e1) * sp.sin(e2) * sp.sin(e3)
            ],
            [
                sp.sin(e2),
                -sp.sin(e1) * sp.cos(e2),
                sp.cos(e1) * sp.cos(e2)
            ]
        ])

    def Helmerttransformation_Euler_Transformationsparameter_berechne(self):
        db = Datenbank.Datenbankzugriff(self.pfad_datenbank)
        dict_ausgangssystem = db.get_koordinaten("naeherung_lh", "Dict")
        dict_zielsystem = db.get_koordinaten("naeherung_us", "Dict")

        gemeinsame_punktnummern = sorted(set(dict_ausgangssystem.keys()) & set(dict_zielsystem.keys()))
        anzahl_gemeinsame_punkte = len(gemeinsame_punktnummern)

        liste_punkte_ausgangssystem = [dict_ausgangssystem[i] for i in gemeinsame_punktnummern]
        liste_punkte_zielsystem = [dict_zielsystem[i] for i in gemeinsame_punktnummern]

        print("Anzahl gemeinsame Punkte:", anzahl_gemeinsame_punkte)

        print("\nErste Zielpunkte:")
        for pn, P in list(zip(gemeinsame_punktnummern, liste_punkte_zielsystem))[:5]:
            print(pn, [float(P[0]), float(P[1]), float(P[2])])

        print("\nErste Ausgangspunkte:")
        for pn, p in list(zip(gemeinsame_punktnummern, liste_punkte_ausgangssystem))[:5]:
            print(pn, [float(p[0]), float(p[1]), float(p[2])])

        # --- Näherungswerte (minimal erweitert) ---
        p1, p2, p3 = liste_punkte_ausgangssystem[0], liste_punkte_ausgangssystem[1], liste_punkte_ausgangssystem[2]
        P1, P2, P3 = liste_punkte_zielsystem[0], liste_punkte_zielsystem[1], liste_punkte_zielsystem[2]

        # 1) Näherungswert Maßstab: Mittelwert aus allen Punktpaaren
        ratios = []
        for i, j in combinations(range(anzahl_gemeinsame_punkte), 2):
            dp = (liste_punkte_ausgangssystem[j] - liste_punkte_ausgangssystem[i]).norm()
            dP = (liste_punkte_zielsystem[j] - liste_punkte_zielsystem[i]).norm()
            dp_f = float(dp)
            if dp_f > 0:
                ratios.append(float(dP) / dp_f)

        m0 = sum(ratios) / len(ratios)

        if ratios:
            print("min/mean/max:",
                  min(ratios),
                  sum(ratios) / len(ratios),
                  max(ratios))

        U = (P2 - P1) / (P2 - P1).norm()
        W = (U.cross(P3 - P1)) / (U.cross(P3 - P1)).norm()
        V = W.cross(U)

        u = (p2 - p1) / (p2 - p1).norm()
        w = (u.cross(p3 - p1)) / (u.cross(p3 - p1)).norm()
        v = w.cross(u)

        R0 = sp.Matrix.hstack(U, V, W) * sp.Matrix.hstack(u, v, w).T

        XS = sum(liste_punkte_zielsystem, sp.Matrix([0, 0, 0])) / anzahl_gemeinsame_punkte
        xS = sum(liste_punkte_ausgangssystem, sp.Matrix([0, 0, 0])) / anzahl_gemeinsame_punkte

        Translation0 = XS - m0 * R0 * xS

        # 2) Test auf orthonormale Drehmatrix bei 3 Nachkommastellen!
        if R0.T.applyfunc(lambda x: round(float(x), 3)) == R0.inv().applyfunc(lambda x: round(float(x), 3)) \
                and (R0.T * R0).applyfunc(lambda x: round(float(x), 3)) == sp.eye(3).applyfunc(
            lambda x: round(float(x), 3)) \
                and ((round(R0.det(), 3) == 1.000 or round(R0.det(), 3) == -1.000)):
            print("R ist Orthonormal!")
        else:
            print("R ist nicht Orthonormal!")

        # 3) Euler-Näherungswerte aus R0
        e2_0 = sp.asin(R0[2, 0])
        # Schutz gegen Division durch 0 wenn cos(e2) ~ 0:
        cos_e2_0 = sp.cos(e2_0)

        e1_0 = sp.acos(R0[2, 2] / cos_e2_0)
        e3_0 = sp.acos(R0[0, 0] / cos_e2_0)

        # --- Symbolische Unbekannte (klassische 7 Parameter) ---
        dX, dY, dZ, m, e1, e2, e3 = sp.symbols('dX dY dZ m e1 e2 e3')
        R_symbolisch = self.R_matrix_aus_euler(e1, e2, e3)

        # 4) Funktionales Modell
        f_zeilen = []
        for punkt in liste_punkte_ausgangssystem:
            punkt_vektor = sp.Matrix([punkt[0], punkt[1], punkt[2]])
            f_zeile_i = sp.Matrix([dX, dY, dZ]) + m * R_symbolisch * punkt_vektor
            f_zeilen.extend(list(f_zeile_i))

        f_matrix = sp.Matrix(f_zeilen)
        f = f_matrix

        A_ohne_zahlen = f_matrix.jacobian([dX, dY, dZ, m, e1, e2, e3])

        # Parameterschätzung
        schwellenwert = 1e-4
        anzahl_iterationen = 0
        alle_kleiner_vorherige_iteration = False

        l_vektor = sp.Matrix([koord for P in liste_punkte_zielsystem for koord in P])
        l = l_vektor

        P_mat = sp.eye(3 * anzahl_gemeinsame_punkte)
        l_berechnet_0 = None

        while True:
            if anzahl_iterationen == 0:
                zahlen_0 = {
                    dX: float(Translation0[0]),
                    dY: float(Translation0[1]),
                    dZ: float(Translation0[2]),
                    m: float(m0),
                    e1: float(e1_0),
                    e2: float(e2_0),
                    e3: float(e3_0)
                }

                x0 = sp.Matrix([zahlen_0[dX], zahlen_0[dY], zahlen_0[dZ],
                                zahlen_0[m], zahlen_0[e1], zahlen_0[e2], zahlen_0[e3]])

                l_berechnet_0 = f.subs(zahlen_0).evalf(n=30)
                dl_0 = l_vektor - l_berechnet_0

                A_0 = A_ohne_zahlen.subs(zahlen_0).evalf(n=30)
                N = A_0.T * P_mat * A_0
                n_0 = A_0.T * P_mat * dl_0
                Qxx_0 = N.inv()
                dx = Qxx_0 * n_0
                x = x0 + dx
                x = sp.N(x, 30)

                anzahl_iterationen += 1
                print(f"Iteration Nr.{anzahl_iterationen} abgeschlossen")
                print(dx.evalf(n=3))

            else:
                zahlen_i = {
                    dX: float(x[0]),
                    dY: float(x[1]),
                    dZ: float(x[2]),
                    m: float(x[3]),
                    e1: float(x[4]),
                    e2: float(x[5]),
                    e3: float(x[6])
                }

                l_berechnet_i = f.subs(zahlen_i).evalf(n=30)
                dl_i = l_vektor - l_berechnet_i

                A_i = A_ohne_zahlen.subs(zahlen_i).evalf(n=30)
                N_i = A_i.T * P_mat * A_i
                Qxx_i = N_i.inv()
                n_i = A_i.T * P_mat * dl_i

                dx = Qxx_i * n_i
                x = sp.Matrix(x + dx)

                anzahl_iterationen += 1
                print(f"Iteration Nr.{anzahl_iterationen} abgeschlossen")
                print(dx.evalf(n=3))

            alle_kleiner = True
            for i in range(dx.rows):
                wert = float(dx[i])
                if abs(wert) > schwellenwert:
                    alle_kleiner = False

            if (alle_kleiner and alle_kleiner_vorherige_iteration) or anzahl_iterationen == 100:
                break

            alle_kleiner_vorherige_iteration = alle_kleiner

        print(l.evalf(n=3))
        print(l_berechnet_0.evalf(n=3))
        print(f"x = {x.evalf(n=3)}")

        # --- Neuberechnung Zielsystem ---
        zahlen_final = {
            dX: float(x[0]),
            dY: float(x[1]),
            dZ: float(x[2]),
            m: float(x[3]),
            e1: float(x[4]),
            e2: float(x[5]),
            e3: float(x[6])
        }

        l_berechnet_final = f.subs(zahlen_final).evalf(n=30)

        liste_l_berechnet_final = []
        for i in range(anzahl_gemeinsame_punkte):
            Xi = l_berechnet_final[3 * i + 0]
            Yi = l_berechnet_final[3 * i + 1]
            Zi = l_berechnet_final[3 * i + 2]
            liste_l_berechnet_final.append(sp.Matrix([Xi, Yi, Zi]))

        print("")
        print("l_berechnet_final:")
        for punktnummer, l_fin in zip(gemeinsame_punktnummern, liste_l_berechnet_final):
            print(f"{punktnummer}: {float(l_fin[0]):.3f}, {float(l_fin[1]):.3f}, {float(l_fin[2]):.3f}")

        print("Streckendifferenzen:")
        streckendifferenzen = [
            (punkt_zielsys - l_final).norm()
            for punkt_zielsys, l_final in zip(liste_punkte_zielsystem, liste_l_berechnet_final)
        ]
        print([round(float(s), 6) for s in streckendifferenzen])

        Schwerpunkt_Zielsystem = sum(liste_punkte_zielsystem, sp.Matrix([0, 0, 0])) / anzahl_gemeinsame_punkte
        Schwerpunkt_berechnet = sum(liste_l_berechnet_final, sp.Matrix([0, 0, 0])) / anzahl_gemeinsame_punkte

        Schwerpunktsdifferenz = Schwerpunkt_Zielsystem - Schwerpunkt_berechnet

        print("\nDifferenz Schwerpunkt (Vektor):")
        print(Schwerpunktsdifferenz.evalf(3))

        print("Betrag der Schwerpunkt-Differenz:")
        print(f"{float(Schwerpunktsdifferenz.norm()):.3f}m")

        return zahlen_final

    def Helmerttransformation(self, transformationsparameter):
        db = Datenbank.Datenbankzugriff(self.pfad_datenbank)
        dict_ausgangssystem = db.get_koordinaten("naeherung_lh", "Dict")
        dict_zielsystem = db.get_koordinaten("naeherung_us", "Dict")

        dX, dY, dZ, m, e1, e2, e3 = sp.symbols('dX dY dZ m e1 e2 e3')

        unterschiedliche_punktnummern = sorted(set(dict_ausgangssystem.keys()) ^ set(dict_zielsystem.keys()))
        punktnummern_transformieren = [
            punktnummer for punktnummer in unterschiedliche_punktnummern if punktnummer in dict_ausgangssystem
        ]
        liste_punkte_ausgangssystem = [dict_ausgangssystem[punktnummer] for punktnummer in punktnummern_transformieren]

        R = self.R_matrix_aus_euler(transformationsparameter[e1], transformationsparameter[e2], transformationsparameter[e3])

        f_zeilen = []
        for punkt in liste_punkte_ausgangssystem:
            punkt_vektor = sp.Matrix([punkt[0], punkt[1], punkt[2]])
            f_zeile_i = sp.Matrix([transformationsparameter[dX], transformationsparameter[dY], transformationsparameter[dZ]]) + transformationsparameter[m] * R * punkt_vektor
            f_zeilen.extend(list(f_zeile_i))

        f_matrix = sp.Matrix(f_zeilen)
        dict_transformiert = {}
        for i, pn in enumerate(punktnummern_transformieren):
            Xi = f_matrix[3 * i + 0]
            Yi = f_matrix[3 * i + 1]
            Zi = f_matrix[3 * i + 2]

            dict_transformiert[str(pn)] = sp.Matrix([
                [float(Xi)],
                [float(Yi)],
                [float(Zi)]
            ])
        return dict_transformiert

