import os
import ezdxf
from ezdxf.math import Vec2
import math
import argparse
import sys
import shutil
import configparser
from utils import check_environment_var, setup_logger
from pathlib import Path


def get_bbox(entities):
    """
    Berechnet die Bounding Box für eine Liste von DXF-Entities.
    
    Args:
        entities: Liste von DXF-Entities (ezdxf entities)
        
    Returns:
        Vec2 or None: Zentrum der Bounding Box als Vec2-Objekt oder None,
                     falls keine gültige Geometrie gefunden wurde
                     
    Note:
        Unterstützt POLYLINE, LWPOLYLINE und andere Entity-Typen.
        Fehlerhafte Entities werden übersprungen und protokolliert.
    """
    min_x, min_y = float('inf'), float('inf')
    max_x, max_y = float('-inf'), float('-inf')
    for e in entities:
        try:
            if e.dxftype() == "POLYLINE":
                for vertex in e.vertices:
                    if hasattr(vertex.dxf, 'location'):
                        x, y = vertex.dxf.location.x, vertex.dxf.location.y
                        min_x, min_y = min(min_x, x), min(min_y, y)
                        max_x, max_y = max(max_x, x), max(max_y, y)
            elif e.dxftype() == "LWPOLYLINE":
                for x, y, *_ in e.get_points("xy"):
                    min_x, min_y = min(min_x, x), min(min_y, y)
                    max_x, max_y = max(max_x, x), max(max_y, y)
            elif e.dxftype() == "ARC":
                # Handle ARC entities: consider endpoints and cardinal extrema
                try:
                    cx, cy = e.dxf.center.x, e.dxf.center.y
                except Exception:
                    cx, cy = e.dxf.center
                r = float(e.dxf.radius)
                start_deg = float(e.dxf.start_angle)
                end_deg = float(e.dxf.end_angle)
                start = math.radians(start_deg % 360)
                end = math.radians(end_deg % 360)

                def in_sweep(a: float, s: float, e_: float) -> bool:
                    # CCW sweep from s to e_ with wrap handling
                    if e_ >= s:
                        return s <= a <= e_
                    return a >= s or a <= e_

                candidates = []
                # Endpoints
                candidates.append((cx + r * math.cos(start), cy + r * math.sin(start)))
                candidates.append((cx + r * math.cos(end),   cy + r * math.sin(end)))
                # Cardinal angles 0, 90, 180, 270 deg
                for ang in (0.0, math.pi/2, math.pi, 3*math.pi/2):
                    if in_sweep(ang, start, end):
                        candidates.append((cx + r * math.cos(ang), cy + r * math.sin(ang)))
                for px, py in candidates:
                    min_x, min_y = min(min_x, px), min(min_y, py)
                    max_x, max_y = max(max_x, px), max(max_y, py)
            elif e.dxftype() == "LINE":
                # Handle simple line entities by their start/end points
                try:
                    sx, sy = e.dxf.start.x, e.dxf.start.y
                    ex, ey = e.dxf.end.x, e.dxf.end.y
                except Exception:
                    # Some ezdxf versions provide tuples
                    (sx, sy), (ex, ey) = e.dxf.start, e.dxf.end
                min_x, min_y = min(min_x, sx), min(min_y, sy)
                max_x, max_y = max(max_x, ex), max(max_y, ey)
            else:
                
                # Für andere Entity-Typen: Standard bbox() verwenden
                box = e.bbox()
                if box:
                    (x1, y1), (x2, y2) = box.extmin, box.extmax
                    min_x, min_y = min(min_x, x1), min(min_y, y1)
                    max_x, max_y = max(max_x, x2), max(max_y, y2)
        except Exception as err:
            print(f"    BBox Fehler für {e.dxftype()}: {err}")
            continue

    if min_x == float('inf'):
        return None, (0,0)

    return Vec2((min_x + max_x) / 2, (min_y + max_y) / 2), (max_x -min_x, max_y -min_y)


def create_block_library(input_dir, output_file, config, logger=None):
    """
    Erstellt eine DXF-Block-Bibliothek aus einzelnen DXF-Dateien.
    
    Diese Funktion liest alle DXF-Dateien aus einem Verzeichnis und erstellt
    daraus eine Bibliothek mit Blöcken. Die Blöcke werden in einem Raster
    angeordnet und mit Beschriftungen versehen. Fehlerhafte Dateien werden
    protokolliert.
    
    Args:
        input_dir (str): Verzeichnis mit den zu verarbeitenden DXF-Dateien
        output_file (str): Pfad zur zu erstellenden Bibliotheks-DXF-Datei
        config: ConfigParser-Objekt mit den Konfigurationswerten
        logger: Optionaler Logger für Logging-Ausgaben
        
    Note:
        - Unterstützte Entity-Typen: LINE, LWPOLYLINE, POLYLINE, SPLINE, CIRCLE, ARC
        - Blöcke werden zentriert und in einem 20x20 Raster angeordnet
        - Erstellt eine Log-Datei mit Zeitstempel für fehlerhafte Dateien
        - Automatische Erstellung des Output-Verzeichnisses falls nötig
    """
    doc = ezdxf.new()
    msp = doc.modelspace()

    x_offset = 0
    y_offset = 0
    blocks_in_row = 0
    
    error_files = []
    processed_files = []
    max_blockspacing_y = 0
    for filename in os.listdir(input_dir):
        if not filename.lower().endswith(".dxf"):
            continue

        filepath = os.path.join(input_dir, filename)
        name = os.path.splitext(filename)[0]

        try:
            src_doc = ezdxf.readfile(filepath)
            src_msp = src_doc.modelspace()
            entities = list(src_msp)
            allowed_types = {"LINE", "LWPOLYLINE", "POLYLINE", "SPLINE", "CIRCLE", "ARC"}
            filtered_entities = [e for e in entities if e.dxftype() in allowed_types]
            entities = filtered_entities
        except Exception as e:
            error_msg = f"Fehler beim Lesen von {filename}: {e}"
            if logger:
                logger.error(error_msg)
            else:
                print(error_msg)
            error_files.append((filename, error_msg))
            continue

        center, ausdehnung = get_bbox(entities)
        if center is None:
            error_msg = f"Keine gültige Geometrie in {filename}"
            if logger:
                logger.error(error_msg)
            else:
                print(error_msg)
            error_files.append((filename, error_msg))
            continue

        if name in doc.blocks:
            doc.blocks.delete_block(name)

        blk = doc.blocks.new(name=name, base_point=(0,0))

        for e in entities:
            try:
                cp = e.copy()
                cp.translate(-center.x, -center.y, 0)  # Geometrie verschieben!
                blk.add_entity(cp)
            except Exception as err:
                error_msg = f"Fehler beim Verarbeiten von Entity {e.dxftype()} in {filename}: {err}"
                if logger:
                    logger.error(error_msg)
                else:
                    print(error_msg)
                error_files.append((filename, error_msg))

        # Platzierung in Reihen und Spalten
            # Attribut-Definition (ATTDEF) hinzufügen
        blk.add_attdef(
            tag="NAME",
            insert=(0.2, 0.2),   # Position relativ zum Blockursprung
            height=0.25,
            text="Default Text"
        )
        msp.add_blockref(name, insert=(x_offset, y_offset))
        # Text mit Blocknamen über dem Block

        # Werte aus Config holen (Block: [dxf2lib])
        section = "dxf2lib"
        text_height = get_cfg_value(section, "text_height", DEFAULTS["text_height"])
        extra_block_space_x = get_cfg_value(section, "extra_block_space_x", DEFAULTS["extra_block_space_x"])
        blocks_per_row = get_cfg_value(section, "blocks_per_row", DEFAULTS["blocks_per_row"])
        extra_text_space_y = get_cfg_value(section, "extra_text_space_y", DEFAULTS["extra_text_space_y"])

        ausdehnung_x, ausdehung_y = ausdehnung[0], ausdehnung[1]
        block_spacing_y = ausdehung_y
        block_spacing_x = ausdehnung_x + extra_block_space_x
        max_blockspacing_y  = max(max_blockspacing_y, block_spacing_y)
        
        msp.add_text(name, dxfattribs={'height': text_height, 'insert': (x_offset -ausdehnung_x/2 , y_offset + ausdehung_y/2 + extra_text_space_y)})

        processed_files.append(filename)
        blocks_in_row += 1
        x_offset += block_spacing_x  # Abstand zwischen Blöcken in einer Reihe
        if blocks_in_row == blocks_per_row:
            
            blocks_in_row = 0
            x_offset = 0
            y_offset -= max_blockspacing_y  # Neue Zeile, nach unten versetzt
            max_blockspacing_y = 0

    if logger:
        logger.info(f"Bibliotheks-DXF gespeichert: {output_file}")
        logger.info(f"Verarbeitete Dateien: {len(processed_files)}")
        logger.info(f"Fehlerhafte Dateien: {len(error_files)}")
        
        if error_files:
            logger.warning(f"{len(error_files)} Dateien konnten nicht verarbeitet werden:")
            for filename, error_msg in error_files:
                logger.error(f"{filename}: {error_msg}")
        else:
            logger.info("Keine fehlerhaften Dateien gefunden.")
    else:
        print(f"Bibliotheks-DXF gespeichert: {output_file}")
        print(f"Verarbeitete Dateien: {len(processed_files)}")
        print(f"Fehlerhafte Dateien: {len(error_files)}")
        
        if error_files:
            print(f"Warnung: {len(error_files)} Dateien konnten nicht verarbeitet werden.")

    output_dir = output_file.parent
    if not output_dir.exists():
        output_dir.mkdir(parents=True, exist_ok=True)

    doc.saveas(output_file)


# Standardwerte (falls nicht in der Config)
DEFAULTS = {
    "text_height": 20,         # Schriftgröße des Texts (in DXF-Einheiten)
    "blocks_per_row": 20,      # Anzahl Blöcke pro Zeile im Raster
    "extra_block_space_x" : 50, # Extra Platz damit sich Blöcke nicht überlappen
    "extra_text_space_y" : 50  # Abstand der Überschrift über dem Symbol
    
}
def get_cfg_value(section, key, fallback):
    try:
        return int(config.get(section, key))
    except Exception:
        return fallback

if __name__ == "__main__":
    # Argumentparser für Kommandozeilenoptionen
    parser = argparse.ArgumentParser(description="SVG/XML zu DXF Konverter")
    parser.add_argument('-i', '--input', type=str, help='Input-Verzeichnis mit SVG/XML-Dateien')
    parser.add_argument('-n', '--name', required=False, type=str, help='Name der zu erzeugenden Bibliothek (optional, wird sonst abgefragt)', default="test")
    
    if len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help"):
        parser.print_help()
        sys.exit(0)
        
    args = parser.parse_args()

    if not args.name:
        args.name = input("Bitte Namen der zu erzeugenden Bibliothek eingeben: ").strip()
        if not args.name:
            print("Fehler: Kein Name angegeben. Beende.")
            sys.exit(1)

     # Verzeichnisse über Umgebungsvariablen oder Fallback
    if args.input:
        INPUT_DIR = Path(args.input)
        print(f"Verwende Input-Verzeichnis: {INPUT_DIR} \n")
    else:
        INPUT_DIR = check_environment_var("PROJECT_DATA") / "dxf"
        print(f"Kein Input-Verzeichnis angegeben, verwende Standard: {INPUT_DIR} \n")

    OUTPUT_FILE = check_environment_var("PROJECT_DATA") / "block_libraries" / f"{args.name}.dxf"

    # Prüfe und erstelle log-Verzeichnis falls nötig
    log_dir = check_environment_var("PROJECT_LOG")
    if not log_dir.exists():
        log_dir.mkdir(parents=True, exist_ok=True)
        print(f"Log-Verzeichnis erstellt: {log_dir}")

    # Logger Setup
    logger = setup_logger(log_dir, name='dxf2lib')
    logger.info("=== DXF2LIB Verarbeitung gestartet ===")
    logger.info(f"Input-Verzeichnis: {INPUT_DIR}")
    logger.info(f"Output-Datei: {OUTPUT_FILE}")

    # Lade Config-Datei
    config = configparser.ConfigParser()
    config_path = check_environment_var("PROJECT_CFG") / "allgemein.cfg"
    config.read(config_path, encoding="utf-8")
    logger.info(f"Config-Datei geladen: {config_path}")

    # Erstelle die Block-Bibliothek
    logger.info(f"Erstelle Block-Bibliothek aus {INPUT_DIR}...")
    create_block_library(INPUT_DIR, OUTPUT_FILE, config, logger)
    logger.info("=== DXF2LIB Verarbeitung abgeschlossen ===")
