"""Draw a diagram illustrating dual depth.

We build a concentric ("stacked rings") triangulation G with a single level
source S = {0} at the centre, so the vertices fall into clean BFS levels
0, 1, 2, 3 by radius. We then overlay the inner (weak) dual G' -- one dual
vertex per bounded triangular face -- and colour each dual vertex by its dual
depth: the minimum level among the three vertices of the corresponding face.

The construction makes all three attainable dual depths (0, 1, 2) appear; the
outer face (the level-3 triangle) is excluded from the inner dual.
"""
import math
import os
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D

OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# ---------------------------------------------------------------------------
# Construct G: centre 0 (level 0) plus three triangular rings at radii 1,2,3.
# Vertex j of every ring sits at angle 90 + 120*j degrees, so spokes are radial.
# ---------------------------------------------------------------------------
RINGS = 3                      # ring 3 is the outer-face triangle
pos = {0: (0.0, 0.0)}
ring = {0: [0]}                # ring index -> vertex ids
nxt = 1
for r in range(1, RINGS + 1):
    ids = []
    for j in range(3):
        ang = math.radians(90 + 120 * j)
        pos[nxt] = (r * math.cos(ang), r * math.sin(ang))
        ids.append(nxt)
        nxt += 1
    ring[r] = ids

G = nx.Graph()
G.add_nodes_from(pos)

# centre fan
for v in ring[1]:
    G.add_edge(0, v)
# ring triangles
for r in range(1, RINGS + 1):
    a, b, c = ring[r]
    G.add_edges_from([(a, b), (b, c), (c, a)])
# radial spokes + annulus diagonals (inner_j -- outer_{j+1})
for r in range(1, RINGS):
    inner, outer = ring[r], ring[r + 1]
    for j in range(3):
        G.add_edge(inner[j], outer[j])              # spoke
        G.add_edge(inner[j], outer[(j + 1) % 3])    # diagonal

assert G.number_of_edges() == 3 * G.number_of_nodes() - 6, "not a triangulation"

# ---------------------------------------------------------------------------
# Levels: BFS distance from the source S = {0}.
# ---------------------------------------------------------------------------
S = {0}
level = nx.multi_source_dijkstra_path_length(G, S)

# ---------------------------------------------------------------------------
# Bounded faces (the inner dual's vertices). Listed explicitly from the
# construction; the outer face (ring 3 triangle) is omitted.
# ---------------------------------------------------------------------------
faces = []
a, b, c = ring[1]
faces += [(0, a, b), (0, b, c), (0, c, a)]          # centre fan
for r in range(1, RINGS):
    inn, out = ring[r], ring[r + 1]
    for j in range(3):
        i0, i1 = inn[j], inn[(j + 1) % 3]
        o1 = out[(j + 1) % 3]
        o0 = out[j]
        faces += [(i0, i1, o1), (i0, o1, o0)]       # the two annulus triangles

def dual_depth(face):
    return min(level[v] for v in face)

# dual vertex positions = face centroids
dpos = {}
ddepth = {}
for f in faces:
    cx = sum(pos[v][0] for v in f) / 3.0
    cy = sum(pos[v][1] for v in f) / 3.0
    dpos[f] = (cx, cy)
    ddepth[f] = dual_depth(f)

# dual edges: two bounded faces sharing an edge of G
edge_faces = {}
for f in faces:
    for i in range(3):
        e = frozenset((f[i], f[(i + 1) % 3]))
        edge_faces.setdefault(e, []).append(f)
dual_edges = [fs for fs in edge_faces.values() if len(fs) == 2]

# ---------------------------------------------------------------------------
# Draw.
# ---------------------------------------------------------------------------
LEVEL_COLOR = {0: '#1e293b', 1: '#475569', 2: '#94a3b8', 3: '#cbd5e1'}
DEPTH_COLOR = {0: '#16a34a', 1: '#2563eb', 2: '#dc2626'}

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

# G edges (light) and vertices (labelled by level)
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='#d1d5db', width=1.4)
for v, (x, y) in pos.items():
    ax.scatter([x], [y], s=520, color=LEVEL_COLOR[level[v]],
               edgecolors='black', linewidths=1.0, zorder=3)
    ax.text(x, y, f'{v}\n$\\ell{{=}}{level[v]}$', ha='center', va='center',
            color='white', fontsize=8.5, fontweight='bold', zorder=4)

# dual edges (dashed) and dual vertices (squares, coloured by dual depth)
for f0, f1 in dual_edges:
    (x0, y0), (x1, y1) = dpos[f0], dpos[f1]
    ax.plot([x0, x1], [y0, y1], color='#fb923c', lw=1.3, ls='--', zorder=2)
for f, (x, y) in dpos.items():
    ax.scatter([x], [y], s=300, marker='s', color=DEPTH_COLOR[ddepth[f]],
               edgecolors='black', linewidths=0.8, zorder=5)
    ax.text(x, y, str(ddepth[f]), ha='center', va='center',
            color='white', fontsize=9, fontweight='bold', zorder=6)

legend = [
    Line2D([0], [0], marker='o', color='w', label='$G$ vertex (label = level $\\ell$)',
           markerfacecolor='#475569', markeredgecolor='black', markersize=12),
    Line2D([0], [0], color='#fb923c', ls='--', lw=1.3, label="dual edge of $G'$"),
    Line2D([0], [0], marker='s', color='w', label='dual depth $\\delta = 0$',
           markerfacecolor=DEPTH_COLOR[0], markeredgecolor='black', markersize=11),
    Line2D([0], [0], marker='s', color='w', label='dual depth $\\delta = 1$',
           markerfacecolor=DEPTH_COLOR[1], markeredgecolor='black', markersize=11),
    Line2D([0], [0], marker='s', color='w', label='dual depth $\\delta = 2$',
           markerfacecolor=DEPTH_COLOR[2], markeredgecolor='black', markersize=11),
]
ax.legend(handles=legend, loc='upper left', fontsize=10, framealpha=0.95)

ax.set_aspect('equal')
ax.axis('off')
ax.set_title("Dual depth in a stacked-ring triangulation $G$ with source $S=\\{0\\}$.\n"
             "Each bounded face carries a dual vertex (square) coloured by its dual "
             "depth\n$\\delta(d_f)=\\min_{v\\in V(f)}\\ell(v)$. "
             "The outer face (level-3 triangle) has no dual vertex.",
             fontsize=11)
fig.tight_layout()

out = os.path.join(OUT_DIR, 'fig_dual_depth.png')
fig.savefig(out, dpi=180, bbox_inches='tight')
plt.close(fig)

# console summary
from collections import Counter
print(f'n={G.number_of_nodes()} edges={G.number_of_edges()} '
      f'bounded faces={len(faces)} dual edges={len(dual_edges)}')
print('dual depth distribution:', dict(sorted(Counter(ddepth.values()).items())))
print(f'wrote {out}')
