import streamlit as st
from Rautenstrauch import run_rfem_model, connectors_multiple_spans
import pandas as pd
import numpy as np

def dynamic_input_data_editor(data, key, **kwargs):
    changed_key = f"{key}__changed"
    initial_data_key = f"{key}__initial_data"

    if st.session_state.get(changed_key, False):
        data = st.session_state[initial_data_key]
        st.session_state[changed_key] = False
    else:
        st.session_state[initial_data_key] = data.copy()

    def _on_change():
        st.session_state[changed_key] = True

    return st.data_editor(
        data=data,
        key=key,
        on_change=_on_change,
        **kwargs
    )


st.set_page_config(layout = 'wide')
st.title("Holz-Beton-Verbund - Stabwerksgenerator")

firstCol, secondCol = st.columns([1, 3])

with firstCol:
    st.markdown('### Systemgeometrie')

    anzahl_felder = st.number_input("Anzahl Felder", min_value = 1, max_value = 50)
    feldlängen = []
    for i in range(anzahl_felder):
        l = st.number_input(f"Länge Feld {i+1} [m]", min_value = 0.1, value = 6.3, step = 0.1)
        feldlängen.append(l)
    gesamtlänge = sum(feldlängen)
    st.write("Gesamtlänge:", gesamtlänge, "m")

    Holzklasse = st.text_input("Holzklasse", value = "C24")
    b_ug_cm = st.number_input("Breite Holzquerschnitt [cm]", value = 12.0, step = 1.0, min_value = 0.1)
    h_ug_cm = st.number_input("Höhe Holzquerschnitt [cm]", value = 16.0, step = 1.0, min_value = 0.1)

    Betonklasse = st.text_input("Betonklasse", value = "C30/37")
    b_og_cm = st.number_input("Breite Obergurt [cm]", value = 60.0, step = 1.0, min_value = 0.1)
    h_og_cm = st.number_input("Höhe Obergurt [cm]", value = 9.0, step = 0.5, min_value = 0.1)

    K_ser = st.number_input("K_ser [N/mm]", value = 10000.0, step = 100.0, min_value = 1.0)

    offset_options =  {"kein Abstand": 0.0, "halber Schraubenabstand": 0.5, "ganzer Schraubenabstand": 1.0}
    VM1 = st.number_input("Schraubenabstand Bereich 1 [cm]", value = 20.0, step = 1.0, min_value = 0.1)
    offset_left_side = st.selectbox ("Abstand Schraube zum linken Auflager", list(offset_options.keys()))
    offset_left = offset_options[offset_left_side]
    VM2 = st.number_input("Schraubenabstand Bereich 2 [cm]", value = 40.0, step = 1.0, min_value = 0.1)
    VM3 = st.number_input("Schraubenabstand Bereich 3 [cm]", value = 30.0, step = 1.0, min_value = 0.1)
    offset_right_side = st.selectbox ("Abstand Schraube zum rechten Auflager", list(offset_options.keys()))
    offset_right = offset_options[offset_right_side]

    systemeingabe_gueltig = True
    if any(l <= 0 for l in feldlängen):
        st.error("Alle Feldlängen müssen größer als 0 sein.")
        systemeingabe_gueltig = False

    if any(v <= 0 for v in [b_ug_cm, h_ug_cm, b_og_cm, h_og_cm]):
        st.error("Alle Querschnittsabmessungen müssen größer als 0 sein.")
        systemeingabe_gueltig = False

    if K_ser <= 0:
        st.error("K_ser muss größer als 0 sein.")
        systemeingabe_gueltig = False

    if any(v <= 0 for v in [VM1, VM2, VM3]):
        st.error("Die Schraubenabstände in allen Bereichen müssen größer als 0 sein.")
        systemeingabe_gueltig = False


with secondCol:

    optionen_bereich = ["Gesamtsystem"] + [f"Feld {i+1}" for i in range(anzahl_felder)]

    def leere_lasttabelle():
        return pd.DataFrame(columns=[
            "Lastart",
            "Bereich",
            "Größe Kraft [kN/m bzw. kN]",
            "Endwert Trapezlast [kN/m]",
            "Position / Start [m]",
            "Länge der Last [m]",
            "Kategorie"
        ])

    if "load_df" not in st.session_state:
        st.session_state.load_df = leere_lasttabelle()

    def neue_lastzeile(lastart):
        if lastart == "Gleichstreckenlast":
            return {"Lastart": "Gleichstreckenlast",
                    "Bereich": "Gesamtsystem",
                    "Größe Kraft [kN/m bzw. kN]": 1.5,
                    "Endwert Trapezlast [kN/m]": None,
                    "Position / Start [m]": None,
                    "Länge der Last [m]": None,
                    "Kategorie": "Kat. A: Wohngebäude"}

        elif lastart == "Einzellast":
            return {"Lastart": "Einzellast",
                    "Bereich": "Feld 1" if len(optionen_bereich) > 1 else "Gesamtsystem",
                    "Größe Kraft [kN/m bzw. kN]": 1.5,
                    "Endwert Trapezlast [kN/m]": None,
                    "Position / Start [m]": 0.0,
                    "Länge der Last [m]": None,
                    "Kategorie": "Kat. A: Wohngebäude"}

        elif lastart == "Trapezlast":
            return {"Lastart": "Trapezlast",
                    "Bereich": "Gesamtsystem",
                    "Größe Kraft [kN/m bzw. kN]": 1.5,
                    "Endwert Trapezlast [kN/m]": 2.0,
                    "Position / Start [m]": None,
                    "Länge der Last [m]": None,
                    "Kategorie": "Kat. A: Wohngebäude"}

    st.markdown('### Lasteingabe')
    st.markdown("##### Hinweise zur Lasteingabe")
    st.markdown("- **Gleichstreckenlast**: Wirkt ohne weitere Angaben über den gesamten gewählten Bereich. "
                "Mit `Position / Start [m]` und `Länge der Last [m]` kann ein Teilbereich definiert werden.\n"
                "- **Einzellast**: Benötigt eine Eingabe bei `Position / Start [m]`.\n"
                "- **Trapezlast**: Benötigt zusätzlich einen `Endwert Trapezlast [kN/m]`. "
                "Ohne weitere Angaben wirkt sie über den gesamten gewählten Bereich, mit `Position / Start [m]` "
                "und `Länge der Last [m]` nur über einen Teilbereich.")

    load_df = dynamic_input_data_editor(
        st.session_state.load_df,
        num_rows = "fixed",
        column_config = {"Bereich": st.column_config.SelectboxColumn(
                        options = optionen_bereich, default = "Feld 1"),
                        "Lastart": st.column_config.SelectboxColumn(
                        options=["Gleichstreckenlast",
                                "Einzellast",
                                "Trapezlast"],
                                disabled = True),
                        "Größe Kraft [kN/m bzw. kN]": st.column_config.NumberColumn(default = 1.5,
                                                                                    min_value = 0.0),
                        "Endwert Trapezlast [kN/m]": st.column_config.NumberColumn("Endwert Trapezlast [kN/m]",
                                                                                    help = "Nur für Trapezlasten erforderlich.",
                                                                                    min_value = 0.0),
                        "Position / Start [m]": st.column_config.NumberColumn("Position / Start [m]",
                                                                                help = "Für Einzellasten erforderlich. Bei Trapezlasten optionaler Lastanfang innerhalb des gewählten Bereichs.",
                                                                                min_value = 0.0,
                                                                                step = 0.001),
                        "Länge der Last [m]": st.column_config.NumberColumn("Länge der Last [m]",
                                                                            help = "Bei Trapezlasten optionale Länge des belasteten Bereichs. Nur zusammen mit 'Position / Start [m]' angeben.",
                                                                            min_value = 0.0,
                                                                            step = 0.001),
                        "Kategorie": st.column_config.SelectboxColumn(
                        options=["ständig",
                                "Kat. A: Wohngebäude",
                                "Kat. B: Bürogebäude",
                                "Kat. C: Versammlungsbereiche",
                                "Kat. D: Verkaufsflächen",
                                "Kat. E: Lagerflächen",
                                "Kat. H: Dächer",
                                "Windlasten",
                                "Schnee H <= 1000m",
                                "Schnee H > 1000m"],
                            default = "Kat. A: Wohngebäude")},
        width = "stretch",
        key = "_lasten_editor")

    st.session_state.load_df = load_df.copy().reset_index(drop=True)
    loads = st.session_state.load_df.to_dict(orient="records")

    select_col, add_btn_col, delete_btn_col = st.columns([1, 1, 1])

    with select_col:
        neue_lastart = st.radio("Lastart",
                                    ["Gleichstreckenlast", "Einzellast", "Trapezlast"],
                                    index=0)

    with add_btn_col:
        st.write("")
        st.write("")
        neue_last_erzeugen = st.button("Neue Last erzeugen")

    with delete_btn_col:
        st.write("")
        st.write("")
        letzte_last_loeschen = st.button("Letzte Last löschen")

    if neue_last_erzeugen:
        neue_zeile = pd.DataFrame([neue_lastzeile(neue_lastart)])
        st.session_state.load_df = pd.concat([st.session_state.load_df, neue_zeile],
                                            ignore_index=True).reset_index(drop=True)
        st.rerun()

    if letzte_last_loeschen and len(st.session_state.load_df) > 0:
        st.session_state.load_df = st.session_state.load_df.iloc[:-1].reset_index(drop=True)
        st.rerun()

    ungueltige_gleichstreckenlasten_xy = []
    ungueltige_gleichstreckenlasten_bereich = []
    for index, load in load_df.iterrows():
        if load["Lastart"] != "Gleichstreckenlast":
            continue

        start_ist_leer = pd.isna(load["Position / Start [m]"])
        laenge_ist_leer = pd.isna(load["Länge der Last [m]"])

        if start_ist_leer != laenge_ist_leer:
            ungueltige_gleichstreckenlasten_xy.append(index)
            continue

        if start_ist_leer and laenge_ist_leer:
            continue

        try:
            start = float(load["Position / Start [m]"])
            länge = float(load["Länge der Last [m]"])
        except:
            ungueltige_gleichstreckenlasten_xy.append(index)
            continue

        if start < 0 or länge <= 0:
            ungueltige_gleichstreckenlasten_xy.append(index)
            continue

        if load["Bereich"] == "Gesamtsystem":
            if start + länge > gesamtlänge:
                ungueltige_gleichstreckenlasten_bereich.append(index)

        elif "Feld" in load["Bereich"]:
            try:
                feld_index = int(load["Bereich"].split()[1]) - 1
                feldlänge = feldlängen[feld_index]
            except:
                ungueltige_gleichstreckenlasten_bereich.append(index)
                continue

            if start + länge > feldlänge:
                ungueltige_gleichstreckenlasten_bereich.append(index)
        else:
            ungueltige_gleichstreckenlasten_bereich.append(index)

    ungueltige_einzellasten_bereich = load_df[
        (load_df["Lastart"] == "Einzellast") &
        (load_df["Bereich"] == "Gesamtsystem")]

    ungueltige_einzellasten_x = []
    for index, load in load_df.iterrows():
        if load["Lastart"] != "Einzellast":
            continue

        if load["Bereich"] == "Gesamtsystem":
            continue

        try:
            feld_index = int(load["Bereich"].split()[1]) - 1
            feldlänge = feldlängen[feld_index]

            if pd.isna(load["Position / Start [m]"]):
                ungueltige_einzellasten_x.append(index)
                continue

            x = float(load["Position / Start [m]"])
        except:
            ungueltige_einzellasten_x.append(index)
            continue

        if x < 0 or x > feldlänge:
            ungueltige_einzellasten_x.append(index)

    ungueltige_trapezlasten_endwert = []
    ungueltige_trapezlasten_xy = []
    ungueltige_trapezlasten_bereich = []
    for index, load in load_df.iterrows():
        if load["Lastart"] != "Trapezlast":
            continue

        if pd.isna(load["Endwert Trapezlast [kN/m]"]):
            ungueltige_trapezlasten_endwert.append(index)
            continue

        start_ist_leer = pd.isna(load["Position / Start [m]"])
        laenge_ist_leer = pd.isna(load["Länge der Last [m]"])

        if start_ist_leer != laenge_ist_leer:
            ungueltige_trapezlasten_xy.append(index)
            continue

        if start_ist_leer and laenge_ist_leer:
            continue

        try:
            start = float(load["Position / Start [m]"])
            länge = float(load["Länge der Last [m]"])
        except:
            ungueltige_trapezlasten_xy.append(index)
            continue

        if start < 0 or länge <= 0:
            ungueltige_trapezlasten_xy.append(index)
            continue

        if load["Bereich"] == "Gesamtsystem":
            if start + länge > gesamtlänge:
                ungueltige_trapezlasten_bereich.append(index)

        elif "Feld" in load["Bereich"]:
            try:
                feld_index = int(load["Bereich"].split()[1]) - 1
                feldlänge = feldlängen[feld_index]
            except:
                ungueltige_trapezlasten_bereich.append(index)
                continue

            if start + länge > feldlänge:
                ungueltige_trapezlasten_bereich.append(index)
        else:
            ungueltige_trapezlasten_bereich.append(index)

    lasten_eingabe_gueltig = (len(ungueltige_gleichstreckenlasten_xy) == 0 and
                              len(ungueltige_gleichstreckenlasten_bereich) == 0 and
                              ungueltige_einzellasten_bereich.empty and
                              len(ungueltige_einzellasten_x) == 0 and
                              len(ungueltige_trapezlasten_endwert) == 0 and
                              len(ungueltige_trapezlasten_xy) == 0 and
                              len(ungueltige_trapezlasten_bereich) == 0)

    if ungueltige_gleichstreckenlasten_xy:
        st.error("Bei Gleichstreckenlasten müssen 'Position / Start [m]' und 'Länge der Last [m]' entweder beide leer oder beide gültig angegeben werden.")

    if ungueltige_gleichstreckenlasten_bereich:
        st.error("Die Gleichstreckenlast liegt außerhalb des gewählten Bereichs. Bei 'Feld n' muss Position / Start + belastete Länge innerhalb dieses Felds liegen. Bei 'Gesamtsystem' innerhalb der Gesamtlänge.")

    if not ungueltige_einzellasten_bereich.empty:
        st.error("Einzellasten bitte feldweise eingeben, z.B. 'Feld 1'. 'Gesamtsystem' ist für Einzellasten nicht zulässig.")

    if ungueltige_einzellasten_x:
        st.error("Bei Einzellasten muss 'Position / Start [m]' angegeben sein und innerhalb der gewählten Feldlänge liegen.")

    if ungueltige_trapezlasten_endwert:
        st.error("Bei Trapezlasten muss der 'Endwert Trapezlast [kN/m]' angegeben werden.")

    if ungueltige_trapezlasten_xy:
        st.error("Bei Trapezlasten müssen 'Position / Start [m]' und 'Länge der Last [m]' entweder beide leer oder beide gültig angegeben werden.")

    if ungueltige_trapezlasten_bereich:
        st.error("Die Trapezlast liegt außerhalb des gewählten Bereichs. Bei 'Feld n' muss Position / Start + belastete Länge innerhalb dieses Felds liegen. Bei 'Gesamtsystem' innerhalb der Gesamtlänge.")

    _, connector_info = connectors_multiple_spans(feldlängen,
                                                  VM1/100, VM2/100, VM3/100,
                                                  offset_left, offset_right)

    table_data = []

    for feld in connector_info:
            x_values = feld["x"]
            spacings = feld["Abstände"]
            x1 = feld["x1"]
            x2 = feld["x2"]
            bereich1 = []
            bereich2 = []
            bereich3 = []

            for i in range(len(spacings)):
                x_mid = (x_values[i] + x_values[i+1]) / 2

                if x_mid <= x1:
                    bereich1.append(spacings[i])
                elif x_mid <= x2:
                    bereich2.append(spacings[i])
                else:
                    bereich3.append(spacings[i])

            table_data.append({"Feld": feld["Feld"],
                               "Rand links [m]": round(feld["Rand links"], 5),
                               "Bereich 1 [m]": round(np.mean(bereich1), 5) if bereich1 else None,
                               "Bereich 2 [m]": round(np.mean(bereich2), 5) if bereich2 else None,
                               "Bereich 3 [m]": round(np.mean(bereich3), 5) if bereich3 else None,
                               "Rand rechts [m]": round(feld["Rand rechts"], 5)})
    df_spacing = pd.DataFrame(table_data)

    st.markdown("### erzeugte Schraubenabstände")
    st.dataframe(df_spacing, width = "stretch")

if st.button("Modell in RFEM generieren", disabled = not (systemeingabe_gueltig and lasten_eingabe_gueltig)):
    try:
        with st.spinner("Erstelle Modell in RFEM..."):
            run_rfem_model(feldlängen, Holzklasse, b_ug_cm, h_ug_cm,
                Betonklasse, b_og_cm, h_og_cm,
                K_ser, VM1, VM2, VM3, offset_left, offset_right, loads)

        st.success("Modell wurde in RFEM erzeugt.")

    except RuntimeError as e:
        st.error("Verbindung zu RFEM fehlgeschlagen")
        st.markdown(f"""**Fehlermeldung:**
                    {str(e)}

                    **Mögliche Ursachen:**
                    - RFEM ist nicht gestartet
                    - gRPC-Schnittstelle in RFEM ist nicht aktiviert
                    - Keine gültige RFEM-Lizenz aktiv
                    - Der verwendete Port ist blockiert oder wird von einem anderen Programm genutzt""")