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

def remove_namespaces(xml_string):
    xml_string = re.sub(r'\sxmlns(:\w+)?="[^"]+"', "", xml_string)
    xml_string = re.sub(r'<(/?)(\w+):', r'<\1', xml_string)
    return xml_string

def safe_int(value):
    try:
        return int(float(value))
    except:
        return 0

def bmk_prefix(bmk):
    return bmk.split(":")[0] if ":" in bmk else bmk

def normalize_uuid(uuid_str):
    if uuid_str:
        return uuid_str.strip("{}").lower()
    return ""

def process_file(input_file, output_file):
    # Sicherstellen, dass die Ausgabe eine CSV ist
    if not output_file.lower().endswith(".csv"):
        output_file = os.path.splitext(output_file)[0] + ".csv"
    
    try:
        with open(input_file, "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 '{input_file}': {e}")
        sys.exit(1)
    
    # Mapping aller Elemente anhand der UUID:
    # normierte UUID -> { "seite":..., "x":..., "y":..., "label":..., "unity":... }
    elements_by_uuid = {}
    for diagram in root.findall(".//diagram"):
        seite = diagram.get("order", "").strip()
        for elem in diagram.findall(".//element"):
            uuid_attr = elem.get("uuid", "")
            uuid_norm = normalize_uuid(uuid_attr)
            if not uuid_norm:
                continue
            x = safe_int(elem.get("x", "0"))
            y = safe_int(elem.get("y", "0"))
            # Versuche zuerst den unity-Wert aus dem Attribut; wenn nicht vorhanden, aus elementInformations.
            unity = elem.get("unity", "").strip()
            label = ""
            infos = elem.find("elementInformations")
            if infos is not None:
                for info in infos.findall("elementInformation"):
                    name = info.get("name", "").lower()
                    if name == "label" and info.text:
                        label = info.text.strip()
                    elif name == "unity" and info.text:
                        unity = info.text.strip()
            elements_by_uuid[uuid_norm] = {
                "seite": seite,
                "x": x,
                "y": y,
                "label": label,
                "unity": unity
            }
    
    # Vorab: Erstelle ein Mapping für Kabelziele:
    # cable_targets: Kabel-UUID -> set( Ziel-Labels ), wobei ein Kabelziel das Label eines Endes ist,
    # das NICHT als Kabel gilt (also kein "W" im Label hat).
    cable_targets = {}
    for conductor in root.findall(".//conductor"):
        e1_uuid = normalize_uuid(conductor.get("element1", ""))
        e2_uuid = normalize_uuid(conductor.get("element2", ""))
        if not e1_uuid or not e2_uuid:
            continue
        e1 = elements_by_uuid.get(e1_uuid)
        e2 = elements_by_uuid.get(e2_uuid)
        if not e1 or not e2:
            continue
        if "W" in e1["label"].upper() and "W" not in e2["label"].upper():
            cable_targets.setdefault(e1_uuid, set()).add(e2["label"])
        elif "W" in e2["label"].upper() and "W" not in e1["label"].upper():
            cable_targets.setdefault(e2_uuid, set()).add(e1["label"])
    
    # Extrahiere die Klemmen (Elemente mit Typ "klemme") und speichere deren normierte UUID
    klemmen_items = []
    for diagram in root.findall(".//diagram"):
        seite = diagram.get("order", "").strip()
        for elem in diagram.findall(".//element"):
            element_type = elem.get("type", "")
            if "klemme" not in element_type.lower():
                continue
            bmk = ""
            x_position = safe_int(elem.get("x", "0"))
            y_position = safe_int(elem.get("y", "0"))
            uuid_attr = elem.get("uuid", "")
            uuid_norm = normalize_uuid(uuid_attr)
            infos = elem.find("elementInformations")
            if infos is not None:
                for info in infos.findall("elementInformation"):
                    if info.get("name", "").lower() == "label" and info.text:
                        bmk = info.text.strip()
                        break
            if not bmk or not uuid_norm:
                continue
            klemmen_items.append((elem, bmk, seite, x_position, y_position, uuid_norm))
    
    # Sortiere die Klemmen: nach BMK-Präfix, dann nach Seitennummer, X, Y
    klemmen_items.sort(key=lambda item: (
        bmk_prefix(item[1]).lower(),
        item[2],
        item[3],
        item[4]
    ))
    
    # Header: Spalten: Seitennummer, ziel extern, Kabel, Kabeltyp, Klemme, 10x Brücke, ziel intern
    header = ["Seitennummer", "ziel extern", "Kabel", "Kabeltyp", "Klemme"] + ["Brücke"] * 10 + ["ziel intern"]
    
    output_rows = []
    # Für die Gruppierung nach BMK-Prefix nutzen wir diese Variablen:
    previous_prefix = None
    group_function_mapping = {}  # Mapping: function_value -> Spaltenindex (1 bis 10) innerhalb der Brücke-Spalten
    
    # Durchlaufe alle Klemmen
    for _, klemme_bmk, seite, _, y_klemme, klemme_uuid in klemmen_items:
        current_prefix = bmk_prefix(klemme_bmk)
        # Bei Gruppenwechsel: Mapping zurücksetzen und leere Zeile einfügen
        if previous_prefix is not None and current_prefix != previous_prefix:
            group_function_mapping = {}
            output_rows.append([""] * len(header))
        previous_prefix = current_prefix
        
        # Initialisiere die Brücke-Spalten (10 Spalten)
        bruecke = ["" for _ in range(10)]
        
        ziel_extern = set()
        ziel_intern = set()
        kabel = set()
        kabeltyp = set()
        
        # Durchlaufe alle conductor-Einträge, die diese Klemme betreffen
        for conductor in root.findall(".//conductor"):
            e1_uuid = normalize_uuid(conductor.get("element1", ""))
            e2_uuid = normalize_uuid(conductor.get("element2", ""))
            if klemme_uuid != e1_uuid and klemme_uuid != e2_uuid:
                continue
            if klemme_uuid == e1_uuid:
                other_uuid = e2_uuid
            else:
                other_uuid = e1_uuid
            if not other_uuid:
                continue
            other_elem = elements_by_uuid.get(other_uuid)
            if not other_elem:
                continue
            if other_elem["seite"] != seite:
                continue
            
            # Hole den function-Wert aus dem conductor-Eintrag und normalisiere ihn
            function_val = conductor.get("function", "").strip().upper()
            if function_val != "":
                if function_val not in group_function_mapping:
                    if len(group_function_mapping) < 10:
                        group_function_mapping[function_val] = len(group_function_mapping) + 1
                if function_val in group_function_mapping:
                    idx = group_function_mapping[function_val] - 1
                    bruecke[idx] = "•"
            
            # Bestehende Logik für Kabel und Zielzuordnung:
            if "W" in other_elem["label"].upper():
                kabel.add(other_elem["label"])
                # Kabeltyp aus dem Element-Eintrag holen (unity)
                kt = other_elem.get("unity", "")
                if kt:
                    kabeltyp.add(kt)
                if other_uuid in cable_targets:
                    valid_targets = set()
                    for target_label in cable_targets[other_uuid]:
                        for uuid_key, data in elements_by_uuid.items():
                            if data["label"] == target_label and data["y"] > other_elem["y"]:
                                valid_targets.add(target_label)
                    if valid_targets:
                        ziel_extern.update(valid_targets)
            else:
                other_y = other_elem["y"]
                if other_y < y_klemme:
                    ziel_intern.add(other_elem["label"])
                elif other_y > y_klemme:
                    ziel_extern.add(other_elem["label"])
        
        # Bestimme den Wert für die "Klemme"-Spalte: Nur ausgeben, wenn unity leer ist.
        klemme_val = klemme_bmk if elements_by_uuid.get(klemme_uuid, {}).get("unity", "") == "" else ""
        
        row = [
            seite,
            ", ".join(sorted(s for s in ziel_extern if s)),
            ", ".join(sorted(s for s in kabel if s)),
            ", ".join(sorted(s for s in kabeltyp if s)),
            klemme_val
        ] + bruecke + [
            ", ".join(sorted(s for s in ziel_intern if s))
        ]
        output_rows.append(row)
    
    try:
        with open(output_file, "w", encoding="utf-8", newline="") as csvfile:
            writer = csv.writer(csvfile, delimiter=";")
            writer.writerow(header)
            writer.writerows(output_rows)
        print("Erfolgreich sortierte Klemmen mit Verbindungen wurden in '" + output_file + "' gespeichert.")
    except Exception as e:
        print("Fehler beim Schreiben der Datei:", e)

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Verwendung: python3 Klemmen.py <Input-XML-Datei> <Output-CSV-Datei>")
        sys.exit(1)
    
    input_file = sys.argv[1]
    output_file = sys.argv[2]
    process_file(input_file, output_file)

