from typing import Any

import sympy as sp
from decimal import Decimal
import math
import numpy as np
from numpy import ndarray, dtype

import Datenbank

class Berechnungen:
    def __init__(self, a: float, b: float) -> None:
        self.a_wert = a
        self.b_wert = b
        self.e_quadrat_wert = self.e_quadrat()
        self.e_strich_quadrat_wert = self.e_strich_quadrat()

    def e_quadrat(self) -> float:
        return (self.a_wert**2 - self.b_wert**2) / self.a_wert **2

    def e_strich_quadrat(self) -> float:
        return (self.a_wert**2 - self.b_wert**2) / self.b_wert **2

    def P(self, x: float, y: float) -> float:
        return np.sqrt(x**2 + y**2)

    def hilfswinkel(self, x: float, y: float, z: float) -> float:
        hw = np.atan2(z * self.a_wert, self.P(x, y) * self.b_wert)
        return hw

    def B(self, x: float, y: float, z: float) -> float:
        hilfswinkel = self.hilfswinkel(x, y, z)
        B = np.atan2((z + self.e_strich_quadrat_wert * self.b_wert * np.sin(hilfswinkel) ** 3), (self.P(x, y) - self.e_quadrat_wert * self.a_wert * np.cos(hilfswinkel) ** 3))
        return B

    def L(self, x: float, y: float) -> float:
        return np.atan2(y, x)

    def H(self, x: float, y: float, z: float) -> float:
        B = self.B(x, y, z)
        H = (self.P(x, y) / np.cos(B)) - self.a_wert / (np.sqrt(1 - self.e_quadrat_wert * np.sin(B) ** 2))
        return H

    def E(self, L: float, dX: float, dY: float) -> float:
        E = -np.sin(L) * dX + np.cos(L) * dY
        return E

    def N(self, B: float, L: float, dX: float, dY: float, dZ: float) -> float:
        N = -np.sin(B) * np.cos(L) * dX - np.sin(B) * np.sin(L) * dY + np.cos(B) * dZ
        return N

    def U(self, B: float, L: float, dX: float, dY: float, dZ: float) -> float:
        U = np.cos(B) * np.cos(L) * dX + np.cos(B) * np.sin(L) *dY + np.sin(B) * dZ
        return U

    def horizontalstrecke_ENU(self, E: float, N: float) -> float:
        s = np.sqrt(E**2 + N**2)
        return s

    def Zenitwinkel(self, horizontalstrecke_ENU: float, U: float) -> float:
        zw = np.atan2(horizontalstrecke_ENU, U)
        return zw

    def Azimut(self, E: float, N: float) -> float:
        Azimut = np.atan2(E, N)
        if Azimut < 0:
            Azimut += 2 * np.pi
        if Azimut > 2 * np.pi:
            Azimut -= 2 * np.pi
        return Azimut

    def Richtung(self, Azimut: float, Orientierung: float) -> float:
        Richtung = Azimut - Orientierung
        if Richtung < 0:
            Richtung += 2 * np.pi
        if Richtung > 2 * np.pi:
            Richtung -= 2 * np.pi
        return Richtung

    def geometrische_breite_laenge(self, dict_koordinaten: dict) -> dict:
        for punktnummer, matrix in dict_koordinaten.items():

            dict_koordinaten[punktnummer] = [matrix, self.B(matrix[0], matrix[1], matrix[2]), self.L(matrix[0], matrix[1])]
        return dict_koordinaten

    def berechnung_richtung_azimut_zenitwinkel(self, pfad_datenbank: str, dict_koordinaten: dict) -> tuple[list[Any], dict[Any, Any]]:
        dict_koordinaten_erweitert = {}
        dict_orientierungen = {}
        liste_azimut_richtungen = []
        db_zugriff = Datenbank.Datenbankzugriff(pfad_datenbank)
        liste_beobachtungen_tachymeter = db_zugriff.get_beobachtungen_from_beobachtungenid()

        for punktnummer, koordinaten in dict_koordinaten.items():
            X = float(koordinaten[0])
            Y = float(koordinaten[1])
            Z = float(koordinaten[2])

            P = self.P(X, Y)
            hilfwinkel = self.hilfswinkel(X, Y, Z)
            B = self.B(X, Y, Z)
            L = self.L(X, Y)
            H = self.H(X, Y, Z)

            dict_koordinaten_erweitert[punktnummer] = koordinaten, P, hilfwinkel, B, L, H

        beobachtsgruppeID_vorher = None
        for beobachtung_tachymeter in liste_beobachtungen_tachymeter:
            standpunkt = beobachtung_tachymeter[0]
            zielpunkt = beobachtung_tachymeter[1]

            X1 = dict_koordinaten[beobachtung_tachymeter[0]][0]
            X2 = dict_koordinaten[beobachtung_tachymeter[1]][0]
            Y1 = dict_koordinaten[beobachtung_tachymeter[0]][1]
            Y2 = dict_koordinaten[beobachtung_tachymeter[1]][1]
            Z1 = dict_koordinaten[beobachtung_tachymeter[0]][2]
            Z2 = dict_koordinaten[beobachtung_tachymeter[1]][2]

            dX = float(X2 - X1)
            dY = float(Y2 - Y1)
            dZ = float(Z2 - Z1)

            schraegstrecke = np.sqrt(dX ** 2 + dY ** 2 + dZ ** 2)

            B = float(dict_koordinaten_erweitert[beobachtung_tachymeter[0]][3])
            L = float(dict_koordinaten_erweitert[beobachtung_tachymeter[0]][4])

            E = self.E(L, dX, dY)
            N = self.N(B, L, dX, dY, dZ)
            U = self.U(B, L, dX, dY, dZ)

            horizontalstrecke_ENU = self.horizontalstrecke_ENU(E, N)
            Azimut = float(self.Azimut(E, N))
            Zenitwinkel = float(self.Zenitwinkel(horizontalstrecke_ENU, U))

            beobachtsgruppeID_aktuell = beobachtung_tachymeter[3]
            if beobachtsgruppeID_aktuell == beobachtsgruppeID_vorher:
                richtung = float(self.Richtung(Azimut, orientierung))
                liste_azimut_richtungen.append((beobachtsgruppeID_aktuell, standpunkt, zielpunkt, Azimut, richtung, Zenitwinkel, schraegstrecke, orientierung))

            else:
                orientierung = Azimut
                dict_orientierungen[beobachtsgruppeID_aktuell] = orientierung

                richtung = float(self.Richtung(Azimut, orientierung))
                liste_azimut_richtungen.append((beobachtsgruppeID_aktuell, standpunkt, zielpunkt, Azimut, richtung, Zenitwinkel, schraegstrecke, orientierung))

            beobachtsgruppeID_vorher = beobachtsgruppeID_aktuell
        return liste_azimut_richtungen, dict_orientierungen

    def berechne_zenitwinkel_distanz_bodenbezogen(self, zenitwinkel_messung: float, schraegdistanz_messung: float,
                                                  instrumentenhoehe: float, prismenhoehe: float):
        HD = np.sin(np.pi - zenitwinkel_messung) * schraegdistanz_messung
        delta_h_ihzh = schraegdistanz_messung * np.cos(zenitwinkel_messung)
        delta_h_boden = delta_h_ihzh + instrumentenhoehe - prismenhoehe
        schraegdistanz_boden = np.sqrt(HD ** 2 + delta_h_boden ** 2)
        zw_boden = np.atan2(HD, delta_h_boden)
        return schraegdistanz_boden, zw_boden


class Einheitenumrechnung:
    def __init__(self) -> None:
        pass

    def mas_to_rad(mas: float) -> float:
        umrechnungsfaktor = 1 / 1000 * 1 / 3600 * sp.pi / 180
        grad = mas * umrechnungsfaktor
        return grad

    def mm_to_m(mm: float) -> float:
        m = mm / 1000
        return m

    def ppb(ppb: float) -> float:
        ppb *= 10 ** (-9)
        return ppb

    def gon_to_rad_Decimal(gon: float) -> float:
        gon = Decimal(gon)
        pi = Decimal(str(math.pi))
        rad = (gon / Decimal(200)) * pi
        return rad

    def mgon_to_rad_Decimal(gon: float) -> float:
        gon = Decimal(gon)
        pi = Decimal(str(math.pi))
        rad = (gon / Decimal(200000)) * pi
        return rad