#!/usr/bin/env python3
import sys
import xml.etree.ElementTree as ET
import os
import re
import csv

def remove_namespaces(xml_string):
    # Entferne Namespace-Deklarationen, um unbound prefix-Fehler zu vermeiden
    xml_string = re.sub(r'\sxmlns(:\w+)?="[^"]+"', "", xml_string)
    xml_string = re.sub(r'<(/?)(\w+):', r'<\1', xml_string)
    return xml_string

def parse_kabel_elements(root):
    """
    Extrahiert Kabel-Elemente aus der QET-Datei.
    Liefert eine Liste von Dictionaries für jedes Kabel mit den Feldern:
      - seite: Seitenzahl (Attribut "order" des übergeordneten <diagram>)
      - BMK: aus elementInformations, name="label" – nur akzeptiert, wenn es dem Muster "-?ZahlWZahl" entspricht
      - unity: aus elementInformations, name="unity" (Kabeltyp)
      - function: aus elementInformations, name="function" (wird hier nicht genutzt, da Ziel-Funktion verwendet wird)
      - cable_y: y-Position (als float)
      - elem: das XML-Element (optional)
    """
    kabel_list = []
    pattern = re.compile(r'^-?\d+W\d+$')  # z. B. "-15W1" oder "13W2"
    for diagram in root.findall(".//diagram"):
        seite = diagram.get("order", "").strip()
        for elem in diagram.findall(".//element"):
            etype = elem.get("type", "").lower()
            if "kabel" in etype:
                y_val = elem.get("y", "").strip()
                try:
                    cable_y = float(y_val)
                except:
                    cable_y = None
                BMK = ""
                unity = ""
                function_text = ""
                infos = elem.find("elementInformations")
                if infos is not None:
                    for info in infos.findall("elementInformation"):
                        name = info.get("name", "").lower()
                        val = info.text.strip() if info.text else ""
                        if name == "label":
                            BMK = val
                        elif name == "unity":
                            unity = val
                        elif name == "function":
                            function_text = val
                # Filtere nur Kabel, deren BMK exakt dem Muster entspricht
                if BMK and pattern.match(BMK):
                    kabel_list.append({
                        "seite": seite,
                        "BMK": BMK,
                        "unity": unity,
                        "function": function_text,
                        "cable_y": cable_y,
                        "elem": elem
                    })
    return kabel_list

def parse_endpoint_positions(root):
    """
    Extrahiert alle Endpunkte (z.B. Klemmen, Abzweigdosen, etc.), also alle <element>-Tags,
    die ein nicht-leeres Label in elementInformations besitzen.
    Liefert ein Dictionary: { label: (x_position (float), y_position (float), function_text) }
    """
    endpoints = {}
    for elem in root.findall(".//element"):
        label = ""
        function_text = ""
        infos = elem.find("elementInformations")
        if infos is not None:
            for info in infos.findall("elementInformation"):
                n = info.get("name", "").lower()
                if n == "label":
                    if info.text:
                        label = info.text.strip()
                elif n == "function":
                    if info.text:
                        function_text = info.text.strip()
            if label:
                x_val = elem.get("x", "").strip()
                y_val = elem.get("y", "").strip()
                try:
                    x_pos = float(x_val)
                    y_pos = float(y_val)
                except:
                    continue
                endpoints[label] = (x_pos, y_pos, function_text)
    return endpoints

def parse_conductor_connections_list(root):
    """
    Sucht alle <conductor>-Elemente und gibt eine Liste von Tupeln zurück:
       (element1_label, element2_label)
    """
    conn_list = []
    for conductor in root.findall(".//conductor"):
        e1 = conductor.get("element1_label", "").strip()
        e2 = conductor.get("element2_label", "").strip()
        if e1 and e2:
            conn_list.append((e1, e2))
    return conn_list

def determine_start_and_ziel(kabel, conn_list, endpoints):
    """
    Für ein Kabel (Dictionary) wird anhand der Conductor-Verbindungen (conn_list)
    und der Endpunkt-Positionen (endpoints) ermittelt:
      - Es werden alle Verbindungen gesammelt, bei denen das Kabel-BMK als eines der beiden Labels vorkommt.
      - Für jeden dieser Verbindungen wird das andere Label als Kandidat genutzt, sofern es in endpoints vorhanden ist.
      - Anschließend werden die Kandidaten in zwei Gruppen aufgeteilt:
           Gruppe "above": Kandidaten, deren y-Wert < cable_y (also höher)
           Gruppe "below": Kandidaten, deren y-Wert > cable_y (also tiefer)
      - Innerhalb jeder Gruppe wird der Kandidat mit dem kleinsten x-Wert gewählt.
    Gibt (start, ziel) zurück.
    """
    cable_label = kabel["BMK"]
    cable_y = kabel["cable_y"]
    candidates = []
    for (e1, e2) in conn_list:
        if cable_label in (e1, e2):
            other = e2 if e1 == cable_label else e1
            if other in endpoints:
                x_pos, y_pos, _ = endpoints[other]
                candidates.append((other, x_pos, y_pos))
    
    if not candidates:
        return ("", "")
    
    # Aufteilen in Gruppen: Gruppe "above" (y < cable_y) und Gruppe "below" (y > cable_y)
    candidates_above = [c for c in candidates if c[2] < cable_y]
    candidates_below = [c for c in candidates if c[2] > cable_y]
    start = ""
    ziel = ""
    if candidates_above:
        start_candidate = min(candidates_above, key=lambda c: c[1])
        start = start_candidate[0]
    if candidates_below:
        ziel_candidate = min(candidates_below, key=lambda c: c[1])
        ziel = ziel_candidate[0]
    if len(candidates) == 1:
        if candidates[0][2] < cable_y:
            start = candidates[0][0]
        else:
            ziel = candidates[0][0]
    return (start, ziel)

def sort_by_bmk(row):
    """
    Sortierschlüssel für die BMK-Spalte.
    Erwartet z.B. "13W1". Es wird zuerst nach der vorderen Zahl und dann nach der hinteren Zahl sortiert.
    """
    bmk = row[1]
    match = re.match(r'(\d+)[A-Za-z]+(\d+)', bmk)
    if match:
        front = int(match.group(1))
        back = int(match.group(2))
        return (front, back)
    return (bmk,)

def generate_kabelliste(file_path):
    if not os.path.exists(file_path):
        print(f"Datei nicht gefunden: {file_path}")
        sys.exit(1)
    try:
        with open(file_path, "r", encoding="utf-8") as f:
            xml_data = f.read()
        xml_data = remove_namespaces(xml_data)
        root = ET.fromstring(xml_data)
    except Exception as e:
        print(f"Fehler beim Parsen der Datei: {e}")
        sys.exit(1)
    
    kabel_list = parse_kabel_elements(root)
    endpoints = parse_endpoint_positions(root)
    conn_list = parse_conductor_connections_list(root)
    
    rows = []
    for kabel in kabel_list:
        seite = kabel["seite"]
        BMK = kabel["BMK"]
        unity = kabel["unity"]
        start, ziel = determine_start_and_ziel(kabel, conn_list, endpoints)

        if ":" in start:
            start = start.split(":")[0]
        if ":" in ziel:
            ziel = ziel.split(":")[0]

        ziel_funktion = ""
        if ziel and (ziel in endpoints):
            _, _, ziel_funktion = endpoints[ziel]
        rows.append([seite, BMK, start, ziel, unity, ziel_funktion])
    
    rows.sort(key=sort_by_bmk)
    return rows

def main():
    if len(sys.argv) != 2:
        print("Verwendung: python3 Kabelliste.py <QET-Datei>")
        sys.exit(1)

    input_file = sys.argv[1]
    # Erzeuge den neuen Dateinamen im selben Verzeichnis wie die Eingabedatei
    output_file = os.path.join(os.path.dirname(input_file), "kabelliste.csv")
    
    rows = generate_kabelliste(input_file)

    try:
        with open(output_file, "w", newline='', encoding="utf-8") as f:
            writer = csv.writer(f, delimiter=';')
            writer.writerow(["Seite", "BMK", "Start", "Ziel", "Kabeltyp", "Funktion"])
            writer.writerows(rows)
        print(f"Kabelliste erfolgreich in '{output_file}' exportiert ({len(rows)} Einträge).")
    except Exception as e:
        print(f"Fehler beim Schreiben der Datei: {e}")

if __name__ == "__main__":
    main()

