# Copyright (c) 2025 Martin Kayser (tebarius)
# Licensed under the MIT License. See LICENSE file in the project root.
import math
from re import match  # für unkennify

import folium
import streamlit as st
from streamlit_folium import st_folium


# ***recursive quersummenfunktion***
def q_sum(n):
    if n < 10:
        return n
    else:
        n = q_sum(n // 10) + n % 10
        return n

# ***recursive iterierte quersummenfunktion***
def iq_sum(n):
    if n < 10:
        return n
    else:
        n = iq_sum(q_sum(n))
        return n

# ***produziert eine Liste mit Primzahlen kleiner als grenze***
# ***nach dem Prinzip des "Sieb des Eratosthenes"***
@st.cache_resource
def primzahlliste(grenze):
    primes = []
    is_prime = [True] * grenze
    is_prime[0] = False
    is_prime[1] = False
    for i in range(2, int(math.sqrt(grenze))):
        if is_prime[i]:
            for j in range((i * i), grenze, i):
                is_prime[j] = False
    for i in range(len(is_prime)):
        if is_prime[i]:
            primes.append(i)
    return primes

# ***alle permutationen generieren***
def all_perms(liste):
    if len(liste) <= 1:
        yield liste
    else:
        for perm in all_perms(liste[1:]):
            for i in range(len(perm) + 1):
                yield perm[:i] + liste[0:1] + perm[i:]

# ***zählfunktionen für anagramm suche
def buchstabenzaehl(buchstabe, anzahl):
    if buchstabe in anzahl:
        anzahl[buchstabe] = anzahl[buchstabe] + 1
    else:
        anzahl[buchstabe] = 1

def wortzaehl(wort):
    anzahl = {}
    for buchstabe in wort:
        buchstabenzaehl(buchstabe.upper(), anzahl)
    return anzahl

# ***Funktionen für Kachelkoordinaten/Maptiles-Funktion
def dec_to_maptiles(lat_deg, lon_deg, zoom):
    lat_rad = math.radians(lat_deg)
    n = 2.0 ** zoom
    xtile = int((lon_deg + 180.0) / 360.0 * n)
    ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
    return xtile, ytile

def maptiles_to_dec(xtile, ytile, zoom):
    n = 2.0 ** zoom
    lon_dec = xtile / n * 360.0 - 180.0
    lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
    lat_dec = math.degrees(lat_rad)
    return lat_dec, lon_dec

# ***Dezimalgrad in Dezimalminuten (Ausgabe als String)
def dec_to_deg(lat_dec, lon_dec):
    if lat_dec < 0:
        lat_pre = "S"
    else:
        lat_pre = "N"
    if lon_dec < 0:
        lon_pre = "W"
    else:
        lon_pre = "E"
    lat = abs(lat_dec)
    lon = abs(lon_dec)
    lat_degree = int(lat)
    lon_degree = int(lon)
    lat_minutes = (lat - lat_degree) * 60
    lon_minutes = (lon - lon_degree) * 60
    return ("{}{} {:.3f} {}{} {:.3f}".format(lat_pre, lat_degree, round(lat_minutes, 3), lon_pre, lon_degree,
                                             round(lon_minutes, 3)))

# ***ReverseWherIGo zu Dezimalgrad
def rwig_to_coords(a, b, c):
    lat, lon = 0, 0
    a = int(a)
    b = int(b)
    c = int(c)
    if (a % 1000 - a % 100) / 100 == 1:
        lat_sign = 1
        lon_sign = 1
    elif (a % 1000 - a % 100) / 100 == 2:
        lat_sign = -1
        lon_sign = 1
    elif (a % 1000 - a % 100) / 100 == 3:
        lat_sign = 1
        lon_sign = -1
    elif (a % 1000 - a % 100) / 100 == 4:
        lat_sign = -1
        lon_sign = -1
    else:
        return 0, 0
    if ((c % 100000 - c % 10000) / 10000 + (c % 100 - c % 10) / 10) % 2 == 0:
        lat = lat_sign * (
                (a % 10000 - a % 1000) / 100
                + (b % 100 - b % 10) / 10
                + (b % 100000 - b % 10000) / 100000
                + (c % 1000 - c % 100) / 10000
                + (a % 1000000 - a % 100000) / 100000000
                + (c % 100 - c % 10) / 100000
                + a % 10 * 1.0E-5)
    elif ((c % 100000 - c % 10000) / 10000 + (c % 100 - c % 10) / 10) % 2 != 0:
        lat = lat_sign * (
                (b % 1000000 - b % 100000) / 10000
                + a % 10 + (a % 10000 - a % 1000) / 10000
                + (c % 1000000 - c % 100000) / 10000000
                + (c % 1000 - c % 100) / 100000
                + (c % 100 - c % 10) / 100000
                + (a % 1000000 - a % 100000) / 10000000000)
    if ((c % 100000 - c % 10000) / 10000 + (c % 100 - c % 10) / 10) % 2 == 0:
        lon = lon_sign * (
                (a % 100000 - a % 10000) / 100
                + (c % 1000000 - c % 100000) / 10000
                + c % 10 + (b % 1000 - b % 100) / 1000
                + (b % 1000000 - b % 100000) / 10000000
                + (a % 100 - a % 10) / 10000
                + (c % 100000 - c % 10000) / 100000000
                + b % 10 * 1.0E-5)
    elif ((c % 100000 - c % 10000) / 10000 + (c % 100 - c % 10) / 10) % 2 != 0:
        lon = lon_sign * (
                (b % 100 - b % 10) * 10
                + c % 10 * 10
                + (a % 100 - a % 10) / 10
                + (a % 100000 - a % 10000) / 100000
                + (b % 1000 - b % 100) / 10000
                + b % 10 * 0.001
                + (c % 100000 - c % 10000) / 100000000
                + (b % 100000 - b % 10000) / 1000000000)
    return round(lat, 5), round(lon, 5)

def unkennify(text):
    # Funktion hier entnommen und angepasst: https://www.namesuppressed.com/software/kenny.py
    decoded = ''
    codemap = str.maketrans('MmPpFf', '001122')
    n_len = len(text)
    i = 0
    while i + 3 <= n_len:
        if match('[MmPpFf]{3,}', text[i:i + 3]):
            num = int(text[i:i + 3].translate(codemap), 3)  # 'mpf' -> '012' -> 5
            cypher = chr(num + ord('a'))  # 5 -> 'f'
            if text[i].isupper():
                cypher = cypher.upper()
            decoded = decoded + cypher
            i = i + 3
        else:
            decoded = decoded + text[i]
            i = i + 1
    if i < n_len:
        decoded = decoded + text[i:]
    return decoded

def rail_encrypt(plain_text: str, rails: int):
    arr = [["" for _ in range(len(plain_text))] for _ in range(rails)]
    r = 0
    z = 0
    plus = True
    for b in plain_text:
        arr[r][z] = b
        z += 1
        if r + 1 == rails and plus:
            plus = False
            r -= 1
        elif r - 1 < 0 and not plus:
            plus = True
            r += 1
        elif plus:
            r += 1
        else:
            r -= 1
    out = ""
    for i in range(rails):
        for j in range(len(plain_text)):
            out += arr[i][j]
    return out

def rail_decrypt(cipher: str, rails: int):
    arr = [["_" for _ in range(len(cipher))] for _ in range(rails)]
    # cipher ins array reinbasteln
    x, y = 0, 0
    first_x = True
    for b in cipher:
        if x >= len(cipher) and y < rails:
            y += 1
            x = y
            first_x = True
        arr[y][x] = b
        if y == 0 or (first_x and y != rails - 1):
            x = x + (rails - y - 1) * 2
            first_x = False
        elif y == rails - 1 or first_x is False:
            x = x + (y * 2)
            first_x = True
    # dekodierten Text aus array holen
    out = ""
    x, y = 0, 0
    down = True
    for _ in range(len(cipher)):
        out += arr[y][x]
        x += 1
        if down and y + 1 == rails:
            down = False
            y -= 1
        elif down:
            y += 1
        elif not down and y == 0:
            down = True
            y += 1
        elif not down:
            y -= 1
    return out
#from folium.map import DivIcon
def show_map_folium(df):
    if not df.empty:
        # Schritt 1: Bestimme die südwestliche und nordöstliche Ecke
        sw = df[['lat', 'lon']].min().values.tolist()   # Südwesten
        ne = df[['lat', 'lon']].max().values.tolist()   # Nordosten
        # Schritt 2: Initialisiere Karte (Ort egal, wird überschrieben, zoom wird bei mehreren Koordinaten überschrieben)
        m = folium.Map(location=df[["lat", "lon"]].mean().values.tolist(), zoom_start=15)
        # Schritt 3: Marker hinzufügen
        for _, row in df.iterrows():
            if 'label' in row:
                folium.Marker(
                    location=[row["lat"], row["lon"]],
                    popup=f"<b>{int(row['label'])}</b><br> Lat:{round(row['lat'], 5)}, Lon:{round(row['lon'], 5)}",
                    tooltip=f"<b>{int(row['label'])}</b><br> Lat:{round(row['lat'], 5)}, Lon:{round(row['lon'], 5)}",
                    icon = folium.Icon(color="red", icon="map-marker")
                ).add_to(m)
            else:
                folium.Marker(
                    location=[row["lat"], row["lon"]],
                    popup=f"Lat:{round(row['lat'], 5)}, Lon:{round(row['lon'], 5)}",
                    tooltip=f"Lat:{round(row['lat'], 5)}, Lon:{round(row['lon'], 5)}",
                    icon = folium.Icon(color="red", icon="map-marker")
                ).add_to(m)
        if len(df) != 1:
            # Schritt 4: Zoom anpassen bei mehr als einer Koordinate
            m.fit_bounds([sw, ne])
        # Schritt 5: In Streamlit anzeigen
        st_folium(m, use_container_width=True)
    else:
        st.markdown(":red[Keine sinnvoll darstellbaren Koordinaten gefunden]")
