#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
create_example.py - Erzeugt Test-DXF-Dateien für verschiedene Testszenarien

Dieses Skript generiert synthetische DXF-Testdateien mit definierten Layouts
für verschiedene Testfälle (Nummerierung, SPS, Erdung, etc.).
"""

import argparse
import sys
import os
import json
from pathlib import Path
import ezdxf

from utils import draw_symbol_frames

# Umgebungsvariablen
PROJECT = os.getenv('PROJECT', Path(__file__).parent.parent.absolute())
PROJECT_WORK = os.getenv('PROJECT_WORK', os.path.join(PROJECT, 'work'))
PROJECT_CFG = os.getenv('PROJECT_CFG', os.path.join(PROJECT, 'cfg'))


class TestDataGenerator:
    """Generiert Test-DXF-Dateien für verschiedene Szenarien"""

    def __init__(self, filename, scene_name, config_file=None):
        self.filename = filename
        self.scene_name = scene_name
        self.doc = None
        self.msp = None
        self.config = None
        self.generated_symbols = []  # Liste von (x, y, io_pattern, group_name) für Validierung

        # Lade Config-Datei (muss immer angegeben werden)
        if config_file:
            self.load_config(config_file)

    def load_config(self, config_file):
        """
        Lädt die JSON-Konfigurationsdatei

        Args:
            config_file: Pfad zur JSON-Config-Datei
        """
        config_path = Path(config_file)
        if not config_path.is_absolute():
            config_path = Path(PROJECT_CFG) / config_file

        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                self.config = json.load(f)
            print(f"Config geladen: {config_path}")
        except FileNotFoundError:
            print(f"FEHLER: Config-Datei nicht gefunden: {config_path}")
            self.config = None
        except json.JSONDecodeError as e:
            print(f"FEHLER beim Parsen der Config-Datei: {e}")
            self.config = None

    def create_document(self):
        """Erstellt ein neues DXF-Dokument"""
        self.doc = ezdxf.new('R2010')
        self.msp = self.doc.modelspace()

        # Erstelle textstyle3 für die Attribute
        self.create_textstyle()

        # Erstelle Layer mit korrekten Farben
        self.create_layers()

        return self.doc

    def create_textstyle(self):
        """Erstellt den textstyle3 für Attribute"""
        if 'textstyle3' not in self.doc.styles:
            self.doc.styles.new('textstyle3', dxfattribs={
                'font': 'arial.ttf',
                'width': 1.0
            })

    def create_layers(self):
        """
        Erstellt alle benötigten Layer mit korrekten Farben
        Basiert auf Analyse von easy.dxf
        """
        # Layer-Definitionen mit Farben
        layer_defs = [
            {'name': '0', 'color': 7},  # Standard-Layer
            {'name': 'ILS_MOTOR', 'color': 7},
            {'name': 'ILS_RENAMER', 'color': 1},  # Rot
            {'name': 'ILS_Eingang', 'color': 7},  # BG-Symbole
            {'name': 'ILS_Ausgang', 'color': 7},  # MB-Symbole
            {'name': 'ILS_POT-Erdung', 'color': 7},  # POT-Symbole
            {'name': 'VALIDATION_ERROR', 'color': 1},  # Rot für Fehlerkreise
            {'name': 'TEXT-D', 'color': -7},  # Ausgeschaltet (negativ)
            {'name': 'TEXT-E', 'color': 7},
            {'name': 'TEXT-ES', 'color': 7},
            {'name': 'TEXT-F', 'color': 7},
            {'name': 'TEXT-I', 'color': 7}
        ]

        for layer_def in layer_defs:
            if layer_def['name'] not in self.doc.layers:
                self.doc.layers.new(layer_def['name'], dxfattribs={
                    'color': layer_def['color']
                })

    def calculate_symbol_extent(self, io_text):
        """
        Berechnet die ungefähre Ausdehnung eines io-Symbols basierend auf der IO-Länge

        Basierend auf Analyse von IO=MA0062 (6 Zeichen):
        - Bounding Box: 1208.94 x 380.94 Einheiten
        - ~201.49 Einheiten Breite pro Zeichen
        - ~380.94 Einheiten Höhe (konstant)

        Args:
            io_text: Text des IO-Attributs (z.B. "MA-1@@")

        Returns:
            (width, height) in DXF-Einheiten
        """
        char_count = len(io_text)
        width_per_char = 201.49
        fixed_height = 380.94

        width = char_count * width_per_char
        height = fixed_height

        return (width, height)

    def create_io_block(self):
        """
        Erstellt den io-Block mit allen Attribut-Definitionen
        Basiert auf dem io-Block aus easy.dxf (IO=MA0062)

        Returns:
            Block-Definition
        """
        if 'io' in self.doc.blocks:
            return self.doc.blocks['io']

        # Erstelle neuen Block
        io_block = self.doc.blocks.new('io')

        # Attribut-Definitionen basierend auf ILS_Motor-790902001 aus Nummerierung_IO.dxf
        # invisible=True: ID, BEZEICHNUNG, KENNZEICHNUNG, ARTIKELNR, ARTIKELBEZEICHN, TEXT-I
        # invisible=False: IO, VERW, SPS, REALE_POSITION, TEXT-D, TEXT-E, TEXT-ES, TEXT-F
        attrib_defs = [
            {
                'tag': 'REALE_POSITION',
                'insert': (0.0, 230.0, 0.0),
                'height': 50.0,
                'default': 'x',
                'layer': 'ILS_MOTOR',
                'invisible': False
            },
            {
                'tag': 'SPS',
                'insert': (0.0, 130.37, 0.0),
                'height': 125.0,
                'default': '1',
                'layer': 'ILS_MOTOR',
                'invisible': False
            },
            {
                'tag': 'IO',
                'insert': (103.88, 130.37, 0.0),
                'height': 125.0,
                'default': 'MA0000',
                'layer': 'ILS_MOTOR',
                'invisible': False
            },
            {
                'tag': 'TEXT-D',
                'insert': (772.87, 110.44, 0.0),
                'height': 63.75,
                'default': '',
                'layer': 'TEXT-D',
                'invisible': False
            },
            {
                'tag': 'TEXT-E',
                'insert': (772.87, 110.44, 0.0),
                'height': 63.75,
                'default': '',
                'layer': 'TEXT-E',
                'invisible': False
            },
            {
                'tag': 'TEXT-ES',
                'insert': (772.87, 110.44, 0.0),
                'height': 63.75,
                'default': '',
                'layer': 'TEXT-ES',
                'invisible': False
            },
            {
                'tag': 'TEXT-F',
                'insert': (772.87, 110.44, 0.0),
                'height': 63.75,
                'default': '',
                'layer': 'TEXT-F',
                'invisible': False
            },
            {
                'tag': 'TEXT-I',
                'insert': (772.87, 110.44, 0.0),
                'height': 63.75,
                'default': '',
                'layer': 'TEXT-I',
                'invisible': True  # Unsichtbar
            },
            {
                'tag': 'VERW',
                'insert': (6.92, 20.78, 0.0),
                'height': 80.0,
                'default': 'ILS-M0000',
                'layer': 'ILS_MOTOR',
                'invisible': False
            },
            {
                'tag': 'ID',
                'insert': (0.0, 0.73, 0.0),
                'height': 120.0,
                'default': '',
                'layer': 'ILS_MOTOR',
                'invisible': True  # Unsichtbar
            },
            {
                'tag': 'BEZEICHNUNG',
                'insert': (0.0, -67.05, 0.0),
                'height': 63.75,
                'default': 'Motor MA0000',
                'layer': 'ILS_MOTOR',
                'invisible': True  # Unsichtbar
            },
            {
                'tag': 'KENNZEICHNUNG',
                'insert': (0.0, -155.33, 0.0),
                'height': 63.75,
                'default': '=A01+UH00',
                'layer': 'ILS_MOTOR',
                'invisible': True  # Unsichtbar
            },
            {
                'tag': 'ARTIKELNR',
                'insert': (0.0, -248.90, 0.0),
                'height': 63.75,
                'default': '790902001',
                'layer': 'ILS_MOTOR',
                'invisible': True  # Unsichtbar
            },
            {
                'tag': 'ARTIKELBEZEICHN',
                'insert': (0.0, -332.68, 0.0),
                'height': 63.75,
                'default': 'E-Teile fuer SEW Motor ASE1 - HAN10ES - BG',
                'layer': 'ILS_MOTOR',
                'invisible': True  # Unsichtbar
            }
        ]

        # Füge alle Attribut-Definitionen hinzu
        for attrib_def in attrib_defs:
            # Berechne flags: Bit 0 = invisible
            flags = 1 if attrib_def['invisible'] else 0

            io_block.add_attdef(
                tag=attrib_def['tag'],
                insert=attrib_def['insert'],
                text=attrib_def['default'],
                dxfattribs={
                    'height': attrib_def['height'],
                    'layer': attrib_def['layer'],
                    'color': 256,  # BYLAYER
                    'style': 'textstyle3',
                    'rotation': 0,
                    'halign': 0,
                    'valign': 0,
                    'flags': flags
                }
            )

        return io_block

    def create_pot_block(self):
        """
        Erstellt den ILS_POT-Erdung Block mit allen Attribut-Definitionen
        Basiert auf dem ILS_POT-Erdung Block aus Nummerierung_IO.dxf

        Returns:
            Block-Definition
        """
        block_name = 'ILS_POT-Erdung'
        if block_name in self.doc.blocks:
            return self.doc.blocks[block_name]

        # Erstelle neuen Block
        pot_block = self.doc.blocks.new(block_name)

        # Hole Attribut-Definitionen aus pot_defaults (falls vorhanden)
        if self.config and 'pot_defaults' in self.config:
            pot_defaults = self.config['pot_defaults']
            attrib_configs = pot_defaults.get('attrib_defs', [])
        else:
            # Fallback: Hard-coded Attribute basierend auf Analyse von Nummerierung_IO.dxf
            attrib_configs = [
                {
                    'tag': 'REALE_POSITION',
                    'insert': (0.0, 13.0, 0.0),
                    'height': 100.0,
                    'default': 'x',
                    'layer': 'ILS_POT-Erdung',
                    'invisible': False
                },
                {
                    'tag': 'NAME',
                    'insert': (316.17, -179.49, 0.0),
                    'height': 128.0,
                    'default': 'POT-1@@@',
                    'layer': 'ILS_POT-Erdung',
                    'invisible': False
                },
                {
                    'tag': 'KENNZEICHNUNG',
                    'insert': (0.0, -377.38, 0.0),
                    'height': 128.0,
                    'default': '=A01+UH00-X01',
                    'layer': 'ILS_POT-Erdung',
                    'invisible': True
                }
            ]

        # Füge alle Attribut-Definitionen hinzu
        for attrib_def in attrib_configs:
            # Berechne flags: Bit 0 = invisible
            flags = 1 if attrib_def['invisible'] else 0

            # Konvertiere insert-Liste zu Tupel falls nötig
            insert_pos = attrib_def['insert']
            if isinstance(insert_pos, list):
                insert_pos = tuple(insert_pos)

            pot_block.add_attdef(
                tag=attrib_def['tag'],
                insert=insert_pos,
                text=attrib_def['default'],
                dxfattribs={
                    'height': attrib_def['height'],
                    'layer': attrib_def['layer'],
                    'color': 256,  # BYLAYER
                    'style': 'textstyle3',
                    'rotation': 0,
                    'halign': 0,
                    'valign': 0,
                    'flags': flags
                }
            )

        return pot_block

    def save(self, output_dir=None):
        """Speichert das DXF-Dokument"""
        if output_dir is None:
            output_dir = PROJECT_WORK

        output_path = os.path.join(output_dir, self.filename)
        self.doc.saveas(output_path)
        print(f"Test-DXF erstellt: {output_path}")
        return output_path

    def draw_frames_for_generated_symbols(self):
        """
        Zeichnet Rahmen um alle generierten Symbole zur Visualisierung.
        Konvertiert das interne Format in das von draw_symbol_frames erwartete Format.
        """
        if not self.generated_symbols:
            print("Keine generierten Symbole zum Zeichnen von Rahmen gefunden")
            return 0

        # Konvertiere das interne Format in das erwartete Format
        symbols_for_frames = []
        for sym in self.generated_symbols:
            # generated_symbols enthält 'x', 'y' (Einfügepunkt = linke untere Ecke bei left_bottom_corner)
            # draw_symbol_frames erwartet Position = linke untere Ecke des Symbols
            # Wir können die gespeicherten Koordinaten direkt verwenden

            symbols_for_frames.append({
                'position': (sym['x'], sym['y']),  # Position = linke untere Ecke
                'attributes': {'IO': sym['io']},   # IO-Wert für Breitenberechnung
                'layer': sym.get('layer', 'ILS_MOTOR')
            })

        # Zeichne die Rahmen
        return draw_symbol_frames(self.doc, self.msp, symbols_for_frames)

    def load_block_from_file(self, source_file, block_name):
        """
        Lädt einen Block aus einer anderen DXF-Datei

        Args:
            source_file: Pfad zur Quell-DXF-Datei
            block_name: Name des zu ladenden Blocks

        Returns:
            Block-Definition oder None
        """
        try:
            source_doc = ezdxf.readfile(source_file)
            if block_name in source_doc.blocks:
                # Block in das Zieldokument kopieren
                source_block = source_doc.blocks.get(block_name)
                if block_name not in self.doc.blocks:
                    new_block = self.doc.blocks.new(block_name)
                    # Kopiere alle Entities aus dem Quellblock (inkl. ATTDEF)
                    for entity in source_block:
                        try:
                            new_block.add_foreign_entity(entity, copy=True)
                        except Exception as e:
                            print(f"Warnung: Konnte Entity nicht kopieren: {e}")
                            # Versuche direktes Klonen
                            try:
                                cloned = entity.copy()
                                new_block.add_entity(cloned)
                            except:
                                pass
                return self.doc.blocks.get(block_name)
            else:
                print(f"Warnung: Block '{block_name}' nicht in {source_file} gefunden")
                return None
        except Exception as e:
            print(f"Fehler beim Laden von Block '{block_name}': {e}")
            import traceback
            traceback.print_exc()
            return None

    def create_renamer_attrib_block(self, block_name, attrib_tags, frame_type='MA'):
        """
        Erstellt einen Block mit ATTDEF-Attributen für Renamer-Frames

        Args:
            block_name: Name des Attribut-Blocks (z.B. 'RENAMER_ATTRIB_MA')
            attrib_tags: Liste der Attribut-Tags (z.B. ['NAME', 'KENNZEICHNUNG', ...])
            frame_type: 'MA' oder 'MULTI' für Frame-Typ

        Returns:
            Block-Definition
        """
        if block_name in self.doc.blocks:
            return self.doc.blocks[block_name]

        attrib_block = self.doc.blocks.new(block_name)

        # Hole Positionen aus Config, falls vorhanden
        if self.config and 'general' in self.config:
            general = self.config['general']
            if frame_type == 'MA' and 'ma_frame' in general:
                config_positions = general['ma_frame'].get('attdef_positions', {})
            elif frame_type == 'MULTI' and 'multi_frame' in general:
                config_positions = general['multi_frame'].get('attdef_positions', {})
            else:
                config_positions = {}
        else:
            config_positions = {}

        # Definiere Standard-Positionen und Werte für Attribute (Fallback)
        default_definitions = {
            'NAME': {'insert': (10, 50), 'default': 'MA@@', 'height': 21.25},
            'NAME1': {'insert': (10, 80), 'default': 'BG1@@@', 'height': 21.25},
            'NAME2': {'insert': (10, 60), 'default': 'MB1@@@', 'height': 21.25},
            'NAME3': {'insert': (10, 40), 'default': '', 'height': 21.25},
            'KENNZEICHNUNG': {'insert': (10, 20), 'default': '=A01+UC0101', 'height': 21.25},
            'LAYER_NAME': {'insert': (10, 0), 'default': 'ILS_MOTOR', 'height': 21.25},
            'LAYER_NAME1': {'insert': (10, -20), 'default': 'ILS_Eingang', 'height': 21.25},
            'LAYER_NAME2': {'insert': (10, -40), 'default': 'ILS_Ausgang', 'height': 21.25},
            'LAYER_NAME3': {'insert': (10, -60), 'default': '', 'height': 21.25},
            'DIRECTION': {'insert': (10, -80), 'default': 'LEFT_RIGHT', 'height': 21.25},
            'ID': {'insert': (10, -100), 'default': '', 'height': 21.25}
        }

        # Füge nur die angeforderten Attribute hinzu
        for tag in attrib_tags:
            if tag in default_definitions:
                default_def = default_definitions[tag]

                # Verwende Config-Werte, falls vorhanden, sonst Defaults
                if tag in config_positions and not tag.startswith('_'):
                    config_pos = config_positions[tag]
                    x = config_pos.get('x', default_def['insert'][0])
                    y = config_pos.get('y', default_def['insert'][1])
                    height = config_pos.get('height', default_def['height'])
                else:
                    x, y = default_def['insert']
                    height = default_def['height']

                attrib_block.add_attdef(
                    tag=tag,
                    insert=(x, y),
                    text=default_def['default'],
                    dxfattribs={
                        'height': height,
                        'layer': 'ILS_RENAMER',
                        'color': 1,
                        'style': 'textstyle3'
                    }
                )

        return attrib_block

    def create_renamer_frame(self, insert_point, width, height, name, kennzeichnung,
                            layer_name, direction, use_polyline=False, frame_type='MA',
                            name2='', name3='', layer_name2='', layer_name3='', id_value=''):
        """
        Erstellt einen Renamer-Rahmen mit zweistufiger Block-Struktur auf Layer ILS_RENAMER

        Struktur:
        - Äußerer Block (RENAMER_xxx):
          1. LWPOLYLINE (umschließende Polylinie)
          2. Block-Referenz mit Attributen (RENAMER_ATTRIB_xxx)

        Args:
            insert_point: (x, y) Position des Rahmens
            width: Breite des Rahmens
            height: Höhe des Rahmens
            name: NAME-Attribut (oder NAME1 für Multi-Layer)
            kennzeichnung: KENNZEICHNUNG-Attribut
            layer_name: LAYER_NAME-Attribut (oder LAYER_NAME1 für Multi-Layer)
            direction: DIRECTION-Attribut
            use_polyline: Wird ignoriert (für Kompatibilität)
            frame_type: 'MA' für einfachen Frame, 'MULTI' für Multi-Layer Frame
        """
        x, y = insert_point

        # Bestimme Attribut-Tags basierend auf Frame-Typ
        if frame_type == 'MA':
            attrib_tags = ['NAME', 'KENNZEICHNUNG', 'LAYER_NAME', 'DIRECTION']
            attrib_block_name = 'RENAMER_ATTRIB_MA'
        else:  # MULTI
            attrib_tags = ['NAME1', 'NAME2', 'NAME3', 'KENNZEICHNUNG',
                          'LAYER_NAME1', 'LAYER_NAME2', 'LAYER_NAME3', 'DIRECTION', 'ID']
            attrib_block_name = 'RENAMER_ATTRIB_MULTI'

        # Erstelle Attribut-Block (wiederverwendbar) mit Config-Unterstützung
        self.create_renamer_attrib_block(attrib_block_name, attrib_tags, frame_type=frame_type)

        # Erstelle äußeren Renamer-Block mit eindeutigem Namen (inkl. Position und Größe)
        # Jeder Rahmen bekommt einen eindeutigen Namen basierend auf Position
        outer_block_name = f"RENAMER_{name.replace('@', '').replace('-', '_')}_{int(x)}_{int(y)}"
        if outer_block_name not in self.doc.blocks:
            outer_block = self.doc.blocks.new(outer_block_name)

            # 1. Füge LWPOLYLINE hinzu (umschließende Polylinie)
            points = [
                (0, 0),
                (width, 0),
                (width, height),
                (0, height),
                (0, 0)
            ]
            outer_block.add_lwpolyline(points, dxfattribs={
                'layer': 'ILS_RENAMER',
                'color': 1
            })

            # 2. Füge Block-Referenz mit Attributen hinzu
            # Position an der linken oberen Ecke des Rahmens
            attrib_insert_pos = (10, height - 30)  # Linke obere Ecke mit kleinem Abstand
            attrib_blockref = outer_block.add_blockref(
                attrib_block_name,
                insert=attrib_insert_pos,
                dxfattribs={'layer': 'ILS_RENAMER'}
            )

            # Setze Attribut-Werte auf der inneren Block-Referenz
            if frame_type == 'MA':
                attrib_blockref.add_auto_attribs({
                    'NAME': name,
                    'KENNZEICHNUNG': kennzeichnung,
                    'LAYER_NAME': layer_name,
                    'DIRECTION': direction
                })
            else:  # MULTI - für BG/MG/POT mit mehreren Layern
                attrib_blockref.add_auto_attribs({
                    'NAME1': name,
                    'NAME2': name2,
                    'NAME3': name3,
                    'KENNZEICHNUNG': kennzeichnung,
                    'LAYER_NAME1': layer_name,
                    'LAYER_NAME2': layer_name2,
                    'LAYER_NAME3': layer_name3,
                    'DIRECTION': direction,
                    'ID': id_value
                })

        # Füge Block-Referenz im Modelspace ein
        blockref = self.msp.add_blockref(
            outer_block_name,
            insert=(x, y),
            dxfattribs={'layer': 'ILS_RENAMER'}
        )

    def create_polyline_renamer_frame(self, path_points, name, kennzeichnung, layer_name, direction):
        """
        Erstellt einen Renamer-Rahmen mit Polyline-Pfad

        Struktur:
        - Block (RENAMER_POLYLINE_xxx):
          1. LWPOLYLINE (Pfad durch die Punkte)
          2. Block-Referenz mit Attributen (RENAMER_ATTRIB_MA)

        Args:
            path_points: Liste von Dictionaries mit 'x' und 'y' Koordinaten
            name: NAME-Attribut
            kennzeichnung: KENNZEICHNUNG-Attribut
            layer_name: LAYER_NAME-Attribut
            direction: DIRECTION-Attribut (z.B. 'POLYLINE_PATH')
        """
        # Erstelle Attribut-Block (wiederverwendbar)
        attrib_tags = ['NAME', 'KENNZEICHNUNG', 'LAYER_NAME', 'DIRECTION']
        attrib_block_name = 'RENAMER_ATTRIB_MA'
        self.create_renamer_attrib_block(attrib_block_name, attrib_tags, frame_type='MA')

        # Erstelle eindeutigen Block-Namen basierend auf erstem Punkt
        first_point = path_points[0] if path_points else {'x': 0, 'y': 0}
        block_name = f"RENAMER_POLYLINE_{name.replace('@', '').replace('-', '_')}_{int(first_point['x'])}_{int(first_point['y'])}"

        if block_name not in self.doc.blocks:
            renamer_block = self.doc.blocks.new(block_name)

            # 1. Füge LWPOLYLINE hinzu (Pfad)
            # Konvertiere Punkte von Dict zu Tupeln
            points = [(p['x'], p['y']) for p in path_points]

            renamer_block.add_lwpolyline(points, dxfattribs={
                'layer': 'ILS_RENAMER',
                'color': 1
            })

            # 2. Füge Block-Referenz mit Attributen hinzu
            # Position neben dem ersten Punkt des Pfades (mit kleinem Offset)
            first_x = points[0][0]
            first_y = points[0][1]
            attrib_insert_pos = (first_x + 10, first_y + 30)  # Offset vom ersten Punkt
            attrib_blockref = renamer_block.add_blockref(
                attrib_block_name,
                insert=attrib_insert_pos,
                dxfattribs={'layer': 'ILS_RENAMER'}
            )

            # Setze Attribut-Werte
            attrib_blockref.add_auto_attribs({
                'NAME': name,
                'KENNZEICHNUNG': kennzeichnung,
                'LAYER_NAME': layer_name,
                'DIRECTION': direction
            })

        # Füge Block-Referenz im Modelspace ein (am Ursprung, da Pfad absolute Koordinaten hat)
        blockref = self.msp.add_blockref(
            block_name,
            insert=(0, 0),
            dxfattribs={'layer': 'ILS_RENAMER'}
        )

    def generate_nummerierung(self, scene_name='Nummerierung1'):
        """
        Generiert Testdaten für Nummerierung-Szenario

        Verwendet entweder die JSON-Config oder Hard-coded Defaults

        Args:
            scene_name: Name der Testszene (z.B. 'Nummerierung1', 'Nummerierung2')
        """
        print(f"Generiere Nummerierung-Testdaten ({scene_name})...")

        # Erstelle io-Block direkt (ohne Referenzdatei)
        io_block = self.create_io_block()

        if io_block is None:
            print("Fehler: io-Block konnte nicht erstellt werden")
            return

        # Prüfe ob POT-Symbole in der Scene verwendet werden
        if self.config and 'test_scenes' in self.config and scene_name in self.config['test_scenes']:
            scene = self.config['test_scenes'][scene_name]
            if 'pot_groups' in scene and scene['pot_groups']:
                print("POT-Symbole erkannt, erstelle ILS_POT-Erdung Block...")
                pot_block = self.create_pot_block()
                if pot_block:
                    print(f"POT-Block 'ILS_POT-Erdung' erfolgreich erstellt")
                else:
                    print(f"WARNUNG: POT-Block konnte nicht erstellt werden")

        # Wenn Config geladen ist, verwende sie, ansonsten Hard-coded Defaults
        if self.config and 'test_scenes' in self.config and scene_name in self.config['test_scenes']:
            print(f"Verwende JSON-Config für {scene_name}...")
            self._generate_nummerierung_from_config(scene_name)
        else:
            print("Fehler in Config ... terminating")

    def _calculate_insert_offset(self, insert_point, symbol_width, symbol_height):
        """
        Berechnet den Offset vom Einfügepunkt zur linken oberen Ecke

        Args:
            insert_point: String mit dem Einfügepunkt (z.B. "left_top_corner", "center")
            symbol_width: Breite des Symbols
            symbol_height: Höhe des Symbols

        Returns:
            (offset_x, offset_y) - Offset vom Einfügepunkt zur linken oberen Ecke
        """
        offset_map = {
            'left_top_corner': (0, 0),
            'left_bottom_corner': (0, symbol_height),
            'right_top_corner': (-symbol_width, 0),
            'right_bottom_corner': (-symbol_width, symbol_height),
            'center': (-symbol_width / 2, symbol_height / 2),
            'top_center': (-symbol_width / 2, 0),
            'bottom_center': (-symbol_width / 2, symbol_height)
        }

        return offset_map.get(insert_point, (0, 0))  # Default: left_top_corner

    def _generate_symbol_group(self, group, symbol_defaults, symbol_type='MA', spacing_factor=1.2, y_offsets_list=[0, -50, 50], insert_point='left_bottom_corner'):
        """
        Generiert eine Gruppe von Symbolen (MA, BG, MB, POT, etc.)

        Args:
            group: Dict mit Gruppen-Config (name, count, layout_type, base_x, base_y, spacing)
            symbol_defaults: Dict mit Symbol-Default-Attributen (ma_defaults, bg_defaults, etc.)
            symbol_type: Typ des Symbols ('MA', 'BG', 'MB', 'POT') für BEZEICHNUNG
            spacing_factor: Spacing-Faktor zwischen Symbolen
            y_offsets_list: Liste von Y-Offsets für horizontal_offset Layout
            insert_point: Einfügepunkt des Blocks (z.B. "left_bottom_corner", "center")
        """
        name = group['name']
        count = group['count']
        layout_type = group['layout_type']
        base_x = group['base_x']
        base_y = group['base_y']

        # Extrahiere IO-Pattern aus Name (z.B. "MA-1@@_top" -> "MA-1@@")
        io_pattern = name.split('_')[0]

        # Hole Dimensionen aus defaults
        dimensions = symbol_defaults.get('dimensions', {})
        symbol_width = dimensions.get('width', 1210)
        symbol_height = dimensions.get('height', 381)

        # Berechne Spacing: Verwende group['spacing'] falls angegeben, sonst symbol_width * spacing_factor
        if layout_type in ['horizontal', 'horizontal_offset']:
            if 'spacing' in group and group['spacing'] > 0:
                actual_spacing = group['spacing']
            else:
                actual_spacing = symbol_width * spacing_factor
        else:
            actual_spacing = 0

        # Hole Attribute und Block-Info aus defaults
        attributes = symbol_defaults.get('attributes', {})
        layer = symbol_defaults.get('layer', 'ILS_MOTOR')
        block_name = symbol_defaults.get('block_name', 'io')

        # Berechne Offset basierend auf Einfügepunkt
        offset_x, offset_y = self._calculate_insert_offset(insert_point, symbol_width, symbol_height)

        # Bestimme BEZEICHNUNG basierend auf Symbol-Typ
        bezeichnung_prefix = {
            'MA': 'Motor',
            'BG': 'Eingang',
            'MB': 'Ausgang',
            'POT': 'Erdung'
        }.get(symbol_type, 'Symbol')

        # Generiere Symbole basierend auf Layout-Typ
        for i in range(count):
            if layout_type == 'single':
                x, y = base_x, base_y
            elif layout_type == 'horizontal':
                x = base_x + i * actual_spacing
                y = base_y
            elif layout_type == 'horizontal_offset':
                x = base_x + i * actual_spacing
                y_offset = y_offsets_list[i % len(y_offsets_list)]
                y = base_y + y_offset
            else:
                print(f"Unbekannter Layout-Typ: {layout_type}")
                continue

            # Berechne die linke obere Ecke basierend auf dem Einfügepunkt
            left_top_x = x + offset_x
            left_top_y = y + offset_y

            # DXF-Block wird an der linken unteren Ecke eingefügt
            # (da der io-Block bei (0,0) startet und nach oben geht)
            blockref = self.msp.add_blockref(
                block_name,
                insert=(left_top_x, left_top_y - symbol_height),
                dxfattribs={'layer': layer}
            )

            # Setze Attribute (dynamisch IO/NAME und BEZEICHNUNG)
            attrib_values = attributes.copy()

            # Für POT-Symbole: Setze NAME statt IO
            if symbol_type == 'POT':
                attrib_values['NAME'] = io_pattern
            else:
                attrib_values['IO'] = io_pattern

            # Setze BEZEICHNUNG nur wenn im Template vorhanden
            if 'BEZEICHNUNG' in attrib_values:
                attrib_values['BEZEICHNUNG'] = f"{bezeichnung_prefix} {io_pattern}"

            blockref.add_auto_attribs(attrib_values)

            # Speichere Symbol-Position für spätere Validierung
            # Speichere den tatsächlichen Einfügepunkt (linke untere Ecke bei left_bottom_corner)
            actual_insert_x = left_top_x
            actual_insert_y = left_top_y - symbol_height
            self.generated_symbols.append({
                'x': actual_insert_x,
                'y': actual_insert_y,
                'io': io_pattern,
                'group_name': name,
                'layer': layer
            })

    def _generate_nummerierung_from_config(self, scene_name='Nummerierung1'):
        """
        Generiert Nummerierung-Testdaten aus JSON-Config

        Args:
            scene_name: Name der Testszene in der Config
        """
        scene = self.config['test_scenes'][scene_name]
        general = self.config.get('general', {})

        # Hole Layout-Einstellungen
        layout = general.get('layout', {})
        spacing_factor = layout.get('symbol_spacing_factor', 1.2)
        y_offsets_list = layout.get('horizontal_offset_y_offsets', [0, -50, 50])

        # Hole Einfügepunkt aus Config (default: left_bottom_corner)
        ma_frame = general.get('ma_frame', {})
        insert_point = ma_frame.get('insert_point', 'left_bottom_corner')

        # Generiere MA-Gruppen aus Config
        ma_defaults = self.config.get('ma_defaults', {})
        for group in scene.get('ma_groups', []):
            self._generate_symbol_group(group, ma_defaults, symbol_type='MA',
                                       spacing_factor=spacing_factor,
                                       y_offsets_list=y_offsets_list,
                                       insert_point=insert_point)

        # Generiere BG-Gruppen aus Config (Eingänge)
        bg_defaults = self.config.get('bg_defaults', {})
        for group in scene.get('bg_groups', []):
            self._generate_symbol_group(group, bg_defaults, symbol_type='BG',
                                       spacing_factor=spacing_factor,
                                       y_offsets_list=y_offsets_list,
                                       insert_point=insert_point)

        # Generiere MB-Gruppen aus Config (Ausgänge)
        mb_defaults = self.config.get('mb_defaults', {})
        for group in scene.get('mb_groups', []):
            self._generate_symbol_group(group, mb_defaults, symbol_type='MB',
                                       spacing_factor=spacing_factor,
                                       y_offsets_list=y_offsets_list,
                                       insert_point=insert_point)

        # Generiere POT-Gruppen aus Config (Erdungspunkte)
        pot_defaults = self.config.get('pot_defaults', {})
        for group in scene.get('pot_groups', []):
            self._generate_symbol_group(group, pot_defaults, symbol_type='POT',
                                       spacing_factor=spacing_factor,
                                       y_offsets_list=y_offsets_list,
                                       insert_point=insert_point)

        # Generiere Renamer-Rahmen aus Config
        for frame_config in scene.get('renaming_frames', []):
            frame_type = frame_config.get('type',    'rectangle')

            if frame_type == 'polyline_path':
                # Polyline-Pfad Frame
                self.create_polyline_renamer_frame(
                    path_points=frame_config['path_points'],
                    name=frame_config['name'],
                    kennzeichnung=frame_config['kennzeichnung'],
                    layer_name=frame_config['layer_name'],
                    direction=frame_config['direction']
                )
            else:
                # Standard Rectangle Frame
                # Prüfe ob es ein MULTI-Frame ist (NAME1/NAME2 statt NAME)
                is_multi_frame = 'name2' in frame_config or 'layer_name1' in frame_config

                if is_multi_frame:
                    # MULTI-Frame (BG, MG, POT mit mehreren Layern)
                    self.create_renamer_frame(
                        insert_point=(frame_config['x'], frame_config['y']),
                        width=frame_config['width'],
                        height=frame_config['height'],
                        name=frame_config.get('name', ''),
                        kennzeichnung=frame_config.get('kennzeichnung', ''),
                        layer_name=frame_config.get('layer_name1', ''),
                        direction=frame_config.get('direction', ''),
                        use_polyline=frame_config.get('use_polyline', False),
                        frame_type='MULTI',
                        name2=frame_config.get('name2', ''),
                        name3=frame_config.get('name3', ''),
                        layer_name2=frame_config.get('layer_name2', ''),
                        layer_name3=frame_config.get('layer_name3', ''),
                        id_value=frame_config.get('id', '')
                    )
                else:
                    # MA-Frame (einfacher Frame)
                    self.create_renamer_frame(
                        insert_point=(frame_config['x'], frame_config['y']),
                        width=frame_config['width'],
                        height=frame_config['height'],
                        name=frame_config['name'],
                        kennzeichnung=frame_config['kennzeichnung'],
                        layer_name=frame_config['layer_name'],
                        direction=frame_config['direction'],
                        use_polyline=frame_config.get('use_polyline', False)
                    )
        
        # Prüfe ob alle generierten Symbole in einem Renamer-Frame liegen
        self._validate_symbols_in_frames(scene)

        # Prüfe ob sich Renamer-Rechtecke überlappen (außer POLYLINE_PATH)
        self._check_overlapping_frames(scene)

    def _rectangles_overlap(self, rect1, rect2):
        """
        Prüft ob sich zwei Rechtecke überlappen.

        Args:
            rect1: Dictionary mit 'x', 'y', 'width', 'height'
            rect2: Dictionary mit 'x', 'y', 'width', 'height'

        Returns:
            True wenn sich die Rechtecke überlappen
        """
        # Rechteck 1
        r1_left = rect1['x']
        r1_right = rect1['x'] + rect1['width']
        r1_bottom = rect1['y']
        r1_top = rect1['y'] + rect1['height']

        # Rechteck 2
        r2_left = rect2['x']
        r2_right = rect2['x'] + rect2['width']
        r2_bottom = rect2['y']
        r2_top = rect2['y'] + rect2['height']

        # Prüfe ob keine Überlappung (dann ist das Gegenteil eine Überlappung)
        no_overlap = (r1_right <= r2_left or   # R1 ist links von R2
                      r2_right <= r1_left or   # R2 ist links von R1
                      r1_top <= r2_bottom or   # R1 ist unter R2
                      r2_top <= r1_bottom)     # R2 ist unter R1

        return not no_overlap

    def _calculate_overlap_area(self, rect1, rect2):
        """
        Berechnet die Überlappungsfläche zweier Rechtecke.

        Args:
            rect1: Dictionary mit 'x', 'y', 'width', 'height'
            rect2: Dictionary mit 'x', 'y', 'width', 'height'

        Returns:
            Überlappungsfläche in Quadrateinheiten
        """
        # Rechteck 1
        r1_left = rect1['x']
        r1_right = rect1['x'] + rect1['width']
        r1_bottom = rect1['y']
        r1_top = rect1['y'] + rect1['height']

        # Rechteck 2
        r2_left = rect2['x']
        r2_right = rect2['x'] + rect2['width']
        r2_bottom = rect2['y']
        r2_top = rect2['y'] + rect2['height']

        # Berechne überlappenden Bereich
        overlap_left = max(r1_left, r2_left)
        overlap_right = min(r1_right, r2_right)
        overlap_bottom = max(r1_bottom, r2_bottom)
        overlap_top = min(r1_top, r2_top)

        # Wenn keine Überlappung, gib 0 zurück
        if overlap_right <= overlap_left or overlap_top <= overlap_bottom:
            return 0

        # Berechne Fläche
        overlap_width = overlap_right - overlap_left
        overlap_height = overlap_top - overlap_bottom
        return overlap_width * overlap_height

    def _calculate_overlap_recommendations(self, frame1, frame2):
        """
        Berechnet Empfehlungen zur Behebung einer Überlappung zwischen zwei Frames.

        Args:
            frame1: Dictionary mit 'x', 'y', 'width', 'height', 'name'
            frame2: Dictionary mit 'x', 'y', 'width', 'height', 'name'

        Returns:
            Liste von Empfehlungs-Strings
        """
        recommendations = []

        # Berechne Kanten
        f1_left = frame1['x']
        f1_right = frame1['x'] + frame1['width']
        f1_bottom = frame1['y']
        f1_top = frame1['y'] + frame1['height']

        f2_left = frame2['x']
        f2_right = frame2['x'] + frame2['width']
        f2_bottom = frame2['y']
        f2_top = frame2['y'] + frame2['height']

        # Berechne Überlappungsbereiche
        overlap_left = max(f1_left, f2_left)
        overlap_right = min(f1_right, f2_right)
        overlap_bottom = max(f1_bottom, f2_bottom)
        overlap_top = min(f1_top, f2_top)

        overlap_width = overlap_right - overlap_left
        overlap_height = overlap_top - overlap_bottom

        # Horizontale Überlappung
        if overlap_width > 0:
            # Frame 1 ist links von Frame 2
            if f1_left < f2_left:
                # Option 1: Frame 1 width reduzieren
                new_width1 = f1_right - f1_left - overlap_width - 1
                recommendations.append(
                    f"Reduziere '{frame1['name']}' width von {frame1['width']} auf {new_width1:.0f} "
                    f"(um {overlap_width + 1:.0f} Einheiten)"
                )
                # Option 2: Frame 2 nach rechts verschieben
                new_x2 = f2_left + overlap_width + 1
                recommendations.append(
                    f"Verschiebe '{frame2['name']}' x von {frame2['x']} auf {new_x2:.0f} "
                    f"(um {overlap_width + 1:.0f} Einheiten nach rechts)"
                )
            # Frame 2 ist links von Frame 1
            else:
                # Option 1: Frame 2 width reduzieren
                new_width2 = f2_right - f2_left - overlap_width - 1
                recommendations.append(
                    f"Reduziere '{frame2['name']}' width von {frame2['width']} auf {new_width2:.0f} "
                    f"(um {overlap_width + 1:.0f} Einheiten)"
                )
                # Option 2: Frame 1 nach rechts verschieben
                new_x1 = f1_left + overlap_width + 1
                recommendations.append(
                    f"Verschiebe '{frame1['name']}' x von {frame1['x']} auf {new_x1:.0f} "
                    f"(um {overlap_width + 1:.0f} Einheiten nach rechts)"
                )

        # Vertikale Überlappung
        if overlap_height > 0:
            # Frame 1 ist unter Frame 2
            if f1_bottom < f2_bottom:
                # Option 1: Frame 1 height reduzieren
                new_height1 = f1_top - f1_bottom - overlap_height - 1
                recommendations.append(
                    f"Reduziere '{frame1['name']}' height von {frame1['height']} auf {new_height1:.0f} "
                    f"(um {overlap_height + 1:.0f} Einheiten)"
                )
                # Option 2: Frame 2 nach oben verschieben
                new_y2 = f2_bottom + overlap_height + 1
                recommendations.append(
                    f"Verschiebe '{frame2['name']}' y von {frame2['y']} auf {new_y2:.0f} "
                    f"(um {overlap_height + 1:.0f} Einheiten nach oben)"
                )
            # Frame 2 ist unter Frame 1
            else:
                # Option 1: Frame 2 height reduzieren
                new_height2 = f2_top - f2_bottom - overlap_height - 1
                recommendations.append(
                    f"Reduziere '{frame2['name']}' height von {frame2['height']} auf {new_height2:.0f} "
                    f"(um {overlap_height + 1:.0f} Einheiten)"
                )
                # Option 2: Frame 1 nach oben verschieben
                new_y1 = f1_bottom + overlap_height + 1
                recommendations.append(
                    f"Verschiebe '{frame1['name']}' y von {frame1['y']} auf {new_y1:.0f} "
                    f"(um {overlap_height + 1:.0f} Einheiten nach oben)"
                )

        return recommendations

    def _check_overlapping_frames(self, scene):
        """
        Prüft ob sich Renamer-Rechtecke überlappen.
        Polyline-Pfade (DIRECTION=POLYLINE_PATH) werden nicht geprüft.

        Args:
            scene: Scene-Configuration Dictionary
        """
        print("\n" + "="*60)
        print("Prüfe auf überlappende Renamer-Rechtecke")
        print("="*60)

        # Sammle alle Rechteck-Frames (ohne POLYLINE_PATH)
        rectangle_frames = []
        for frame_config in scene.get('renaming_frames', []):
            frame_type = frame_config.get('type', 'rectangle')
            direction = frame_config.get('direction', '')

            # Überspringe POLYLINE_PATH Frames
            if frame_type == 'polyline_path' or direction == 'POLYLINE_PATH':
                continue

            # Überspringe Frames ohne Positionsangaben
            if 'x' not in frame_config or 'y' not in frame_config:
                continue

            rectangle_frames.append({
                'name': frame_config.get('name', 'UNKNOWN'),
                'x': frame_config['x'],
                'y': frame_config['y'],
                'width': frame_config.get('width', 0),
                'height': frame_config.get('height', 0),
                'direction': direction
            })

        # Prüfe alle Paare von Rechtecken auf Überlappung
        overlaps_found = []
        for i in range(len(rectangle_frames)):
            for j in range(i + 1, len(rectangle_frames)):
                frame1 = rectangle_frames[i]
                frame2 = rectangle_frames[j]

                if self._rectangles_overlap(frame1, frame2):
                    overlap_area = self._calculate_overlap_area(frame1, frame2)
                    recommendations = self._calculate_overlap_recommendations(frame1, frame2)
                    overlaps_found.append({
                        'frame1': frame1,
                        'frame2': frame2,
                        'overlap_area': overlap_area,
                        'recommendations': recommendations
                    })

        # Gib Warnungen aus
        if overlaps_found:
            print(f"\nWARNUNG: {len(overlaps_found)} Überlappung(en) zwischen Renamer-Rechtecken gefunden:")
            for overlap in overlaps_found:
                f1 = overlap['frame1']
                f2 = overlap['frame2']
                area = overlap['overlap_area']
                print(f"  Frame '{f1['name']}' (Position: {f1['x']}, {f1['y']}, "
                      f"Größe: {f1['width']}x{f1['height']}) "
                      f"überlappt mit Frame '{f2['name']}' "
                      f"(Position: {f2['x']}, {f2['y']}, Größe: {f2['width']}x{f2['height']})")
                print(f"    Überlappungsfläche: {area:.1f} Quadrateinheiten")
                print(f"    Empfohlene Änderungen (wähle eine):")
                for rec in overlap['recommendations']:
                    print(f"      - {rec}")
        else:
            print(f"\nOK: Keine Überlappungen zwischen {len(rectangle_frames)} Renamer-Rechtecken gefunden")

    def _point_in_rectangle(self, point, rect_x, rect_y, rect_width, rect_height):
        """
        Prüft ob ein Punkt innerhalb eines Rechtecks liegt.

        Args:
            point: (x, y) Tupel
            rect_x, rect_y: Linke obere Ecke des Rechtecks
            rect_width, rect_height: Breite und Höhe des Rechtecks
        """
        x, y = point
        return (rect_x <= x <= rect_x + rect_width and
                rect_y <= y <= rect_y + rect_height)

    def _calculate_shift_to_rectangle(self, point, rect_x, rect_y, rect_width, rect_height):
        """
        Berechnet den minimalen Verschiebungsvektor, um einen Punkt in ein Rechteck zu verschieben.

        Args:
            point: (x, y) Tupel
            rect_x, rect_y: Linke untere Ecke des Rechtecks
            rect_width, rect_height: Breite und Höhe des Rechtecks

        Returns:
            (delta_x, delta_y) - Verschiebungsvektor
        """
        x, y = point
        delta_x = 0
        delta_y = 0

        # Berechne Verschiebung in x-Richtung
        if x < rect_x:
            delta_x = rect_x - x
        elif x > rect_x + rect_width:
            delta_x = (rect_x + rect_width) - x

        # Berechne Verschiebung in y-Richtung
        if y < rect_y:
            delta_y = rect_y - y
        elif y > rect_y + rect_height:
            delta_y = (rect_y + rect_height) - y

        return (delta_x, delta_y)

    def _point_in_polygon(self, point, polygon):
        """
        Prüft, ob ein Punkt innerhalb eines Polygons liegt (Ray-casting Algorithmus).
        """
        x, y = point
        n = len(polygon)
        inside = False

        p1x, p1y = polygon[0]
        for i in range(1, n + 1):
            p2x, p2y = polygon[i % n]
            if y > min(p1y, p2y):
                if y <= max(p1y, p2y):
                    if x <= max(p1x, p2x):
                        if p1y != p2y:
                            xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
                        if p1x == p2x or x <= xinters:
                            inside = not inside
            p1x, p1y = p2x, p2y

        return inside

    def _circle_intersects_polyline(self, center, radius, polyline):
        """
        Prüft ob ein Kreis mit Mittelpunkt 'center' und Radius 'radius'
        irgendein Segment der Polylinie schneidet.

        Args:
            center: (x, y) Mittelpunkt des Kreises
            radius: Radius des Kreises
            polyline: Liste von (x, y) Punkten der Polylinie

        Returns:
            True wenn der Kreis die Polylinie schneidet
        """
        cx, cy = center

        # Prüfe jedes Segment der Polylinie
        for i in range(len(polyline) - 1):
            p1 = polyline[i]
            p2 = polyline[i + 1]

            # Berechne den kürzesten Abstand vom Kreis-Mittelpunkt zum Linien-Segment
            dist = self._distance_point_to_segment(center, p1, p2)

            # Wenn Abstand <= Radius, dann gibt es eine Schnittmenge
            if dist <= radius:
                return True

        return False

    def _distance_point_to_segment(self, point, seg_start, seg_end):
        """
        Berechnet den kürzesten Abstand von einem Punkt zu einem Linien-Segment.

        Args:
            point: (x, y) Punkt
            seg_start: (x, y) Start des Segments
            seg_end: (x, y) Ende des Segments

        Returns:
            Kürzester Abstand
        """
        px, py = point
        x1, y1 = seg_start
        x2, y2 = seg_end

        # Vektor von seg_start zu seg_end
        dx = x2 - x1
        dy = y2 - y1

        # Spezialfall: Segment ist ein Punkt
        if dx == 0 and dy == 0:
            return ((px - x1)**2 + (py - y1)**2)**0.5

        # Parameter t für die Projektion des Punktes auf die Linie
        # t = 0 bedeutet seg_start, t = 1 bedeutet seg_end
        t = max(0, min(1, ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy)))

        # Nächster Punkt auf dem Segment
        closest_x = x1 + t * dx
        closest_y = y1 + t * dy

        # Abstand vom Punkt zum nächsten Punkt auf dem Segment
        return ((px - closest_x)**2 + (py - closest_y)**2)**0.5

    def _validate_symbols_in_frames(self, scene):
        """
        Prüft ob alle generierten Symbole aus ma_groups in mindestens einem Renamer-Frame liegen.

        Für POLYLINE_PATH Frames: Prüft ob die Polylinie einen Kreis um das Symbol schneidet
        Für andere Frames: Prüft ob das Symbol innerhalb des Frames liegt

        Gibt Warnungen aus für Symbole, die nicht die Kriterien erfüllen.
        """
        if not self.generated_symbols:
            return

        print("\n" + "="*60)
        print("Prüfe ob alle generierten Symbole in Renamer-Frames liegen")
        print("="*60)

        # Hole Symbol-Dimensionen aus Config (feste Werte)
        dimensions = self.config.get('ma_defaults', {}).get('dimensions', {})
        ma_symbol_width = dimensions.get('width', 1210)
        ma_symbol_height = dimensions.get('height', 381)

        # Hole Multi-Frame Dimensionen
        multi_dimensions = self.config.get('general', {}).get('multi_frame', {}).get('dimensions', {})
        multi_symbol_width = multi_dimensions.get('width', 1410)
        multi_symbol_height = multi_dimensions.get('height', 381)

        # Sammle alle Frame-Boundaries
        frame_boundaries = []
        for frame_config in scene.get('renaming_frames', []):
            frame_type = frame_config.get('type', 'rectangle')
            direction = frame_config.get('direction', '')
            layer_name = frame_config.get('layer_name', '')

            if frame_type == 'polyline_path' or direction == 'POLYLINE_PATH':
                # Polyline-Pfad: verwende path_points direkt
                path_points = frame_config.get('path_points', [])
                if path_points:
                    polyline = [(p['x'], p['y']) for p in path_points]
                    frame_boundaries.append({
                        'validation_type': 'polyline_path',
                        'polyline': polyline,
                        'name': frame_config.get('name', 'UNKNOWN'),
                        'layer_name': layer_name,
                        'direction': direction
                    })
            else:
                # Rechteck: berechne Eckpunkte
                rect_x = frame_config.get('x', 0)
                rect_y = frame_config.get('y', 0)
                rect_width = frame_config.get('width', 0)
                rect_height = frame_config.get('height', 0)

                # Erstelle Rechteck als Polygon (4 Eckpunkte)
                polygon = [
                    (rect_x, rect_y),
                    (rect_x + rect_width, rect_y),
                    (rect_x + rect_width, rect_y + rect_height),
                    (rect_x, rect_y + rect_height)
                ]
                frame_boundaries.append({
                    'validation_type': 'containment',
                    'boundary': polygon,
                    'rect_x': rect_x,
                    'rect_y': rect_y,
                    'rect_width': rect_width,
                    'rect_height': rect_height,
                    'name': frame_config.get('name', 'UNKNOWN'),
                    'layer_name': layer_name,
                    'direction': direction
                })

        # Prüfe jedes generierte Symbol
        symbols_outside = []
        for symbol in self.generated_symbols:
            # Berechne Symbol-Dimensionen basierend auf IO-Text Länge
            io_text = symbol['io']
            char_count = len(io_text)
            if char_count >= 7:
                symbol_width = multi_symbol_width  # Multi-Frame (BG/MB/POT)
                symbol_height = multi_symbol_height
            else:
                symbol_width = ma_symbol_width  # MA-Frame
                symbol_height = ma_symbol_height

            # Berechne Mittelpunkt des Symbols
            # symbol['x'], symbol['y'] ist die linke untere Ecke
            symbol_center_x = symbol['x'] + symbol_width / 2
            symbol_center_y = symbol['y'] + symbol_height / 2
            symbol_point = (symbol_center_x, symbol_center_y)

            # Radius für Kreis-Test
            symbol_radius = max(symbol_width, symbol_height) / 2

            in_any_frame = False
            validation_type_used = None

            closest_frame = None
            min_shift_distance = float('inf')
            best_shift = (0, 0)

            for frame in frame_boundaries:
                # Prüfe ob Symbol auf dem richtigen Layer ist
                if frame['layer_name'] and symbol['layer'] != frame['layer_name']:
                    continue

                # Speichere den Validierungstyp für Fehlermeldung
                if validation_type_used is None:
                    validation_type_used = frame['validation_type']

                # Wähle Validierungsmethode basierend auf Frame-Typ
                if frame['validation_type'] == 'polyline_path':
                    # POLYLINE_PATH: Prüfe ob Polylinie den Kreis um das Symbol schneidet
                    if self._circle_intersects_polyline(symbol_point, symbol_radius, frame['polyline']):
                        in_any_frame = True
                        break
                    # Für Polyline-Pfade: Berechne minimalen Abstand zur Polylinie
                    # (vereinfacht: nicht implementiert, da komplex)
                else:
                    # Containment: Prüfe ob Symbol innerhalb des Frames liegt
                    if self._point_in_rectangle(
                        symbol_point,
                        frame['rect_x'],
                        frame['rect_y'],
                        frame['rect_width'],
                        frame['rect_height']
                    ):
                        in_any_frame = True
                        break

                    # Berechne Verschiebung für diesen Frame
                    shift_x, shift_y = self._calculate_shift_to_rectangle(
                        symbol_point,
                        frame['rect_x'],
                        frame['rect_y'],
                        frame['rect_width'],
                        frame['rect_height']
                    )
                    shift_distance = (shift_x**2 + shift_y**2)**0.5

                    # Speichere den nächsten Frame
                    if shift_distance < min_shift_distance:
                        min_shift_distance = shift_distance
                        best_shift = (shift_x, shift_y)
                        closest_frame = frame

            if not in_any_frame:
                symbol_with_type = symbol.copy()
                symbol_with_type['validation_type'] = validation_type_used
                symbol_with_type['symbol_radius'] = symbol_radius
                symbol_with_type['shift_x'] = best_shift[0]
                symbol_with_type['shift_y'] = best_shift[1]
                symbol_with_type['closest_frame_name'] = closest_frame.get('name', 'UNKNOWN') if closest_frame else 'UNKNOWN'
                symbol_with_type['closest_frame_dict'] = closest_frame  # Speichere das gesamte Dictionary
                symbols_outside.append(symbol_with_type)

        # Gib Warnungen aus und zeichne Fehlerkreise
        if symbols_outside:
            print(f"\nWARNUNG: {len(symbols_outside)} Symbol(e) erfüllen nicht die Frame-Kriterien:")
            for symbol in symbols_outside:
                base_msg = (f"  Symbol IO='{symbol['io']}' aus Gruppe '{symbol['group_name']}' "
                           f"an Position ({symbol['x']:.1f}, {symbol['y']:.1f}) "
                           f"auf Layer '{symbol['layer']}'")

                if symbol.get('validation_type') == 'polyline_path':
                    print(f"{base_msg}: "
                          f"Die Polylinie schneidet nicht den Umkreis des Symbols "
                          f"(Radius: {symbol['symbol_radius']:.1f})")
                else:
                    shift_x = symbol.get('shift_x', 0)
                    shift_y = symbol.get('shift_y', 0)
                    closest_frame_name = symbol.get('closest_frame_name', 'UNKNOWN')

                    if shift_x != 0 or shift_y != 0:
                        print(f"{base_msg}: "
                              f"Symbol liegt nicht innerhalb des Renamer-Rahmens '{closest_frame_name}'. "
                              f"Verschieben Sie um dx={shift_x:.1f}, dy={shift_y:.1f}")
                    else:
                        print(f"{base_msg}: "
                              f"Symbol liegt nicht innerhalb des Renamer-Rahmens")

                # Zeichne Fehlerkreis um das Symbol
                # Berechne Symbol-Zentrum (symbol['x'], symbol['y'] = linke untere Ecke)
                io_text = symbol['io']
                char_count = len(io_text)
                if char_count >= 7:
                    sym_width = multi_symbol_width
                    sym_height = multi_symbol_height
                else:
                    sym_width = ma_symbol_width
                    sym_height = ma_symbol_height

                symbol_center_x = symbol['x'] + sym_width / 2
                symbol_center_y = symbol['y'] + sym_height / 2

                self._draw_error_circle(symbol_center_x, symbol_center_y, symbol['symbol_radius'])
        else:
            print(f"\nOK: Alle {len(self.generated_symbols)} generierten Symbole erfüllen die Frame-Kriterien")

    def _draw_error_circle(self, center_x, center_y, radius):
        """
        Zeichnet einen roten Kreis um ein fehlerhaftes Symbol in die DXF-Datei.

        Args:
            center_x: X-Koordinate des Kreismittelpunkts
            center_y: Y-Koordinate des Kreismittelpunkts
            radius: Radius des Kreises
        """
        self.msp.add_circle(
            center=(center_x, center_y),
            radius=radius,
            dxfattribs={
                'layer': 'VALIDATION_ERROR',
                'color': 1  # Rot
            }
        )



    def generate(self):
        """Generiert die Testdaten basierend auf dem Test-Typ"""
        self.create_document()

        # Prüfe ob Scene in Config existiert
        if not self.config:
            print("FEHLER: Keine Config-Datei geladen")
            return False
        
        if 'test_scenes' not in self.config:
            print("FEHLER: Config enthält keinen 'test_scenes' Abschnitt")
            return False
        
        if self.scene_name not in self.config['test_scenes']:
            print(f"FEHLER: Scene '{self.scene_name}' nicht in Config gefunden")
            print(f"Verfügbare Scenes: {', '.join(self.config['test_scenes'].keys())}")
            return False
        
        # Generiere nur Nummerierung (andere Test-Typen werden später unterstützt)
        self.generate_nummerierung(self.scene_name)
        
        return True


def main():
    """Hauptfunktion mit Argparse"""
    # Versuche Config zu lesen für --help Ausgabe
    default_config = 'create_tests.json'
    config_path = Path(PROJECT_CFG) / default_config
    available_scenes = []
    scene_descriptions = {}

    if config_path.exists():
        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
                if 'test_scenes' in config:
                    for scene_name, scene_data in config['test_scenes'].items():
                        available_scenes.append(scene_name)
                        if 'description' in scene_data:
                            scene_descriptions[scene_name] = scene_data['description']
        except Exception:
            pass  # Ignoriere Fehler beim Lesen für --help

    # Erstelle Epilog mit verfügbaren Szenen
    epilog_text = "\nVerfügbare Test-Szenen:\n"
    if available_scenes:
        for scene in sorted(available_scenes):
            desc = scene_descriptions.get(scene, '')
            if desc:
                epilog_text += f"  {scene}: {desc}\n"
            else:
                epilog_text += f"  {scene}\n"
        epilog_text += f"\n(aus Konfigurationsdatei: {config_path})"
    else:
        epilog_text += "  (keine Szenen konfiguriert oder Config-Datei nicht gefunden)\n"
        epilog_text += f"  Standard-Pfad: {config_path}"

    parser = argparse.ArgumentParser(
        description='Erzeugt Test-DXF-Dateien für verschiedene Testszenarien',
        epilog=epilog_text,
        formatter_class=argparse.RawDescriptionHelpFormatter
    )

    parser.add_argument(
        '--scene',
        type=str,
        default=None,
        help='Name der Testszene (z.B. Nummerierung1, Nummerierung2). Muss in der Config existieren.'
    )

    parser.add_argument(
        '--all',
        action='store_true',
        help='Erzeugt alle konfigurierten Testszenen'
    )

    parser.add_argument(
        '--filename',
        type=str,
        default=None,
        help='Name der zu erzeugenden Testdatei (z.B. Nummerierung1.dxf). Falls nicht angegeben, wird scene + .dxf verwendet. Wird bei --all ignoriert.'
    )

    parser.add_argument(
        '--output-dir',
        type=str,
        default=None,
        help='Ausgabeverzeichnis (Standard: testdata)'
    )

    parser.add_argument(
        '--config',
        type=str,
        default=default_config,
        help=f'JSON-Konfigurationsdatei (Standard: {default_config})'
    )

    parser.add_argument(
        '--show_symbol_frames',
        action='store_true',
        help='Zeichne Rahmen um jedes Symbol, um die Grenze des Symbols zu visualisieren'
    )

    args = parser.parse_args()

    # Validierung: entweder --scene oder --all muss angegeben sein
    if not args.scene and not args.all:
        parser.error("Entweder --scene oder --all muss angegeben werden")

    if args.scene and args.all:
        parser.error("--scene und --all können nicht zusammen verwendet werden")

    # Bestimme welche Szenen zu erzeugen sind
    if args.all:
        # Lade Config, um alle Szenen zu bekommen
        config_path = Path(PROJECT_CFG) / args.config
        if not config_path.exists():
            print(f"FEHLER: Config-Datei nicht gefunden: {config_path}")
            return 1

        try:
            with open(config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
        except json.JSONDecodeError as e:
            print(f"FEHLER beim Parsen der Config-Datei: {e}")
            return 1

        if 'test_scenes' not in config:
            print("FEHLER: Config enthält keinen 'test_scenes' Abschnitt")
            return 1

        scenes_to_generate = list(config['test_scenes'].keys())
        print(f"Erzeuge alle {len(scenes_to_generate)} konfigurierten Testszenen:")
        for scene in scenes_to_generate:
            print(f"  - {scene}")
        print()
    else:
        # Einzelne Szene
        scenes_to_generate = [args.scene]

    # Generiere alle Szenen
    success_count = 0
    error_count = 0

    for scene_name in scenes_to_generate:
        # Bestimme Filename: entweder angegeben (nur bei einzelner Szene) oder aus scene ableiten
        if args.filename and not args.all:
            filename = args.filename
        else:
            filename = scene_name + '.dxf'

        # Stelle sicher, dass Dateiname .dxf Endung hat
        if not filename.endswith('.dxf'):
            filename += '.dxf'

        print(f"\n{'='*60}")
        print(f"Erzeuge Testszene: {scene_name}")
        print(f"Datei: {filename}")
        print(f"{'='*60}")

        # Generiere Testdaten
        generator = TestDataGenerator(filename, scene_name, config_file=args.config)

        if generator.generate():
            # Zeichne Symbol-Rahmen wenn gewünscht
            if args.show_symbol_frames:
                generator.draw_frames_for_generated_symbols()

            generator.save(args.output_dir)
            success_count += 1
        else:
            print(f"Fehler bei der Generierung von {scene_name}")
            error_count += 1

    # Zusammenfassung
    print(f"\n{'='*60}")
    print(f"Zusammenfassung:")
    print(f"  Erfolgreich: {success_count}")
    print(f"  Fehler: {error_count}")
    print(f"{'='*60}")

    return 0 if error_count == 0 else 1


if __name__ == '__main__':
    sys.exit(main())
