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

def remove_namespaces(xml_string):
    """Entfernt Namespaces, um das Parsen zu erleichtern."""
    try:
        it = ET.iterparse(io.StringIO(xml_string))
        for _, el in it:
            if isinstance(el.tag, str) and '}' in el.tag:
                el.tag = el.tag.split('}', 1)[1]
            for attr in list(el.attrib):
                if '}' in attr:
                    new_attr = attr.split('}', 1)[1]
                    el.attrib[new_attr] = el.attrib.pop(attr)
        return ET.tostring(it.root, encoding='unicode')
    except Exception:
        s = re.sub(r'\sxmlns(:\w+)?="[^"]+"', "", xml_string)
        s = re.sub(r'(<\/?)[A-Za-z0-9_-]+:', r'\1', s)
        return s

def parse_kabel_elements(root):
    """Extrahiert Kabel-Elemente (-W...) aus der QET-Datei."""
    kabel_list = []
    pattern = re.compile(r'^-?\d*W\d+$')
    for diagram in root.findall(".//diagram"):
        seite = diagram.get("folio", "").strip()
        order_attr = (diagram.get("order") or "").strip()
        for elem in diagram.findall(".//element"):
            etype = (elem.get("type") or "").lower()
            if "kabel" in etype:
                y_val = elem.get("y", "").strip()
                try:
                    cable_y = float(y_val)
                except:
                    cable_y = 0.0

                bmk, unity, func = "", "", ""
                infos = elem.find("elementInformations")
                if infos is not None:
                    for info in infos.findall("elementInformation"):
                        name = (info.get("name") or "").lower()
                        val = info.text.strip() if info.text else ""
                        if name == "label": bmk = val
                        elif name == "unity": unity = val
                        elif name == "function": func = val

                if bmk and pattern.match(bmk):
                    kabel_list.append({
                        "seite": seite,
                        "order": order_attr,
                        "BMK": bmk,
                        "unity": unity,
                        "function": func,
                        "cable_y": cable_y,
                        "elem": elem
                    })
    return kabel_list

def build_uuid_endpoint_map(root):
    """Baut eine Map: UUID -> (x, y, function, label)."""
    m = {}
    for elem in root.findall(".//element"):
        raw_uuid = (elem.get("uuid") or elem.get("id") or "").strip()
        if not raw_uuid: continue
        uid = raw_uuid.strip("{}").lower()

        label, function = "", ""
        infos = elem.find("elementInformations")
        if infos is not None:
            for info in infos.findall("elementInformation"):
                n = (info.get("name") or "").lower()
                if n == "label": label = info.text.strip() if info.text else ""
                elif n == "function": function = info.text.strip() if info.text else ""

        # Fallback auf dynamic_elmt_text
        if (not label) or (not function):
            for d in elem.findall(".//dynamic_elmt_text"):
                inm = d.find("info_name")
                itxt = d.find("text")
                if inm is not None and itxt is not None:
                    nm = (inm.text or "").lower()
                    if nm == "label" and not label: label = itxt.text.strip() if itxt.text else ""
                    elif nm == "function" and not function: function = itxt.text.strip() if itxt.text else ""

        try:
            x = float(elem.get("x", 0))
            y = float(elem.get("y", 0))
        except:
            x, y = 0.0, 0.0
        m[uid] = (x, y, function, label)
    return m

def generate_kabelliste(file_path):
    if not os.path.exists(file_path): return []
    with open(file_path, "r", encoding="utf-8") as f:
        xml_data = remove_namespaces(f.read())
    root = ET.fromstring(xml_data)

    uuid_map = build_uuid_endpoint_map(root)
    kabel_list = parse_kabel_elements(root)

    # Alle Verbindungen (conductors) sammeln
    all_conductors = []
    for cond in root.findall(".//conductor"):
        e1 = (cond.get("element1") or "").strip("{}").lower()
        e2 = (cond.get("element2") or "").strip("{}").lower()
        l1 = (cond.get("element1_label") or "").strip()
        l2 = (cond.get("element2_label") or "").strip()
        all_conductors.append({'e1': e1, 'e2': e2, 'l1': l1, 'l2': l2})

    # rows_with_sort: liste von tuples (order_num, front, back, row)
    rows_with_sort = []

    for kabel in kabel_list:
        cable_uuid = (kabel["elem"].get("uuid") or "").strip("{}").lower()
        cable_y = kabel["cable_y"]

        start_label = ""
        ziel_label = ""
        ziel_funktion = ""

        # Suche nach Anschlüssen für dieses Kabel
        for c in all_conductors:
            other_uid = ""
            label_from_conductor = ""

            if c['e1'] == cable_uuid:
                other_uid = c['e2']
                label_from_conductor = c['l2']
            elif c['e2'] == cable_uuid:
                other_uid = c['e1']
                label_from_conductor = c['l1']

            if other_uid:
                ue = uuid_map.get(other_uid)
                if ue:
                    x_pos, y_pos, func, label_from_el = ue
                    # Priorität: Label am Bauteil, sonst Label am Leiter-Attribut
                    final_label = label_from_el if label_from_el else label_from_conductor

                    # Logik: Oben ist Start, Unten ist Ziel
                    if y_pos < cable_y:
                        if not start_label: start_label = final_label
                    else:
                        if not ziel_label: ziel_label = final_label
                        if not ziel_funktion: ziel_funktion = func

        # Bereinigung Start (z.B. -X11:1 -> -X11)
        if ":" in start_label:
            start_label = start_label.split(":")[0]

        # die eigentliche Zeile (er wird später extrahiert und in CSV geschrieben)
        row = [
            kabel["seite"],
            kabel["BMK"],
            start_label,
            ziel_label,
            kabel["unity"],
            ziel_funktion
        ]

        # --- Sortier-Meta berechnen ---
        # order_num: numeric value of diagram.order if possible, else +inf (sodass fehlende order hinten landen)
        order_raw = kabel.get("order", "")
        try:
            order_num = int(order_raw) if order_raw != "" else math.inf
        except Exception:
            # falls nicht numerisch (z.B. "A"), schiebe ans Ende
            order_num = math.inf

        # Parse BMK für numeric W-sortierung:
        bmk = kabel["BMK"] or ""
        m = re.match(r'^(-?\d+)W(\d+)$', bmk)
        if m:
            front = int(m.group(1))
            back = int(m.group(2))
        else:
            m2 = re.search(r'W0*([0-9]+)', bmk, flags=re.IGNORECASE)
            if m2:
                front = 0
                back = int(m2.group(1))
            else:
                # Wenn BMK nicht erkennbar, setze sehr hohe Werte, damit diese hinten sind
                front = 999999
                back = 999999

        rows_with_sort.append((order_num, front, back, row))

    # Sortiere nach (order_num asc, front asc, back asc)
    rows_with_sort.sort(key=lambda t: (t[0], t[1], t[2]))

    # Extrahiere die finalen Reihen (ohne sortier-meta)
    rows = [t[3] for t in rows_with_sort]

    return rows

def main():
    if len(sys.argv) < 2:
        print("Verwendung: python3 script.py <datei.qet>")
        return

    input_file = sys.argv[1]
    base_name = os.path.splitext(input_file)[0]
    output_file = f"{base_name}_kabelliste.csv"

    rows = generate_kabelliste(input_file)

    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"Erfolgreich: {len(rows)} Kabel in {output_file} geschrieben.")

if __name__ == "__main__":
    main()
