
from ezdxf.entities import Line
import math
from pydantic import BaseModel, Field, field_validator
from typing import Optional
import plant2dxf 


ATTR_TAG = "TeileId"     # Attributtag im Block
RADIUS   = 400           # Radius der Kreiselkreise (mm)

class Kreisel(BaseModel):
    """Pydantic-Modell für Kreisel-Komponenten."""
    teileid: str
    x: float = Field(description="X-Koordinate des Kreisel-Zentrums")
    y: float = Field(description="Y-Koordinate des Kreisel-Zentrums")
    hoehe: float = Field(description="Höhe in mm")
    drehung: float = Field(default=0.0, description="Drehung/Winkel in Grad")
    drehrichtung: Optional[str] = Field(default=None, description="Drehrichtung: UZS oder GUZS")
    abstand: float = Field(default=20000.0, description="Abstand zwischen Kreiselachsen in mm")
    kreiselart: Optional[str] = Field(default=None, description="Kreiselart, z.B. 'Pin'")
    anzahl_scanner: float = Field(default=0.0, description="Anzahl der Scanner")
    anzahl_separatoren: float = Field(default=0.0, description="Anzahl der Separatoren")
    
    @field_validator('abstand')
    @classmethod
    def validate_abstand(cls, v):
        """Konvertiert Abstand von Meter zu mm, falls nötig."""
        if isinstance(v, str):
            v = v.replace(",", ".")
            try:
                v = float(v) * 1000  # Meter → mm
            except ValueError:
                v = 10000.0  # Fallback 10 m
        return v
    
    @field_validator('hoehe')
    @classmethod
    def validate_hoehe(cls, v):
        """Konvertiert Höhe von Meter zu mm, falls nötig."""
        if isinstance(v, str):
            v = v.replace(",", ".")
            try:
                v = float(v) * 1000  # Meter → mm
            except ValueError:
                v = 0.0
        return v
    
    @property
    def halbabstand(self) -> float:
        """Halbabstand zwischen den beiden Blöcken."""
        return self.abstand / 2
    
    @property
    def winkel_rad(self) -> float:
        """Winkel in Radianten für Berechnungen."""
        if self.drehung == 270 or self.drehung == 90:
            return math.radians(self.drehung)
        else:
            return math.radians(self.drehung - 180)
    
    @property
    def richtung_rad(self) -> float:
        """Richtung in Radianten (für am_kreisel_direct_verbunden)."""
        # Wird aus drehung abgeleitet oder separat gesetzt
        return math.radians(self.drehung)
    
    @property
    def pos1(self) -> tuple[float, float, float]:
        """Position des ersten Blocks (x, y, z)."""
        dx = self.halbabstand * math.cos(self.winkel_rad)
        dy = self.halbabstand * math.sin(self.winkel_rad)
        return (self.x - dx, self.y - dy, self.hoehe)
    
    @property
    def pos2(self) -> tuple[float, float, float]:
        """Position des zweiten Blocks (x, y, z)."""
        dx = self.halbabstand * math.cos(self.winkel_rad)
        dy = self.halbabstand * math.sin(self.winkel_rad)
        return (self.x + dx, self.y + dy, self.hoehe)
    
    @property
    def z(self) -> float:
        """Z-Koordinate (gleich der Höhe)."""
        return self.hoehe
    
    @classmethod
    def from_merkmale(cls, teileid: str, x: float, y: float, merkmale: dict) -> 'Kreisel':
        """Erstellt ein Kreisel-Objekt aus einem merkmale-Dictionary."""
        hoehe_m = merkmale.get("Höhe in m", "0").replace(",", ".")
        try:
            hoehe = float(hoehe_m) * 1000
        except (ValueError, TypeError):
            hoehe = 0.0
        
        abstand_m = merkmale.get("Abstand (Kreiselachse A - Kreiselachse) in Meter", "20").replace(",", ".")
        try:
            abstand = float(abstand_m) * 1000
        except (ValueError, TypeError):
            abstand = 10000.0
        
        try:
            drehung = float(merkmale.get("Drehung", "0"))
        except (ValueError, TypeError):
            drehung = 0.0
        
        try:
            anzahl_scanner = float(merkmale.get("Anzahl der Scanner", "0"))
        except (ValueError, TypeError):
            anzahl_scanner = 0.0
        
        try:
            anzahl_separatoren = float(merkmale.get("Anzahl der Separatoren", "0"))
        except (ValueError, TypeError):
            anzahl_separatoren = 0.0
        
        return cls(
            teileid=teileid,
            x=x,
            y=y,
            hoehe=hoehe,
            drehung=drehung,
            drehrichtung=merkmale.get("Drehrichtung"),
            abstand=abstand,
            kreiselart=merkmale.get("Kreiselart"),
            anzahl_scanner=anzahl_scanner,
            anzahl_separatoren=anzahl_separatoren
        )
    
    def draw_kreisel_lines(msp, pos1, pos2, kreisel):
        """Zeichnet tangentiale Linien zwischen zwei Kreiselblöcken, unabhängig vom Winkel."""
        rotation = kreisel.drehung
        x1, y1, z1 = pos1
        x2, y2, z1 = pos2
        # Verbindungsvektor
        dx = x2 - x1
        dy = y2 - y1
        # Länge
        length = math.hypot(dx, dy)
        if length == 0:
            return  # keine Linie bei identischen Punkten
        # Normalenvektor (senkrecht, normiert, Länge = RADIUS)

        nx = -dy / length * RADIUS
        ny = dx / length * RADIUS
        # Tangentialpunkte
        p1a = (x1 + nx, y1 + ny,z1)
        p1b = (x1 - nx, y1 - ny,z1)
        p2a = (x2 + nx, y2 + ny,z1)
        p2b = (x2 - nx, y2 - ny,z1)
        if kreisel.kreiselart == "Pin":
            if rotation == 0.0:
                p1a2 = p1a[0] - RADIUS - 50, p1a[1] + 50, z1
                p1b2 = p1b[0] - RADIUS - 50, p1b[1] - 50, z1
                p2a2 = p2a[0] + RADIUS + 50, p2a[1] + 50, z1
                p2b2 = p2b[0] + RADIUS + 50, p2b[1] - 50, z1
                Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
                Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
                msp.add_entity(Line1)
                msp.add_entity(Line2)
            elif  rotation == 180.0:
                p1a2 = p1a[0] + RADIUS + 50, p1a[1] - 50, z1
                p1b2 = p1b[0] + RADIUS + 50, p1b[1] + 50, z1
                p2a2 = p2a[0] - RADIUS - 50, p2a[1] - 50, z1
                p2b2 = p2b[0] - RADIUS - 50, p2b[1] + 50, z1
                Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
                Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
                msp.add_entity(Line1)
                msp.add_entity(Line2)
            elif  rotation == 90.0:
                p1a2 = p1a[0] + 50, p1a[1] - 50 + RADIUS , z1
                p1b2 = p1b[0]  - 50, p1b[1] - 50 + RADIUS, z1
                p2a2 = p2a[0]  + 50, p2a[1] + 50 - RADIUS, z1
                p2b2 = p2b[0]  - 50, p2b[1] + 50 - RADIUS, z1
                Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
                Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
                msp.add_entity(Line1)
                msp.add_entity(Line2)
            elif  rotation == 270.0:
                p1a2 = p1a[0] - 50, p1a[1] + 50 - RADIUS , z1
                p1b2 = p1b[0]  + 50, p1b[1] + 50 - RADIUS, z1
                p2a2 = p2a[0]  - 50, p2a[1] - 50 + RADIUS, z1
                p2b2 = p2b[0]  + 50, p2b[1] - 50 + RADIUS, z1
                Line1 = Line.new(dxfattribs={"start": p1a2,"end": p2a2,"layer": "Pinbereich"})
                Line2 = Line.new(dxfattribs={"start": p1b2,"end": p2b2,"layer": "Pinbereich"})
                msp.add_entity(Line1)
                msp.add_entity(Line2)

        # Linien zeichnen
        msp.add_line(p1a, p2a)
        msp.add_line(p1b, p2b)
    
    def draw_kreisel_drehrichtung_markierung(msp, pos1, pos2, kreisel, lib_doc, doc, verbose):
        drehrichtung = (kreisel.drehrichtung or "").upper()
        if drehrichtung not in ("UZS", "GUZS"):
            return
        x1, y1,z1= pos1
        x2, y2,z2 = pos2
        dx = x2 - x1
        dy = y2 - y1
        length = math.hypot(dx, dy)
        if length == 0:
            return
        # Normalenvektor (senkrecht, normiert, Länge = RADIUS)
        nx = -dy / length * RADIUS
        ny = dx / length * RADIUS
        # Obere Linie
        p1_oben = (x1 + nx, y1 + ny)
        p2_oben = (x2 + nx, y2 + ny)
        # Untere Linie
        p1_unten = (x1 - nx, y1 - ny)
        p2_unten = (x2 - nx, y2 - ny)
        # S-LP auf oberer Linie (Drehrichtung wie angegeben)
        for i in range(1, 4):
            t = i / 4  # 1/4, 2/4, 3/4
            px = p1_oben[0] + t * (p2_oben[0] - p1_oben[0])
            py = p1_oben[1] + t * (p2_oben[1] - p1_oben[1])
            rotation = math.degrees(math.atan2(p2_oben[1] - p1_oben[1], p2_oben[0] - p1_oben[0]))
            if drehrichtung == "GUZS":
                rotation += 180
            plant2dxf.import_block("Richtungspfeil", lib_doc, doc)
            blockref_layer, color = plant2dxf.get_insert_color_layer(lib_doc, "Richtungspfeil")
            bref = msp.add_blockref("Richtungspfeil", (px, py,z1), dxfattribs={"rotation": rotation,"layer": blockref_layer})
            if verbose:
                print(f"[INFO] Drehrichtung '{drehrichtung}': Richtungspfeil oben bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}")
        # S-LP auf unterer Linie (Drehrichtung invertiert)
        for i in range(1, 4):
            t = i / 4
            px = p1_unten[0] + t * (p2_unten[0] - p1_unten[0])
            py = p1_unten[1] + t * (p2_unten[1] - p1_unten[1])
            rotation = math.degrees(math.atan2(p2_unten[1] - p1_unten[1], p2_unten[0] - p1_unten[0]))
            if drehrichtung == "UZS":
                rotation += 180
            plant2dxf.import_block("Richtungspfeil", lib_doc, doc)
            blockref_layer, color = plant2dxf.get_insert_color_layer( lib_doc, "Richtungspfeil")
            bref = msp.add_blockref("Richtungspfeil", (px, py, z1), dxfattribs={"rotation": rotation , "layer": blockref_layer})
            if verbose:
                print(f"[INFO] Drehrichtung '{drehrichtung}':Richtungspfeil unten bei ({px:.1f}, {py:.1f}), rot={rotation:.1f}")