# -*- coding: utf-8 -*-
"""
Bodygraph-Renderer im Human Design Analyse-Stil.
Zeichnet aus einem hd_engine-Chart eine hochwertige PNG-Grafik:
klassisches Bodygraph-Layout, Design (rot) / Personality (anthrazit),
warmer Cream-Hintergrund, Gold-Akzente.
"""

import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon, Circle, FancyBboxPatch
from matplotlib.path import Path
import matplotlib.patches as mpatches

from hd_engine import (compute_chart, CHANNELS, GATE_CENTER,
                       PLANET_ORDER, PLANET_SYMBOLS, CHANNEL_NAMES)

# ----------------------------------------------------------------
# Farben (Human Design Analyse)
# ----------------------------------------------------------------
BG = '#FAF8F3'
GOLD = '#B8923D'
DARK = '#3D3D3A'
TEXT2 = '#5F5E5A'
RED = '#A93226'          # Design
CHARCOAL = '#2C2C2A'     # Personality
TUBE_EDGE = '#CFC9BA'
OPEN_FILL = '#FFFFFF'
OPEN_EDGE = '#C6C0B0'

CENTER_FILL = {
    'Kopf': '#F2E3B3', 'Ajna': '#BCD0A8', 'Kehle': '#DCBE8F',
    'G': '#F2E3B3', 'Herz': '#E2A18F', 'Sakral': '#E2A18F',
    'Milz': '#DCBE8F', 'Solarplexus': '#DCBE8F', 'Wurzel': '#DCBE8F',
}
CENTER_EDGE = {
    'Kopf': '#C9A93F', 'Ajna': '#7C9A60', 'Kehle': '#A97E3F',
    'G': '#C9A93F', 'Herz': '#B05A42', 'Sakral': '#B05A42',
    'Milz': '#A97E3F', 'Solarplexus': '#A97E3F', 'Wurzel': '#A97E3F',
}

# ----------------------------------------------------------------
# Geometrie: Zentren als Polygone
# ----------------------------------------------------------------
CENTER_SHAPES = {
    'Kopf':  [(0, 13.45), (-1.45, 12.2), (1.45, 12.2)],
    'Ajna':  [(-1.45, 11.5), (1.45, 11.5), (0, 10.25)],
    'Kehle': [(-1.2, 9.95), (1.2, 9.95), (1.2, 7.85), (-1.2, 7.85)],
    'G':     [(0, 7.25), (1.25, 6.0), (0, 4.75), (-1.25, 6.0)],
    'Herz':  [(1.45, 5.1), (2.95, 4.6), (1.45, 4.1)],
    'Milz':  [(-5.6, 3.45), (-3.4, 2.4), (-5.6, 1.35)],
    'Solarplexus': [(5.6, 3.45), (3.4, 2.4), (5.6, 1.35)],
    'Sakral': [(-1.15, 3.55), (1.15, 3.55), (1.15, 1.25), (-1.15, 1.25)],
    'Wurzel': [(-1.15, 1.05), (1.15, 1.05), (1.15, -1.25), (-1.15, -1.25)],
}

# Dezente Beschriftung der Zentren (Position, Ausrichtung)
CENTER_LABELS = {
    'Kopf':  ((0, 12.92), 'center'),
    'Ajna':  ((1.62, 11.05), 'left'),
    'Kehle': ((0, 8.88), 'center'),
    'G':     ((0, 6.02), 'center'),
    'Herz':  ((2.35, 5.22), 'left'),
    'Milz':  ((-4.95, 2.42), 'center'),
    'Solarplexus': ((4.85, 0.95), 'center'),
    'Sakral': ((0, 2.38), 'center'),
    'Wurzel': ((0, -0.12), 'center'),
}

# Tor-Anker (dort sitzt der Tor-Kreis, dort enden die Kanäle)
GATE_POS = {
    # Kopf
    64: (-0.78, 12.45), 61: (0, 12.45), 63: (0.78, 12.45),
    # Ajna
    47: (-0.78, 11.27), 24: (0, 11.27), 4: (0.78, 11.27),
    17: (-0.50, 10.85), 43: (0, 10.50), 11: (0.50, 10.85),
    # Kehle
    62: (-0.72, 9.70), 23: (0, 9.70), 56: (0.72, 9.70),
    16: (-0.97, 9.32), 35: (0.97, 9.32),
    20: (-0.97, 8.50), 12: (0.97, 8.48), 45: (0.97, 8.90),
    31: (-0.72, 8.10), 8: (0, 8.10), 33: (0.72, 8.10),
    # G
    1: (0, 6.95), 7: (-0.58, 6.45), 13: (0.58, 6.45),
    10: (-1.0, 6.0), 25: (0.95, 5.90),
    15: (-0.50, 5.50), 46: (0.50, 5.50), 2: (0, 5.05),
    # Herz
    21: (1.75, 4.88), 51: (1.75, 4.32), 26: (2.25, 4.47), 40: (2.62, 4.60),
    # Milz (Anker leicht innerhalb)
    48: (-5.18, 3.10), 57: (-4.68, 2.87), 44: (-4.18, 2.64),
    50: (-3.72, 2.42),
    32: (-4.18, 2.16), 28: (-4.68, 1.93), 18: (-5.18, 1.70),
    # Solarplexus
    36: (5.18, 3.10), 22: (4.68, 2.87), 37: (4.18, 2.64),
    6: (3.72, 2.42),
    49: (4.18, 2.16), 55: (4.68, 1.93), 30: (5.18, 1.70),
    # Sakral
    5: (-0.68, 3.28), 14: (0, 3.28), 29: (0.68, 3.28),
    27: (-0.88, 2.75), 59: (0.88, 2.75), 34: (-0.88, 2.08),
    42: (-0.68, 1.52), 3: (0, 1.52), 9: (0.68, 1.52),
    # Wurzel
    53: (-0.68, 0.78), 60: (0, 0.78), 52: (0.68, 0.78),
    54: (-0.88, 0.32), 38: (-0.88, -0.18), 58: (-0.88, -0.68),
    19: (0.88, 0.32), 39: (0.88, -0.18), 41: (0.88, -0.68),
}

# Kanal-Routen: (gateA, gateB) -> optionaler Bezier-Kontrollpunkt
# (None = gerade Linie)
CHANNEL_CTRL = {
    (16, 48): (-3.9, 6.8), (35, 36): (3.9, 6.8),
    (12, 22): (3.2, 5.6), (21, 45): (1.6, 6.8),
    (10, 20): (-1.45, 7.3), (10, 34): (-1.65, 4.0),
    (10, 57): (-3.1, 4.6), (20, 34): (-2.0, 5.3),
    (20, 57): (-3.6, 6.2), (34, 57): (-2.5, 2.2),
    (26, 44): (-0.9, 3.95),
}


def channel_path(a, b, n=80):
    """Punktfolge des Kanals von Tor a nach Tor b."""
    p0 = np.array(GATE_POS[a], float)
    p2 = np.array(GATE_POS[b], float)
    key = (a, b) if (a, b) in CHANNEL_CTRL else (b, a)
    if key in CHANNEL_CTRL:
        if key != (a, b):
            p0, p2 = p2, p0
        c = np.array(CHANNEL_CTRL[key], float)
        t = np.linspace(0, 1, n)[:, None]
        pts = (1 - t) ** 2 * p0 + 2 * (1 - t) * t * c + t ** 2 * p2
        if key != (a, b):
            pts = pts[::-1]
        return pts
    t = np.linspace(0, 1, n)[:, None]
    return (1 - t) * p0 + t * p2


def draw_half(ax, pts, color, lw, zorder, dashed=False):
    style = (0, (1.6, 1.4)) if dashed else 'solid'
    ax.plot(pts[:, 0], pts[:, 1], color=color, lw=lw,
            solid_capstyle='round', dash_capstyle='round',
            linestyle=style, zorder=zorder)


def render_bodygraph(chart, outfile, subtitle_extra=None):
    fig, ax = plt.subplots(figsize=(10.6, 13.6), dpi=200)
    fig.patch.set_facecolor(BG)
    ax.set_facecolor(BG)
    ax.set_xlim(-8.6, 8.6)
    ax.set_ylim(-3.8, 16.4)
    ax.set_aspect('equal')
    ax.axis('off')

    gp = chart['gates_personality']
    gd = chart['gates_design']
    defined = chart['defined_centers']

    # ---------- 1. Kanal-Röhren (Basis) ----------
    for (a, b) in CHANNELS:
        pts = channel_path(a, b)
        ax.plot(pts[:, 0], pts[:, 1], color=TUBE_EDGE, lw=8.5,
                solid_capstyle='round', zorder=1)
        ax.plot(pts[:, 0], pts[:, 1], color='white', lw=6.0,
                solid_capstyle='round', zorder=2)

    # ---------- 2. Aktive Kanal-Hälften ----------
    for (a, b) in CHANNELS:
        pts = channel_path(a, b)
        half = len(pts) // 2 + 4          # leichte Überlappung in der Mitte
        for gate, seg in [(a, pts[:half]), (b, pts[len(pts) - half:])]:
            in_p, in_d = gate in gp, gate in gd
            if in_p and in_d:
                draw_half(ax, seg, CHARCOAL, 6.0, 3)
                draw_half(ax, seg, RED, 6.0, 4, dashed=True)
            elif in_d:
                draw_half(ax, seg, RED, 6.0, 3)
            elif in_p:
                draw_half(ax, seg, CHARCOAL, 6.0, 3)

    # ---------- 3. Zentren ----------
    for name, verts in CENTER_SHAPES.items():
        is_def = name in defined
        poly = Polygon(verts, closed=True,
                       facecolor=CENTER_FILL[name] if is_def else OPEN_FILL,
                       edgecolor=CENTER_EDGE[name] if is_def else OPEN_EDGE,
                       lw=2.2, zorder=5)
        poly.set_path_effects([])
        ax.add_patch(poly)

    # ---------- 3b. Dezente Zentren-Beschriftung ----------
    for name, ((lx_, ly_), ha) in CENTER_LABELS.items():
        label = 'G' if name == 'G' else name.upper()
        ax.text(lx_, ly_, label, ha=ha, va='center',
                fontsize=7.2, color='#9A9384', zorder=6,
                alpha=0.95)

    # ---------- 4. Tor-Kreise ----------
    for gate, (x, y) in GATE_POS.items():
        in_p, in_d = gate in gp, gate in gd
        if in_p and in_d:
            face, edge, tcol, ew = CHARCOAL, RED, 'white', 1.8
        elif in_d:
            face, edge, tcol, ew = RED, RED, 'white', 1.0
        elif in_p:
            face, edge, tcol, ew = CHARCOAL, CHARCOAL, 'white', 1.0
        else:
            face, edge, tcol, ew = 'white', OPEN_EDGE, '#9B968A', 0.9
        ax.add_patch(Circle((x, y), 0.185, facecolor=face,
                            edgecolor=edge, lw=ew, zorder=7))
        ax.text(x, y, str(gate), ha='center', va='center',
                fontsize=6.4, color=tcol, zorder=8,
                fontweight='bold' if (in_p or in_d) else 'normal')

    # ---------- 5. Planeten-Spalten ----------
    def planet_panel(x0, side, header, color):
        rows = len(PLANET_ORDER)
        top, rh = 12.9, 0.78
        h = rows * rh + 1.05
        box = FancyBboxPatch((x0, top - h), 2.45, h,
                             boxstyle='round,pad=0.12,rounding_size=0.18',
                             facecolor='white', edgecolor='#DDD7C8',
                             lw=1.3, zorder=4)
        ax.add_patch(box)
        ax.text(x0 + 1.22, top - 0.42, header, ha='center', va='center',
                fontsize=11.5, color=color, fontweight='bold', zorder=6)
        ax.plot([x0 + 0.25, x0 + 2.2], [top - 0.78, top - 0.78],
                color='#E4DECF', lw=1.1, zorder=6)
        for i, p in enumerate(PLANET_ORDER):
            a = chart['activations'][side][p]
            y = top - 1.18 - i * rh
            ax.text(x0 + 0.42, y, PLANET_SYMBOLS[p], ha='center',
                    va='center', fontsize=12.5, color=color, zorder=6)
            ax.text(x0 + 1.55, y, f"{a['gate']}.{a['line']}",
                    ha='center', va='center', fontsize=11,
                    color=DARK, zorder=6,
                    fontfamily='DejaVu Sans Mono')

    planet_panel(-8.35, 'design', 'Design', RED)
    planet_panel(5.9, 'personality', 'Personality', CHARCOAL)

    # ---------- 5b. Variables-Pfeile ----------
    v = chart['variables']

    def draw_arrow(var, x_inner, x_outer, y, color):
        left = (var['arrow'] == 'links')
        if left:
            tip, tail = (min(x_inner, x_outer), y), (max(x_inner, x_outer), y)
        else:
            tip, tail = (max(x_inner, x_outer), y), (min(x_inner, x_outer), y)
        ax.annotate('', xy=tip, xytext=tail,
                    arrowprops=dict(arrowstyle='-|>', color=color,
                                    lw=2.4, mutation_scale=17), zorder=6)
        cx = (x_inner + x_outer) / 2
        ax.text(cx, y + 0.28, f"{var['color']}.{var['tone']}",
                ha='center', va='bottom', fontsize=9, fontweight='bold',
                color=color, zorder=6)
        ax.text(cx, y - 0.30, var['name'], ha='center', va='top',
                fontsize=7.2, color=TEXT2, zorder=6)

    draw_arrow(v['determination'], -2.95, -1.95, 13.0, RED)
    draw_arrow(v['environment'], -2.95, -1.95, 11.75, RED)
    draw_arrow(v['motivation'], 1.95, 2.95, 13.0, CHARCOAL)
    draw_arrow(v['perspective'], 1.95, 2.95, 11.75, CHARCOAL)

    # ---------- 6. Titel & Eckdaten ----------
    ax.text(0, 15.75, chart['name'], ha='center', va='center',
            fontsize=25, color=DARK, fontweight='bold',
            fontfamily='DejaVu Serif')
    ax.plot([-2.5, -0.65], [15.05, 15.05], color=GOLD, lw=1.1)
    ax.plot([0.65, 2.5], [15.05, 15.05], color=GOLD, lw=1.1)
    ax.text(0, 15.05, '\u2726', ha='center', va='center',
            fontsize=13, color=GOLD)
    ax.text(0, 14.45, f"{chart['type']}  ·  Autorität: {chart['authority']}",
            ha='center', va='center', fontsize=12.5, color=TEXT2)
    ax.text(0, 13.92, f"Profil {chart['profile']}  ·  {chart['definition']}",
            ha='center', va='center', fontsize=12.5, color=TEXT2)

    # ---------- 7. Fußzeile ----------
    ax.text(0, -1.95, chart['cross'], ha='center', va='center',
            fontsize=11.5, color=DARK, style='italic')
    if subtitle_extra:
        ax.text(0, -2.5, subtitle_extra, ha='center', va='center',
                fontsize=10, color=TEXT2)
    # Legende
    lx = -8.2
    ax.plot([lx, lx + 0.55], [-3.15, -3.15], color=RED, lw=5,
            solid_capstyle='round')
    ax.text(lx + 0.75, -3.15, 'Design (unbewusst)', fontsize=9,
            color=TEXT2, va='center')
    ax.plot([lx + 4.7, lx + 5.25], [-3.15, -3.15], color=CHARCOAL, lw=5,
            solid_capstyle='round')
    ax.text(lx + 5.45, -3.15, 'Personality (bewusst)', fontsize=9,
            color=TEXT2, va='center')
    ax.plot([lx + 9.9, lx + 10.45], [-3.15, -3.15], color=CHARCOAL, lw=5,
            solid_capstyle='round')
    ax.plot([lx + 9.9, lx + 10.45], [-3.15, -3.15], color=RED, lw=5,
            linestyle=(0, (1.2, 1.1)), dash_capstyle='round')
    ax.text(lx + 10.65, -3.15, 'beide', fontsize=9, color=TEXT2, va='center')
    ax.text(8.3, -3.5, 'Human Design Analyse', fontsize=8.5,
            color=GOLD, ha='right', style='italic')

    plt.savefig(outfile, dpi=200, bbox_inches='tight',
                facecolor=BG, edgecolor='none')
    plt.close(fig)
    return outfile


if __name__ == '__main__':
    # Muster: Dirks verifizierter Chart
    chart = compute_chart(1976, 3, 31, 14, 0, name='Dirk Zessin')
    render_bodygraph(chart, '/home/claude/bodygraph_muster.png',
                     subtitle_extra='31.03.1976 · 15:00 · Hamburg')
    print('Muster erstellt.')
