"""Draw a partial tire dual on top of a small tire graph.

Uses a spoke-only tire with m=6 outer cycle, k=4 inner cycle, no
chords.  Overlays the partial tire dual:
  - d_f vertices at centroids of annular triangles (purple squares).
  - Leaf vertices for B_out edges (orange diamonds, outside the tire).
  - Leaf vertices for B_in edges (orange diamonds, inside the inner hole).
  - Interior dual edges (dashed purple).
  - Leaf edges (dashed orange).
"""
import math
import os
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)

import matplotlib.pyplot as plt
import matplotlib.patches as patches

from tire_graph import random_tire, planar_positions


def draw_partial_tire_dual(tire, filename):
    m, k = tire['m'], tire['k']
    outer, inner = tire['outer'], tire['inner']
    triangles = tire['triangles']
    lattice_path = tire['lattice_path']

    R_out, R_in = 1.0, 0.45
    pos = planar_positions(tire, R_out=R_out, R_in=R_in)

    fig, ax = plt.subplots(figsize=(9, 9))

    # === Draw underlying tire graph faintly ===
    outer_set = set(outer)
    inner_set = set(inner)
    for u, v in tire['edges']:
        x1, y1 = pos[u]; x2, y2 = pos[v]
        if u in outer_set and v in outer_set:
            color = '#a8c9e8'; lw = 2.0
        elif u in inner_set and v in inner_set:
            color = '#e8a8a8'; lw = 2.0
        else:
            color = '#cccccc'; lw = 1.0
        ax.plot([x1, x2], [y1, y2], color=color, linewidth=lw, zorder=1)
    for v in outer:
        x, y = pos[v]
        ax.plot(x, y, 'o', color='#a8c9e8', markersize=14, zorder=2)
        ax.annotate(str(v), (x, y), color='white', ha='center', va='center',
                    fontsize=8, fontweight='bold', zorder=3)
    for v in inner:
        x, y = pos[v]
        ax.plot(x, y, 'o', color='#e8a8a8', markersize=12, zorder=2)
        ax.annotate(str(v), (x, y), color='white', ha='center', va='center',
                    fontsize=7, fontweight='bold', zorder=3)

    # === Reconstruct annular triangles in cyclic order ===
    # The triangles list from random_tire gives them in lattice-path order.
    print(f"Lattice path: {lattice_path}")
    print(f"Triangles: {triangles}")
    n_tri = len(triangles)
    assert n_tri == m + k, f"expected {m+k} triangles, got {n_tri}"

    # Compute centroid of each triangle
    centroids = []
    for tri in triangles:
        v1, v2, v3 = tri
        cx = (pos[v1][0] + pos[v2][0] + pos[v3][0]) / 3
        cy = (pos[v1][1] + pos[v2][1] + pos[v3][1]) / 3
        centroids.append((cx, cy))

    # === Compute leaves and which annular face each is incident to ===
    # For each B_out edge: find which annular triangle contains it.
    # For each B_in edge: similarly.
    from collections import defaultdict
    bout_edges = [frozenset({outer[i], outer[(i+1) % m]}) for i in range(m)]
    bin_edges = [frozenset({inner[j], inner[(j+1) % k]}) for j in range(k)]

    # Map: edge -> list of triangle indices containing it
    edge_to_tris = defaultdict(list)
    for t_idx, tri in enumerate(triangles):
        e1 = frozenset({tri[0], tri[1]})
        e2 = frozenset({tri[1], tri[2]})
        e3 = frozenset({tri[0], tri[2]})
        for e in (e1, e2, e3):
            edge_to_tris[e].append(t_idx)

    bout_leaves = []   # list of (edge, tri_idx, leaf_position)
    for e in bout_edges:
        tris_with_e = edge_to_tris[e]
        assert len(tris_with_e) == 1, \
            f"B_out edge {e} should be in exactly 1 annular triangle"
        t_idx = tris_with_e[0]
        u, v = list(e)
        midx = (pos[u][0] + pos[v][0]) / 2
        midy = (pos[u][1] + pos[v][1]) / 2
        # Push outward
        d = math.sqrt(midx**2 + midy**2)
        push = 1.18
        leaf_pos = (midx * push / d, midy * push / d)
        bout_leaves.append((e, t_idx, leaf_pos))

    bin_leaves = []
    for e in bin_edges:
        tris_with_e = edge_to_tris[e]
        assert len(tris_with_e) == 1, \
            f"B_in edge {e} should be in exactly 1 annular triangle"
        t_idx = tris_with_e[0]
        u, v = list(e)
        midx = (pos[u][0] + pos[v][0]) / 2
        midy = (pos[u][1] + pos[v][1]) / 2
        # Place the leaf aligned with the midpoint of the red edge it crosses,
        # on the central-hole side, at a fixed small distance past the
        # midpoint along the (d_f -> midpoint) direction.  This keeps the
        # leaf inside the central hole (not on the border) while making the
        # leaf edge cross the red edge exactly at its midpoint.
        cx, cy = centroids[t_idx]
        vx, vy = midx - cx, midy - cy
        norm = math.sqrt(vx * vx + vy * vy)
        if norm < 1e-9:
            nx, ny = 0.0, -1.0
        else:
            nx, ny = vx / norm, vy / norm
        offset = 0.13   # absolute distance past the midpoint
        leaf_pos = (midx + nx * offset, midy + ny * offset)
        bin_leaves.append((e, t_idx, leaf_pos))

    # === Determine interior dual cycle order ===
    # Two annular triangles are adjacent in the dual cycle iff they share an
    # interior annular edge (= an edge with both endpoints in V(B_out) ∪ V(B_in)
    # whose two incident faces are both annular triangles, i.e., shared by 2
    # of our triangles).
    # In a spoke-only tire each interior edge is shared by exactly 2 triangles.
    tri_adj = defaultdict(set)
    for e, ts in edge_to_tris.items():
        if len(ts) == 2:
            t1, t2 = ts
            tri_adj[t1].add(t2)
            tri_adj[t2].add(t1)

    # === Draw partial tire dual ===
    DUAL_COLOR = '#7d3c98'   # purple
    LEAF_COLOR = '#e67e22'   # orange

    # Interior dual edges
    for t1 in range(n_tri):
        for t2 in tri_adj[t1]:
            if t2 <= t1:
                continue
            x1, y1 = centroids[t1]
            x2, y2 = centroids[t2]
            ax.plot([x1, x2], [y1, y2], color=DUAL_COLOR, linewidth=2.0,
                    linestyle='-', zorder=4)

    # Leaf edges
    for e, t_idx, lpos in bout_leaves + bin_leaves:
        cx, cy = centroids[t_idx]
        lx, ly = lpos
        ax.plot([cx, lx], [lx and cy or cy, ly], visible=False)
        # use a proper plot:
        ax.plot([cx, lx], [cy, ly], color=LEAF_COLOR, linewidth=1.8,
                linestyle='--', zorder=4)

    # Interior dual vertices
    for t_idx, (cx, cy) in enumerate(centroids):
        ax.plot(cx, cy, 's', color=DUAL_COLOR, markersize=14, zorder=5)
        ax.annotate(f"$d_{{{t_idx}}}$", (cx, cy), color='white',
                    ha='center', va='center', fontsize=7, fontweight='bold',
                    zorder=6)

    # Leaf vertices
    for label_prefix, leaves in (('out', bout_leaves), ('in', bin_leaves)):
        for i, (e, t_idx, lpos) in enumerate(leaves):
            ax.plot(lpos[0], lpos[1], 'D', color=LEAF_COLOR,
                    markersize=11, zorder=5)
            ax.annotate(f"$\\ell^{{{label_prefix}}}_{{{i}}}$",
                        (lpos[0], lpos[1]), color='white', ha='center',
                        va='center', fontsize=6, fontweight='bold', zorder=6)

    # Legend
    legend_items = [
        plt.Line2D([], [], marker='o', color='w', markerfacecolor='#a8c9e8',
                   markersize=12, label='$B_{\\mathrm{out}}$ vertex (tire)'),
        plt.Line2D([], [], marker='o', color='w', markerfacecolor='#e8a8a8',
                   markersize=11, label='$B_{\\mathrm{in}}$ vertex (tire)'),
        plt.Line2D([], [], marker='s', color='w', markerfacecolor=DUAL_COLOR,
                   markersize=12, label='$d_f$ (interior dual vertex)'),
        plt.Line2D([], [], marker='D', color='w', markerfacecolor=LEAF_COLOR,
                   markersize=10, label='leaf vertex'),
        plt.Line2D([], [], color=DUAL_COLOR, linewidth=2.0,
                   label='interior dual edge'),
        plt.Line2D([], [], color=LEAF_COLOR, linewidth=1.8, linestyle='--',
                   label='leaf edge'),
    ]
    ax.legend(handles=legend_items, loc='upper left',
              bbox_to_anchor=(1.0, 1.0), fontsize=9, frameon=False)

    ax.set_xlim(-1.35, 1.35)
    ax.set_ylim(-1.35, 1.35)
    ax.set_aspect('equal')
    ax.axis('off')
    ax.set_title(
        f'Partial tire dual $D(T)$ (m={m}, k={k}, spoke-only)\n'
        f'underlying tire faint; $D(T) \\cong C_{{{m+k}}} \\circ K_1$',
        fontsize=12)

    plt.savefig(filename, dpi=160, bbox_inches='tight')
    plt.close()
    print(f"wrote {filename}")


def main():
    # m=6 outer, k=4 inner, no chords (spoke-only), reproducible seed
    tire = random_tire(m=6, k=4, n_chords=0, seed=3)
    out = os.path.join(HERE, 'partial_tire_dual_example.png')
    draw_partial_tire_dual(tire, out)


if __name__ == '__main__':
    main()
