"""
This skript shows all calculation methods for the parameter boundaries of a tool
"""

import math
import numpy as np

#defines the number of digits the boundaries are rounded to
ndigits=2


def eta_function(r_q, r_h, a_v, omega):
    # Calculate intermediate values
    cos_omega = np.cos(omega)
    sin_omega = np.sin(omega)
    term1 = r_q * (r_q * cos_omega + a_v) * sin_omega
    term2 = np.sqrt(r_h ** 2 - (r_q * cos_omega + a_v) ** 2)
    numerator = term1 / term2
    denominator = np.sqrt(r_q ** 2 + (numerator) ** 2)

    # Calculate eta
    eta = np.arcsin(numerator / denominator)

    return eta


def find_max_angle(r_h, r_q, a_v, num_points=500):
    # Create an array of omega values from 0 to 2π
    omega_values = np.linspace(0, 2 * np.pi, num_points)

    # Calculate eta for each omega value
    eta_values = eta_function(r_q, r_h, a_v, omega_values)

    # Find the maximum eta value
    max_eta = np.max(eta_values)

    # Find the corresponding omega value for maximum eta
    max_eta_omega = omega_values[np.argmax(eta_values)]

    return max_eta, max_eta_omega

def verschneidungspunkte(r_hb, r_qb, av, n_points):
    # Aktuell nur postiver Achsversatz zulässig
    winkelschritt = 360.0 / n_points
    winkelschritt = 2 * math.pi / n_points
    x = [0] * n_points
    y = [0] * n_points
    z = [0] * n_points
    omega_rad = [0] * n_points
    omega_deg = [0] * n_points
    for i in range(n_points):
        omega_rad[i] = i * winkelschritt
        omega_deg[i] = (i * winkelschritt) * math.pi / 180.0
        h_squared = (r_hb ** 2 - (r_qb * math.cos(omega_rad[i]) + av) ** 2)
        # Für Platte
        # h_squared = r_hb ** 2
        z[i] = math.sqrt(h_squared)
        x[i] = r_qb * math.sin(omega_rad[i])
        y[i] = r_qb * math.cos(omega_rad[i]) + av
    return x, y, z, omega_rad, omega_deg

def verschneidungspunkte_eben(r_qb, z_, n_points):
    # Aktuell nur postiver Achsversatz zulässig
    winkelschritt = 360.0 / n_points
    winkelschritt = 2 * math.pi / n_points
    x = [0] * n_points
    y = [0] * n_points
    z = [0] * n_points
    omega_rad = [0] * n_points
    omega_deg = [0] * n_points
    for i in range(n_points):
        omega_rad[i] = i * winkelschritt
        omega_deg[i] = (i * winkelschritt) / math.pi * 180.0
        # Für Platte
        # h_squared = r_hb ** 2
        z[i] = z_
        x[i] = r_qb * math.sin(omega_rad[i])
        y[i] = r_qb * math.cos(omega_rad[i])
    return x, y, z, omega_rad, omega_deg

def punkte_verschneidung(d_hb, d_qb, av, n_points, ebener_Austritt):
    r_hb = d_hb / 2.0
    r_qb = d_qb / 2.0
    delta_r = 0.01

    if ebener_Austritt:
        x, y, z, omega_rad, omega_deg = verschneidungspunkte_eben(r_qb, 0.0, n_points)
        x_, y_, z_, _, _ = verschneidungspunkte_eben(r_qb + delta_r, 0.0, n_points)
    else:
        x, y, z, omega_rad, omega_deg = verschneidungspunkte(r_hb, r_qb, av, n_points)
        x_, y_, z_, _, _ = verschneidungspunkte(r_hb, r_qb + delta_r, av, n_points)

    kantenvektor_hb_x = [0] * n_points
    kantenvektor_hb_y = [0] * n_points
    kantenvektor_hb_z = [0] * n_points

    for i in range(n_points):
        kantenvektor_hb_x[i] = x_[i] - x[i]
        kantenvektor_hb_y[i] = y_[i] - y[i]
        kantenvektor_hb_z[i] = z_[i] - z[i]
        length = math.sqrt(kantenvektor_hb_x[i] ** 2 + kantenvektor_hb_y[i] ** 2 + kantenvektor_hb_z[i] ** 2)
        kantenvektor_hb_x[i] *= 1 / length
        kantenvektor_hb_y[i] *= 1 / length
        kantenvektor_hb_z[i] *= 1 / length

    if ebener_Austritt:
        x_, y_, z_, _, _ = verschneidungspunkte_eben(r_qb, 0.0 + delta_r, n_points)
    else:
        x_, y_, z_, _, _ = verschneidungspunkte(r_hb + delta_r, r_qb, av, n_points)

    kantenvektor_qb_x = [0] * n_points
    kantenvektor_qb_y = [0] * n_points
    kantenvektor_qb_z = [0] * n_points

    for i in range(n_points):
        kantenvektor_qb_x[i] = x_[i] - x[i]
        kantenvektor_qb_y[i] = y_[i] - y[i]
        kantenvektor_qb_z[i] = z_[i] - z[i]
        length = math.sqrt(kantenvektor_qb_x[i] ** 2 + kantenvektor_qb_y[i] ** 2 + kantenvektor_qb_z[i] ** 2)
        kantenvektor_qb_x[i] *= 1 / length
        kantenvektor_qb_y[i] *= 1 / length
        kantenvektor_qb_z[i] *= 1 / length

    return {
        'x': x,
        'y': y,
        'z': z,
        'kantenvektor_hb_x': kantenvektor_hb_x,
        'kantenvektor_hb_y': kantenvektor_hb_y,
        'kantenvektor_hb_z': kantenvektor_hb_z,
        'kantenvektor_qb_x': kantenvektor_qb_x,
        'kantenvektor_qb_y': kantenvektor_qb_y,
        'kantenvektor_qb_z': kantenvektor_qb_z
    }


def calculate_max_kantenwinkel(d_hb, d_qb, av, ebener_Austritt, n_points=200):
    if (d_hb / 2.0) < (d_qb / 2.0 + av):
        kantenwinkel = [180]
    else:
        vals = punkte_verschneidung(d_hb, d_qb, av, n_points, ebener_Austritt)

        kantenvektor_qb_x = vals['kantenvektor_qb_x']
        kantenvektor_qb_y = vals['kantenvektor_qb_y']
        kantenvektor_qb_z = vals['kantenvektor_qb_z']
        kantenvektor_hb_x = vals['kantenvektor_hb_x']
        kantenvektor_hb_y = vals['kantenvektor_hb_y']
        kantenvektor_hb_z = vals['kantenvektor_hb_z']

        betrag = lambda X: math.sqrt(sum(map(lambda x: x ** 2, X)))
        kantenwinkel = [0] * n_points

        for i in range(n_points):
            vektor1 = [kantenvektor_qb_x[i], kantenvektor_qb_y[i], kantenvektor_qb_z[i]]
            vektor2 = [kantenvektor_hb_x[i], kantenvektor_hb_y[i], kantenvektor_hb_z[i]]
            kantenwinkel[i] = (math.acos(np.dot(vektor1, vektor2) / (betrag(vektor1) * betrag(vektor2))) * 180) / math.pi

    return max(kantenwinkel)


def calcDKopf(d_qb) -> float:
    """Berechne den Durchmesser vom Kopf"""
    return (round(d_qb,ndigits),round(d_qb,ndigits))

def calcLKopf(spitzenwinkel,d_hb,d_qb,av,fb_soll, schneidenwinkel, d_schaft, eberAustritt) -> tuple[float, float]:
    """Berechne den Wertebereich für die Länge des Kopfes"""
    max_kopflaenge = float("inf")
    if d_hb / 2 < (0.5 * d_qb + av + fb_soll):
        max_kopflaenge = 0
    else:
        if not eberAustritt:
            x_rechts = av + 0.5 * d_qb + fb_soll
            max_kopflaenge = 2 * np.sqrt((d_hb / 2) ** 2 - x_rechts ** 2)
            if spitzenwinkel != 0:
                max_kopflaenge -= (d_qb / 2.0) / np.tan(spitzenwinkel/2)
            if schneidenwinkel != 0:
                delta_r = (d_qb - d_schaft) / 2.0
                max_kopflaenge -= (delta_r / np.tan(schneidenwinkel))
    return (0, round(max_kopflaenge,ndigits))

def calcLKopfGesamt(d_hb,d_qb,av,fb_soll,eberAustritt) -> tuple[float, float]:
    """Berechne den Wertebereich für die Länge des Kopfes"""
    max_kopflaenge = float("inf")
    if d_hb / 2 < (0.5 * d_qb + av + fb_soll):
        max_kopflaenge = 0
    else:
        if not eberAustritt:
            x_rechts = av + 0.5 * d_qb + fb_soll
            max_kopflaenge = 2 * np.sqrt((d_hb / 2) ** 2 - x_rechts ** 2)
    return (0, round(max_kopflaenge,ndigits))

def calcDSchaft(d_kopf,schneidenwinkel,fb_soll,Sicherheit) -> tuple[float, float]:
    """Berechne den Wertebereich für den Durchmesser des Schaftes"""
    schneidenwinkel_sicherheit = schneidenwinkel
    if(schneidenwinkel_sicherheit == 0):
        schneidenwinkel_sicherheit = 90
    return (0, round(d_kopf - 2 * math.sin(schneidenwinkel_sicherheit) * fb_soll,ndigits))

def calcLWerkzeug(senkwinkel, spitzenwinkel, kopflaenge, fb, bohrtiefe, d_hb, d_qb, av) -> tuple[float, float]:
    """Berechne den Wertebereich für die Spannutlänge des Werkzeuges"""
    if senkwinkel == 0:
        senkwinkel = 90/180*math.pi
    fb_x = fb * np.sin(senkwinkel)
    bohrtiefe *= -1
    if d_hb / 2 < (d_qb / 2 + av):
        val = 0
    else:
        schnittpunkt_hb_qb = bohrtiefe + np.sqrt((d_hb / 2.0) ** 2 - (d_qb / 2.0 + av + fb_x) ** 2)
        val = 1.5 * d_qb - schnittpunkt_hb_qb
        #val = kopflaenge + 1.5 * d_qb - schnittpunkt_hb_qb
        #if spitzenwinkel != 0:
        #    val += (d_qb / 2.0) / np.tan(spitzenwinkel/2)
    return (round(val,ndigits), float("inf"))

def calcLWerkzeugGesamt(spitzenwinkel, d_kopf, l_kopf, schneidenwinkel, d_schaft, l_hals, l_schaft) -> tuple[float, float]:
    """Berechne den Wertebereich für die Länge des Werkzeuges"""
    # Laenge Spitzenwinkel
    if spitzenwinkel == 0:
        l_spitzenwinkel = 0
    else:
        l_spitzenwinkel = (d_kopf / 2.0) / np.tan((spitzenwinkel / 2))

    # Laenge Senkschneide
    delta_r = (d_kopf - d_schaft) / 2.0
    if schneidenwinkel == 0:
        l_schneide = 0
    else:
        l_schneide = delta_r / np.tan(schneidenwinkel)

    value = round(l_spitzenwinkel + l_kopf + l_schneide + l_hals + l_schaft, 2)

    return(value ,float("inf"))

def calcLSchaft() -> tuple[float, float]:
    """Berechne den Wertebereich für die Länge des Werkzeuges"""
    return(0,float("inf"))

def calcDSchaft2() -> tuple[float, float]:
    """Berechne den Wertebereich für die Länge des Werkzeuges"""
    return(0,float("inf"))

def calcSpitzenwinkel() -> tuple[float, float]:
    """Berechne den Wertebereich für den Spitzenwinkel"""
    return (0, 180)

def calcSchneidewinkel(d_hb, d_qb, av, ebener_Austritt) -> tuple[float, float]:
    """Berechne den Wertebereich für den Senkwinkel"""
    if d_hb / 2 < (d_qb / 2 + av) and not ebener_Austritt:
        max_angle_deg = 90
    else:
        max_kantenwinkel = calculate_max_kantenwinkel(d_hb, d_qb, av, ebener_Austritt)
        max_angle_deg = round(180-max_kantenwinkel, ndigits)
    return (0, max_angle_deg)

def calcFreiwinkel(d_hb, d_qb, av) -> tuple[float, float]:
    """Berechne den Wertebereich für den Freiwinkel"""
    max_angle, _ = find_max_angle(d_hb / 2.0, d_qb / 2.0, av)
    max_angle_deg = round(max_angle/np.pi * 180, ndigits)
    return (max_angle_deg, 90)

def calcWinkeloffset() -> tuple[float, float]:
    """Berechne den Wertebereich für den Winkeloffset"""
    return (-360, 360)

