"""
Setzt den Einfuegepunkt ($INSBASE) von Omniflo DXF-Dateien.

Mit --boegen: Einfuegepunkt auf den Schnittpunkt der Tangenten
am Anfangs- und Endpunkt der Bogenkurve.

Mit --weichen45 / --weichen90: Einfuegepunkt auf den Schnittpunkt
der laengsten vertikalen Linie und des Endpunktes der Bogenkurve.
"""

import argparse
import json
import math
import os
import sys

import ezdxf


def find_arc(doc):
    """Findet die erste ARC-Entity im Modelspace."""
    msp = doc.modelspace()
    for entity in msp:
        if entity.dxftype() == "ARC":
            return entity
    return None


def tangent_intersection(arc):
    """Berechnet den Schnittpunkt der Tangenten am Start- und Endpunkt des Bogens.
    Bei 180-Grad-Boegen wird der Mittelpunkt zwischen Start und Ende verwendet.
    """
    cx, cy, _ = arc.dxf.center
    r = arc.dxf.radius
    a_start = math.radians(arc.dxf.start_angle)
    a_end = math.radians(arc.dxf.end_angle)

    # Start- und Endpunkt
    p1x = cx + r * math.cos(a_start)
    p1y = cy + r * math.sin(a_start)
    p2x = cx + r * math.cos(a_end)
    p2y = cy + r * math.sin(a_end)

    # Tangentenrichtungen (senkrecht zum Radius)
    d1x = -math.sin(a_start)
    d1y = math.cos(a_start)
    d2x = -math.sin(a_end)
    d2y = math.cos(a_end)

    # Determinante
    det = d1x * (-d2y) - (-d2x) * d1y

    if abs(det) < 1e-9:
        # Tangenten parallel (z.B. 180-Grad-Bogen) -> Mittelpunkt
        return (p1x + p2x) / 2, (p1y + p2y) / 2

    dx = p2x - p1x
    dy = p2y - p1y
    t = (-dx * d2y + dy * d2x) / det

    ix = p1x + t * d1x
    iy = p1y + t * d1y
    return ix, iy


def find_longest_vertical_line(doc):
    """Findet die laengste vertikale Linie im Modelspace und gibt deren x-Koordinate zurueck."""
    msp = doc.modelspace()
    longest = None
    longest_len = 0
    for entity in msp:
        if entity.dxftype() != "LINE":
            continue
        start = entity.dxf.start
        end = entity.dxf.end
        dx = abs(end[0] - start[0])
        if dx > 0.01:
            continue
        length = abs(end[1] - start[1])
        if length > longest_len:
            longest_len = length
            longest = entity
    return longest


def arc_endpoint(arc):
    """Gibt den Endpunkt (end_angle) des Bogens zurueck."""
    cx, cy, _ = arc.dxf.center
    r = arc.dxf.radius
    a_end = math.radians(arc.dxf.end_angle)
    return cx + r * math.cos(a_end), cy + r * math.sin(a_end)


def weichen_insertion_point(doc):
    """Berechnet den Einfuegepunkt fuer eine Weiche:
    Schnittpunkt der laengsten vertikalen Linie mit einer Horizontalen
    durch den Endpunkt der Bogenkurve.
    """
    vline = find_longest_vertical_line(doc)
    if vline is None:
        return None
    arc = find_arc(doc)
    if arc is None:
        return None
    vx = vline.dxf.start[0]
    _, ey = arc_endpoint(arc)
    return vx, ey


def process_weichen(data_dir, json_path, results_dir, kurven_winkel):
    """Verarbeitet Weichen-DXF-Dateien fuer den angegebenen KurvenWinkel."""
    with open(json_path, "r", encoding="utf-8") as f:
        weichen = json.load(f)

    omniflo_dir = os.path.join(data_dir, "omniflo")
    filtered = [w for w in weichen if w["KurvenWinkel"] == kurven_winkel]

    if not filtered:
        print(f"Keine Weichen mit KurvenWinkel={kurven_winkel} gefunden.")
        return

    for weiche in filtered:
        sivasnr = str(weiche["Sivasnr"])
        dxf_path = os.path.join(omniflo_dir, f"{sivasnr}.dxf")

        if not os.path.exists(dxf_path):
            print(f"WARNUNG: {dxf_path} nicht gefunden, ueberspringe.")
            continue

        doc = ezdxf.readfile(dxf_path)
        result = weichen_insertion_point(doc)

        if result is None:
            print(f"WARNUNG: Kein ARC oder keine vertikale Linie in {sivasnr}.dxf, ueberspringe.")
            continue

        ix, iy = result
        old_base = doc.header.get("$INSBASE", (0, 0, 0))
        doc.header["$INSBASE"] = (ix, iy, 0)

        out_path = os.path.join(results_dir, f"{sivasnr}.dxf")
        doc.saveas(out_path)

        print(
            f"{sivasnr}: INSBASE ({old_base[0]:.2f}, {old_base[1]:.2f}) "
            f"-> ({ix:.2f}, {iy:.2f})  [{weiche['ProfilTyp']}]"
        )

    print(f"\nErgebnisse in: {results_dir}")


def process_boegen(data_dir, json_path, results_dir):
    """Verarbeitet alle Boegen-DXF-Dateien."""
    with open(json_path, "r", encoding="utf-8") as f:
        boegen = json.load(f)

    omniflo_dir = os.path.join(data_dir, "omniflo")

    for bogen in boegen:
        sivasnr = str(bogen["Sivasnr"])
        dxf_path = os.path.join(omniflo_dir, f"{sivasnr}.dxf")

        if not os.path.exists(dxf_path):
            print(f"WARNUNG: {dxf_path} nicht gefunden, ueberspringe.")
            continue

        doc = ezdxf.readfile(dxf_path)
        arc = find_arc(doc)

        if arc is None:
            print(f"WARNUNG: Kein ARC in {sivasnr}.dxf gefunden, ueberspringe.")
            continue

        ix, iy = tangent_intersection(arc)

        old_base = doc.header.get("$INSBASE", (0, 0, 0))
        doc.header["$INSBASE"] = (ix, iy, 0)

        out_path = os.path.join(results_dir, f"{sivasnr}.dxf")
        doc.saveas(out_path)

        print(
            f"{sivasnr}: INSBASE ({old_base[0]:.2f}, {old_base[1]:.2f}) "
            f"-> ({ix:.2f}, {iy:.2f})  [{bogen['ProfilTyp']}]"
        )

    print(f"\nErgebnisse in: {results_dir}")


def main():
    parser = argparse.ArgumentParser(
        description="Setzt Einfuegepunkte fuer Omniflo DXF-Dateien"
    )
    parser.add_argument(
        "--boegen",
        action="store_true",
        help="Einfuegepunkt auf Tangentenschnittpunkt der Bogenkurve setzen",
    )
    parser.add_argument(
        "--weichen45",
        action="store_true",
        help="Einfuegepunkt fuer 45-Grad-Weichen setzen",
    )
    parser.add_argument(
        "--weichen90",
        action="store_true",
        help="Einfuegepunkt fuer 90-Grad-Weichen setzen",
    )
    args = parser.parse_args()

    if not args.boegen and not args.weichen45 and not args.weichen90:
        parser.print_help()
        sys.exit(1)

    data_dir = os.environ.get("DXFM_DATA")
    results_dir = os.environ.get("DXFM_RESULTS")

    if not data_dir or not results_dir:
        print("FEHLER: Umgebungsvariablen DXFM_DATA und DXFM_RESULTS muessen gesetzt sein.")
        print("Bitte zuerst setenv.bat ausfuehren.")
        sys.exit(1)

    os.makedirs(results_dir, exist_ok=True)

    if args.boegen:
        json_path = os.path.join(data_dir, "json", "omniflo_boegen.json")
        if not os.path.exists(json_path):
            print(f"FEHLER: {json_path} nicht gefunden.")
            sys.exit(1)
        process_boegen(data_dir, json_path, results_dir)

    if args.weichen45 or args.weichen90:
        json_path = os.path.join(data_dir, "json", "omniflo_weichen.json")
        if not os.path.exists(json_path):
            print(f"FEHLER: {json_path} nicht gefunden.")
            sys.exit(1)
        if args.weichen45:
            print("=== Weichen 45 Grad ===")
            process_weichen(data_dir, json_path, results_dir, 45)
        if args.weichen90:
            print("=== Weichen 90 Grad ===")
            process_weichen(data_dir, json_path, results_dir, 90)


if __name__ == "__main__":
    main()
