"""Gemeinsamer Werkzeugkasten fuer alles, was ins Blickfeld gezeichnet wird.

Hier liegen die Grundformen — Flaechen, Rahmen, Farbverlaeufe, Text und
Karten — damit Heatmap, Objektrahmen und die Renderanzeige gleich aussehen und
nicht jede Stelle ihre eigenen Rechtecke zusammenbaut.

Zwei Dinge sind bewusst anders als im urspruenglichen Add-on:

* **Text ueber ``blf``**, Blenders eigene Schriftausgabe. Die mitgelieferte
  Bitmap-Schrift zeichnet jeden Buchstabenpixel als eigenes Viereck; das wirkt
  kantig, kostet pro Zeichen dutzende Zeichenvorgaenge und benutzt mit
  ``TRI_FAN`` eine Zeichenart, die unter Vulkan und Metal nicht mehr getragen
  wird.
* **Alle Masse folgen der Oberflaechenskalierung.** Sonst ist die Anzeige auf
  einem grossen Bildschirm winzig.
"""

import contextlib

import bpy
import blf
import gpu
from gpu_extras.batch import batch_for_shader
from mathutils import Matrix


SCHRIFT = 0          # Blenders Standardschrift

# Gemeinsame Farbwelt
DUNKEL = (0.07, 0.07, 0.08, 0.92)
RAHMEN = (1.0, 0.55, 0.10, 0.9)
RAHMEN_LEISE = (0.35, 0.35, 0.38, 0.85)
TEXT = (0.95, 0.95, 0.95, 1.0)
TEXT_LEISE = (0.68, 0.68, 0.72, 1.0)
TEXT_AKZENT = (1.0, 0.72, 0.30, 1.0)


def skala(context=None):
    """Skalierung der Oberflaeche — Masse muessen ihr folgen."""
    try:
        return (context or bpy.context).preferences.system.ui_scale
    except Exception:
        return 1.0


def freie_raender(context=None):
    """Platz, den Werkzeugleiste und Seitenleiste im Blickfeld verdecken.

    Beide liegen in Blender UEBER dem Zeichenbereich, nicht daneben. Wer das
    nicht beruecksichtigt, zeichnet seine Anzeige unter die Leisten — der
    Titel der Farbskala verschwand so hinter der Werkzeugleiste.
    """
    links = rechts = 0.0
    try:
        bereich = (context or bpy.context).area
        for region in bereich.regions:
            if region.width <= 1:
                continue
            if region.type == 'TOOLS':
                links = max(links, region.width)
            elif region.type == 'UI':
                rechts = max(rechts, region.width)
    except Exception:
        pass
    return links, rechts


@contextlib.contextmanager
def pixelraum(breite, hoehe):
    """Sorgt dafuer, dass Koordinaten wieder Bildschirmpixel bedeuten.

    Der Heatmap-Zeichner setzt mit ``gpu.matrix.reset()`` die Matrizen auf
    Einheitswerte; danach rechnet die Grafikkarte in Clip-Koordinaten von -1
    bis 1. Wer dort mit Pixelwerten zeichnet, landet weit ausserhalb des
    Bildes. Dieser Block stellt eine gerade Projektion her, in der (0, 0)
    unten links und (breite, hoehe) oben rechts liegt — und raeumt danach
    wieder auf.
    """
    breite = max(1.0, float(breite))
    hoehe = max(1.0, float(hoehe))
    projektion = Matrix(((2.0 / breite, 0.0, 0.0, -1.0),
                         (0.0, 2.0 / hoehe, 0.0, -1.0),
                         (0.0, 0.0, -1.0, 0.0),
                         (0.0, 0.0, 0.0, 1.0)))
    gpu.matrix.push()
    gpu.matrix.push_projection()
    try:
        gpu.matrix.load_identity()
        gpu.matrix.load_projection_matrix(projektion)
        yield
    finally:
        gpu.matrix.pop_projection()
        gpu.matrix.pop()


def flaeche(x, y, breite, hoehe, farbe):
    """Gefuelltes Rechteck aus zwei Dreiecken ('TRI_FAN' ist ueberholt)."""
    ecken = ((x, y), (x + breite, y), (x + breite, y + hoehe), (x, y + hoehe))
    shader = gpu.shader.from_builtin('UNIFORM_COLOR')
    batch = batch_for_shader(shader, 'TRIS', {"pos": ecken},
                             indices=((0, 1, 2), (0, 2, 3)))
    shader.uniform_float("color", farbe)
    batch.draw(shader)


def rahmen(x, y, breite, hoehe, farbe, staerke=1.0):
    flaeche(x, y, breite, staerke, farbe)
    flaeche(x, y + hoehe - staerke, breite, staerke, farbe)
    flaeche(x, y, staerke, hoehe, farbe)
    flaeche(x + breite - staerke, y, staerke, hoehe, farbe)


def verlauf_senkrecht(x, y, breite, hoehe, farben, alpha=1.0):
    """Farbverlauf von unten nach oben ueber eine Liste von Stuetzfarben."""
    if len(farben) < 2:
        return
    abschnitte = len(farben) - 1
    teil = hoehe / abschnitte
    shader = gpu.shader.from_builtin('SMOOTH_COLOR')
    for i in range(abschnitte):
        unten, oben = farben[i], farben[i + 1]
        y0 = y + i * teil
        ecken = ((x, y0), (x + breite, y0),
                 (x + breite, y0 + teil), (x, y0 + teil))
        werte = ((*unten, alpha), (*unten, alpha), (*oben, alpha), (*oben, alpha))
        batch = batch_for_shader(shader, 'TRIS',
                                 {"pos": ecken, "color": werte},
                                 indices=((0, 1, 2), (0, 2, 3)))
        batch.draw(shader)


def kurve(punkte, farbe, staerke=1.5):
    """Linienzug durch eine Punktfolge — als schmale Vierecke gezeichnet.

    ``LINE_STRIP`` mit Breite gibt es in den neueren Grafikschnittstellen nicht
    mehr verlaesslich; deshalb wird jedes Teilstueck als Rechteck gelegt.
    """
    if len(punkte) < 2:
        return
    import math
    shader = gpu.shader.from_builtin('UNIFORM_COLOR')
    ecken = []
    indizes = []
    for i in range(len(punkte) - 1):
        (x0, y0), (x1, y1) = punkte[i], punkte[i + 1]
        dx, dy = x1 - x0, y1 - y0
        laenge = math.hypot(dx, dy) or 1.0
        nx, ny = -dy / laenge * staerke / 2.0, dx / laenge * staerke / 2.0
        b = len(ecken)
        ecken.extend([(x0 + nx, y0 + ny), (x1 + nx, y1 + ny),
                      (x1 - nx, y1 - ny), (x0 - nx, y0 - ny)])
        indizes.extend([(b, b + 1, b + 2), (b, b + 2, b + 3)])
    batch = batch_for_shader(shader, 'TRIS', {"pos": ecken}, indices=indizes)
    shader.uniform_float("color", farbe)
    batch.draw(shader)


def flaeche_unter_kurve(punkte, grundlinie, farben, alpha=0.5):
    """Fuellt den Raum zwischen Linienzug und Grundlinie mit Farbverlauf."""
    if len(punkte) < 2:
        return
    shader = gpu.shader.from_builtin('SMOOTH_COLOR')
    ecken, werte, indizes = [], [], []
    for i in range(len(punkte) - 1):
        (x0, y0), (x1, y1) = punkte[i], punkte[i + 1]
        f0 = farben[min(len(farben) - 1, i)]
        f1 = farben[min(len(farben) - 1, i + 1)]
        b = len(ecken)
        ecken.extend([(x0, grundlinie), (x1, grundlinie), (x1, y1), (x0, y0)])
        werte.extend([(*f0, alpha * 0.25), (*f1, alpha * 0.25),
                      (*f1, alpha), (*f0, alpha)])
        indizes.extend([(b, b + 1, b + 2), (b, b + 2, b + 3)])
    batch = batch_for_shader(shader, 'TRIS', {"pos": ecken, "color": werte},
                             indices=indizes)
    batch.draw(shader)


def text(x, y, groesse, farbe, inhalt, schatten=True, rechtsbuendig=False):
    """Schreibt Text und liefert seine Masse zurueck."""
    blf.size(SCHRIFT, int(max(8, groesse)))
    if rechtsbuendig:
        breite, _ = blf.dimensions(SCHRIFT, inhalt)
        x -= breite
    blf.color(SCHRIFT, *farbe)
    if schatten:
        blf.enable(SCHRIFT, blf.SHADOW)
        blf.shadow(SCHRIFT, 5, 0.0, 0.0, 0.0, 0.85)
        blf.shadow_offset(SCHRIFT, 1, -1)
    blf.position(SCHRIFT, x, y, 0)
    blf.draw(SCHRIFT, inhalt)
    if schatten:
        blf.disable(SCHRIFT, blf.SHADOW)
    return blf.dimensions(SCHRIFT, inhalt)


def textmasse(groesse, inhalt):
    """Breite und Hoehe eines Textes bei einer bestimmten Groesse."""
    blf.size(SCHRIFT, int(max(8, groesse)))
    return blf.dimensions(SCHRIFT, inhalt)


def textbreite(groesse, inhalt):
    return textmasse(groesse, inhalt)[0]


def karte(x, y, breite, hoehe, titel=None, s=1.0, alpha=1.0,
          rahmenfarbe=None):
    """Dunkle Karte mit optionaler Kopfzeile.

    Rueckgabe: die y-Hoehe, ab der Inhalt gezeichnet werden kann (unter dem Titel).
    """
    grund = (DUNKEL[0], DUNKEL[1], DUNKEL[2], DUNKEL[3] * alpha)
    flaeche(x, y, breite, hoehe, grund)
    rahmen(x, y, breite, hoehe,
           tuple(list(rahmenfarbe or RAHMEN_LEISE)[:3]) + ((rahmenfarbe or RAHMEN_LEISE)[3] * alpha,),
           max(1.0, s))
    if not titel:
        return y + hoehe - 6 * s

    kopfhoehe = 22 * s
    flaeche(x, y + hoehe - kopfhoehe, breite, kopfhoehe,
            (RAHMEN[0], RAHMEN[1], RAHMEN[2], 0.18 * alpha))
    text(x + 10 * s, y + hoehe - kopfhoehe + 6 * s, 12 * s,
         (TEXT_AKZENT[0], TEXT_AKZENT[1], TEXT_AKZENT[2], alpha), titel)
    return y + hoehe - kopfhoehe - 4 * s


def zeile(x, y, breite, beschriftung, wert, s=1.0, alpha=1.0, groesse=11):
    """Eine Zeile 'Beschriftung .......... Wert' — Wert rechtsbuendig."""
    text(x, y, groesse * s, (TEXT_LEISE[0], TEXT_LEISE[1], TEXT_LEISE[2], alpha),
         beschriftung)
    text(x + breite, y, groesse * s, (TEXT[0], TEXT[1], TEXT[2], alpha),
         str(wert), rechtsbuendig=True)


def zahl_kurz(wert):
    """Grosse Zahlen lesbar kuerzen: 1.234.567 -> 1.23M"""
    try:
        wert = float(wert)
    except Exception:
        return str(wert)
    if abs(wert) >= 1_000_000_000:
        return f"{wert / 1_000_000_000:.2f}B"
    if abs(wert) >= 1_000_000:
        return f"{wert / 1_000_000:.2f}M"
    if abs(wert) >= 1_000:
        return f"{wert / 1_000:.1f}k"
    if wert == int(wert):
        return str(int(wert))
    return f"{wert:.1f}"
