"""Masse des Objektrahmens und Platzierung seiner Beschriftungen.

Hier liegt die Form des Rahmens — genau einmal. Das ist der Grund, warum es
diese Ergaenzung gibt: Rahmen und Beschriftung wurden bisher an zwei Stellen
getrennt gerechnet, und zwar mit **verschiedenen** Formeln. Der gezeichnete
Rahmen (``hud/draw_bbox``) schneidet seine Ecken mit festen 10 Pixeln und laesst
sich nie kleiner als 20 Pixel halbe Kantenlaenge machen; die Beschriftung
(``hud/draw_labels``) rechnete sich daneben ein eigenes Oktagon mit einem
Eckenschnitt von 20 Prozent der halben Breite und ohne jede Mindestgroesse.

Die Folge war an beiden Enden sichtbar: Die Fuehrungslinie einer Beschriftung
setzte nicht am gezeichneten Rahmen an, sondern daneben, und das Ein- und
Ausblenden bei Mausnaehe mass den Abstand zu einer Form, die gar nicht auf dem
Bildschirm stand. Bei kleinen Objekten lagen beide Formen am weitesten
auseinander — dort, wo die Mindestgroesse greift.

Seit dem Umbau auf Module (1.40) sind die beiden Rechnungen ausserdem in
verschiedenen Dateien gelandet und koennen gar nicht mehr von selbst
zusammenbleiben. Deshalb steht die Form ab jetzt hier, und beide Seiten holen
sie sich von hier ab.
"""

import bpy
import os
import glob
import time
import math
import traceback

import gpu
import blf
from gpu_extras.batch import batch_for_shader
from bpy_extras.object_utils import world_to_camera_view
from bpy_extras import view3d_utils
from mathutils import Vector
from bpy.types import Panel, Operator, PropertyGroup, AddonPreferences
from bpy.props import (IntProperty, StringProperty, PointerProperty,
                       FloatProperty, BoolProperty, EnumProperty)

from . import state


# Laenge des 45-Grad-Eckenschnitts in Pixeln.
RAHMEN_ECKENSCHNITT = 10.0

# Kleiner als das wird ein Rahmen nicht — sonst bliebe vom Eckenschnitt nichts
# uebrig und aus dem Oktagon wuerde ein Fleck.
RAHMEN_MINDESTHALBMASS = 20.0


def rahmenmasse(halbe_breite, halbe_hoehe):
    """Die tatsaechlichen Masse des gezeichneten Rahmens.

    Rueckgabe: ``(halbe_breite, halbe_hoehe, eckenschnitt)`` — bereits auf die
    Mindestgroesse angehoben und mit dem auf halbe Kantenlaenge begrenzten
    Eckenschnitt. Wer den Rahmen zeichnet und wer etwas an ihn anhaengt,
    benutzt dieselben drei Zahlen.
    """
    hb = max(float(halbe_breite), RAHMEN_MINDESTHALBMASS)
    hh = max(float(halbe_hoehe), RAHMEN_MINDESTHALBMASS)
    # Der Schnitt darf hoechstens die halbe Seitenlaenge betragen
    schnitt = min(RAHMEN_ECKENSCHNITT, hb * 0.5)
    return hb, hh, schnitt


def oktagon(mitte_x, mitte_y, halbe_breite, halbe_hoehe):
    """Die acht Punkte des Objektrahmens, gegen den Uhrzeigersinn ab unten links.

    Die Reihenfolge ist Teil der Schnittstelle: ``punkte[4]`` ist die obere
    rechte Kante nach dem Eckenschnitt, ``punkte[5]`` die obere linke. Genau
    dort setzen die Fuehrungslinien der Beschriftungen an.
    """
    hb, hh, schnitt = rahmenmasse(halbe_breite, halbe_hoehe)
    cx, cy = float(mitte_x), float(mitte_y)
    return [
        Vector((cx - hb + schnitt, cy - hh)),   # 0 unten links
        Vector((cx + hb - schnitt, cy - hh)),   # 1 unten rechts
        Vector((cx + hb, cy - hh + schnitt)),   # 2 rechts unten
        Vector((cx + hb, cy + hh - schnitt)),   # 3 rechts oben
        Vector((cx + hb - schnitt, cy + hh)),   # 4 oben rechts
        Vector((cx - hb + schnitt, cy + hh)),   # 5 oben links
        Vector((cx - hb, cy + hh - schnitt)),   # 6 links oben
        Vector((cx - hb, cy - hh + schnitt)),   # 7 links unten
    ]


def abstand_zum_rahmen(punkt, ecken):
    """Kuerzester Abstand eines Punktes zu den Kanten des Rahmens.

    Frueher stand diese Schleife in der Beschriftungsdatei und lief ueber ein
    anderes Oktagon als das gezeichnete. Jetzt bekommt sie die Ecken herein und
    misst damit zwangslaeufig gegen das, was zu sehen ist.
    """
    kuerzester = float('inf')
    anzahl = len(ecken)
    for i in range(anzahl):
        p1 = ecken[i]
        p2 = ecken[(i + 1) % anzahl]
        kante = p2 - p1
        laenge = kante.length
        if laenge <= 0.0:
            continue
        zum_punkt = punkt - p1
        anteil = max(0.0, min(1.0, zum_punkt.dot(kante) / (laenge * laenge)))
        naechster = p1 + kante * anteil
        kuerzester = min(kuerzester, (punkt - naechster).length)
    return kuerzester


def check_label_collision(new_rect, existing_rects, min_spacing=5):
    """Prüft ob ein Label-Rechteck mit existierenden kollidiert"""
    # new_rect = (x, y, width, height)
    nx, ny, nw, nh = new_rect
    
    for ex, ey, ew, eh in existing_rects:
        # AABB Collision Check mit Spacing
        if (nx < ex + ew + min_spacing and
            nx + nw + min_spacing > ex and
            ny < ey + eh + min_spacing and
            ny + nh + min_spacing > ey):
            return True
    return False


def adjust_label_position(initial_pos, text_width, text_height, existing_rects, region, max_iterations=20):
    """Verschiebt Label-Position um Kollisionen zu vermeiden"""
    x, y = initial_pos
    
    # Spirale: Versuche Positionen in zunehmenden Abständen
    for iteration in range(max_iterations):
        offset_distance = iteration * 15  # 15px Schritte
        
        # Teste 8 Richtungen (wie Kompass)
        for angle in [0, 45, 90, 135, 180, 225, 270, 315]:
            rad = math.radians(angle)
            test_x = x + offset_distance * math.cos(rad)
            test_y = y + offset_distance * math.sin(rad)
            
            # Prüfe ob im Viewport
            if test_x < 0 or test_x + text_width > region.width:
                continue
            if test_y < 0 or test_y + text_height > region.height:
                continue
            
            # Prüfe Kollision
            test_rect = (test_x, test_y, text_width, text_height)
            if not check_label_collision(test_rect, existing_rects):
                return Vector((test_x, test_y))
    
    # Keine freie Position gefunden - Original zurückgeben
    return Vector((x, y))
