"""Draw a partial tire dual D(T) for a tire whose inner outerplanar
graph O has a (non-pendant) bridge: a single edge whose removal
disconnects O into two non-trivial components.

Tire construction:
  - Outer cycle B_out: 4-cycle on {0, 1, 2, 3}.
  - Inner outerplanar O on {4..9}: two disjoint triangles {4, 5, 6}
    and {7, 8, 9}, connected by the bridge edge 6-7.
  - Annular triangulation built by hand below.

The bridge 6-7 has both incident faces in the annular region, so in
the partial tire dual it contributes an interior dual edge (not
leaves).  This makes the interior dual subgraph a theta graph: two
trivalent vertices (the two annular faces incident to the bridge,
or those with all interior edges) connected by three paths.
"""
import math
import os
import sys
from collections import defaultdict

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

from sage.all import Graph


# Explicit vertex positions
POS = {
    0: (0.0, 1.6),         # outer top
    1: (-1.6, 0.0),        # outer left
    2: (0.0, -1.6),        # outer bottom
    3: (1.6, 0.0),         # outer right
    4: (-0.65, 0.42),      # left triangle, top
    5: (-0.65, -0.42),     # left triangle, bottom
    6: (-0.20, 0.0),       # left triangle, right (= bridge endpoint 1)
    7: (0.20, 0.0),        # right triangle, left (= bridge endpoint 2)
    8: (0.65, 0.42),       # right triangle, top
    9: (0.65, -0.42),      # right triangle, bottom
}

T_EDGES = [
    # outer cycle B_out (length 4)
    (0, 1), (1, 2), (2, 3), (3, 0),
    # O: triangle 4-5-6
    (4, 5), (5, 6), (4, 6),
    # O: triangle 7-8-9
    (7, 8), (8, 9), (7, 9),
    # O: bridge
    (6, 7),
    # annular edges (spokes + diagonals chosen to triangulate)
    (0, 4), (0, 6), (0, 8),
    (1, 4), (1, 5),
    (2, 5), (2, 6), (2, 7), (2, 9),
    (3, 8), (3, 9),
    (0, 7),       # extra diagonal to triangulate top
]

# Annular triangles -- listed explicitly so we know exactly which
# faces are which.  12 of them.
TRIANGLES = [
    (0, 1, 4),     # top-left top
    (1, 4, 5),     # left side, with triangle 1
    (1, 2, 5),     # bottom-left
    (2, 5, 6),     # right of left-triangle, bottom
    (0, 4, 6),     # right of left-triangle, top
    (0, 6, 7),     # top-center over the bridge (bridge-incident)
    (2, 6, 7),     # bottom-center over the bridge (bridge-incident)
    (0, 7, 8),     # right of right-triangle, top
    (2, 7, 9),     # right of right-triangle, bottom
    (0, 8, 3),     # top-right
    (3, 8, 9),     # right side, with triangle 2
    (2, 9, 3),     # bottom-right
]

# Boundary edges:
B_OUT_EDGES = [(0, 1), (1, 2), (2, 3), (3, 0)]
# Edges of O on inner-triangle-face boundaries (= NOT the bridge):
B_IN_EDGES = [(4, 5), (5, 6), (4, 6),
              (7, 8), (8, 9), (7, 9)]
BRIDGE = (6, 7)


def main():
    # Sanity-check via sage that the graph is planar
    G = Graph(T_EDGES)
    assert G.is_planar(), "Constructed tire is not planar"
    print(f"Tire graph: n={G.order()}, m={G.size()}, planar=True")

    # Map: edge -> triangle indices containing it
    edge_to_tris = defaultdict(list)
    for i, t in enumerate(TRIANGLES):
        for e in (frozenset({t[0], t[1]}),
                  frozenset({t[1], t[2]}),
                  frozenset({t[0], t[2]})):
            edge_to_tris[e].append(i)

    # Verify each B_out edge in exactly 1 annular triangle
    for u, v in B_OUT_EDGES:
        ts = edge_to_tris[frozenset({u, v})]
        assert len(ts) == 1, f"B_out edge {u}-{v} in {ts} (expected 1)"
    # Each B_in (non-bridge O edge) in exactly 1 annular triangle
    for u, v in B_IN_EDGES:
        ts = edge_to_tris[frozenset({u, v})]
        assert len(ts) == 1, f"B_in edge {u}-{v} in {ts} (expected 1)"
    # Bridge in exactly 2 annular triangles
    bts = edge_to_tris[frozenset(BRIDGE)]
    print(f"Bridge {BRIDGE} is in annular triangles {bts}")
    assert len(bts) == 2

    # Verify every edge of T is accounted for
    seen_in_tris = set()
    for t in TRIANGLES:
        for e in (frozenset({t[0], t[1]}),
                  frozenset({t[1], t[2]}),
                  frozenset({t[0], t[2]})):
            seen_in_tris.add(e)
    all_T_edges = {frozenset(e) for e in T_EDGES}
    # Each edge appears in at least one triangle EXCEPT it might be that
    # boundary edges to non-annular faces are accounted for differently.
    # Annular triangles use boundary edges (B_out and inner-triangle-of-O
    # edges) plus interior annular edges.  Every edge of T should appear
    # in at least one annular triangle EXCEPT the inner faces of O don't
    # exist as separate annular triangles.  Wait, the inner triangle
    # faces of O (= {4,5,6} and {7,8,9}) are themselves face boundaries
    # of T but NOT in the annular region.  So edges of the inner
    # triangles of O appear in exactly ONE annular triangle (across the
    # boundary) + the inner triangle face on the other side.
    # Check: every T edge should appear in at most 2 annular triangles
    # (and at least 1 if it's incident to any annular face).
    for e in all_T_edges:
        n = len(edge_to_tris[e])
        assert n in (1, 2), \
            f"Edge {sorted(e)} appears in {n} annular triangles"

    # Interior dual adjacencies
    n_tri = len(TRIANGLES)
    tri_adj = defaultdict(set)
    for e, ts in edge_to_tris.items():
        if len(ts) == 2:
            tri_adj[ts[0]].add(ts[1])
            tri_adj[ts[1]].add(ts[0])
    print("Interior dual subgraph degree sequence:")
    for i in range(n_tri):
        print(f"  T{i} {TRIANGLES[i]}: degree {len(tri_adj[i])}")

    # Centroids of triangles (for d_f positions)
    centroids = [((POS[t[0]][0] + POS[t[1]][0] + POS[t[2]][0]) / 3,
                  (POS[t[0]][1] + POS[t[1]][1] + POS[t[2]][1]) / 3)
                 for t in TRIANGLES]

    # === Plot ===
    fig, ax = plt.subplots(figsize=(10, 9))

    outer_set = {0, 1, 2, 3}
    inner_set = {4, 5, 6, 7, 8, 9}
    bridge_fs = frozenset(BRIDGE)
    for u, v in T_EDGES:
        x1, y1 = POS[u]; x2, y2 = POS[v]
        if frozenset({u, v}) == bridge_fs:
            color, lw = '#cc6677', 2.8     # bridge of O
        elif u in outer_set and v in outer_set:
            color, lw = '#a8c9e8', 2.0
        elif u in inner_set and v in inner_set:
            color, lw = '#e8a8a8', 2.0
        else:
            color, lw = '#cccccc', 1.0
        ax.plot([x1, x2], [y1, y2], color=color, linewidth=lw, zorder=1)

    for v in outer_set:
        x, y = POS[v]
        ax.plot(x, y, 'o', color='#a8c9e8', markersize=16, zorder=2)
        ax.annotate(str(v), (x, y), color='white', ha='center', va='center',
                    fontsize=9, fontweight='bold', zorder=3)
    for v in inner_set:
        x, y = POS[v]
        ax.plot(x, y, 'o', color='#e8a8a8', markersize=13, zorder=2)
        ax.annotate(str(v), (x, y), color='white', ha='center', va='center',
                    fontsize=8, fontweight='bold', zorder=3)

    DUAL_COLOR = '#7d3c98'
    LEAF_COLOR = '#e67e22'
    BRIDGE_DUAL_COLOR = '#cc4444'

    # Interior dual edges
    for i in range(n_tri):
        for j in tri_adj[i]:
            if j <= i: continue
            ti_es = {frozenset({TRIANGLES[i][a], TRIANGLES[i][b]})
                     for a, b in [(0, 1), (1, 2), (0, 2)]}
            tj_es = {frozenset({TRIANGLES[j][a], TRIANGLES[j][b]})
                     for a, b in [(0, 1), (1, 2), (0, 2)]}
            shared = (ti_es & tj_es).pop()
            if shared == bridge_fs:
                ec, lw = BRIDGE_DUAL_COLOR, 3.0
            else:
                ec, lw = DUAL_COLOR, 2.0
            x1, y1 = centroids[i]; x2, y2 = centroids[j]
            ax.plot([x1, x2], [y1, y2], color=ec, linewidth=lw, zorder=4)

    # Leaves for B_out edges
    for u, v in B_OUT_EDGES:
        ti = edge_to_tris[frozenset({u, v})][0]
        midx = (POS[u][0] + POS[v][0]) / 2
        midy = (POS[u][1] + POS[v][1]) / 2
        d = math.sqrt(midx**2 + midy**2)
        push = 1.18
        lpos = (midx * push / d, midy * push / d)
        cx, cy = centroids[ti]
        ax.plot([cx, lpos[0]], [cy, lpos[1]], color=LEAF_COLOR,
                linewidth=1.5, linestyle='--', zorder=4)
        ax.plot(lpos[0], lpos[1], 'D', color=LEAF_COLOR, markersize=10,
                zorder=5)

    # Leaves for non-bridge edges of O (= inner triangle edges)
    for u, v in B_IN_EDGES:
        ti = edge_to_tris[frozenset({u, v})][0]
        midx = (POS[u][0] + POS[v][0]) / 2
        midy = (POS[u][1] + POS[v][1]) / 2
        cx, cy = centroids[ti]
        vx, vy = midx - cx, midy - cy
        norm = math.sqrt(vx*vx + vy*vy)
        offset = 0.10
        nx, ny = (vx / norm, vy / norm) if norm > 1e-9 else (0, -1)
        lpos = (midx + nx * offset, midy + ny * offset)
        ax.plot([cx, lpos[0]], [cy, lpos[1]], color=LEAF_COLOR,
                linewidth=1.5, linestyle='--', zorder=4)
        ax.plot(lpos[0], lpos[1], 'D', color=LEAF_COLOR, markersize=10,
                zorder=5)

    # Interior dual vertices, highlight degree-3 (trivalent)
    for ti, (cx, cy) in enumerate(centroids):
        deg = len(tri_adj[ti])
        c = '#5a2273' if deg == 3 else DUAL_COLOR
        ax.plot(cx, cy, 's', color=c, markersize=14, zorder=5)
        ax.annotate(f"$d_{{{ti}}}$", (cx, cy), color='white', ha='center',
                    va='center', fontsize=6, fontweight='bold', zorder=6)

    legend_items = [
        plt.Line2D([], [], marker='o', color='w', markerfacecolor='#a8c9e8',
                   markersize=12, label='$B_{\\mathrm{out}}$ vertex'),
        plt.Line2D([], [], marker='o', color='w', markerfacecolor='#e8a8a8',
                   markersize=11, label='$V(O)$ vertex'),
        plt.Line2D([], [], color='#a8c9e8', linewidth=2.0,
                   label='$B_{\\mathrm{out}}$ edge'),
        plt.Line2D([], [], color='#e8a8a8', linewidth=2.0,
                   label='triangle edge of $O$'),
        plt.Line2D([], [], color='#cc6677', linewidth=2.8,
                   label='bridge of $O$'),
        plt.Line2D([], [], marker='s', color='w', markerfacecolor=DUAL_COLOR,
                   markersize=12, label='$d_f$ (deg 2 in interior dual)'),
        plt.Line2D([], [], marker='s', color='w', markerfacecolor='#5a2273',
                   markersize=12,
                   label='$d_f$ (deg 3 -- trivalent theta-graph vertex)'),
        plt.Line2D([], [], marker='D', color='w', markerfacecolor=LEAF_COLOR,
                   markersize=10, label='leaf'),
        plt.Line2D([], [], color=DUAL_COLOR, linewidth=2.0,
                   label='interior dual edge'),
        plt.Line2D([], [], color=BRIDGE_DUAL_COLOR, linewidth=3.0,
                   label='dual of the bridge'),
        plt.Line2D([], [], color=LEAF_COLOR, linewidth=1.5, linestyle='--',
                   label='leaf edge'),
    ]
    ax.legend(handles=legend_items, loc='upper left',
              bbox_to_anchor=(1.02, 1.0), fontsize=8, frameon=False)

    ax.set_xlim(-2.10, 2.60)
    ax.set_ylim(-1.90, 1.90)
    ax.set_aspect('equal')
    ax.axis('off')
    ax.set_title(
        '$D(T)$ when $O$ is a barbell (two triangles + one bridge edge)\n'
        '$O$ = triangle $\\{4,5,6\\}$ $\\cup$ bridge $6$--$7$ $\\cup$ '
        'triangle $\\{7,8,9\\}$;\n'
        'bridge is interior to the annulus, so it contributes an interior '
        'dual edge (red).\n'
        'Interior dual is a theta graph (2 trivalent $d_f$ + 3 paths).',
        fontsize=10)

    HERE = os.path.dirname(os.path.abspath(__file__))
    out = os.path.join(HERE, 'partial_tire_dual_bridge.png')
    plt.savefig(out, dpi=160, bbox_inches='tight')
    plt.close()
    print(f"wrote {out}")


if __name__ == '__main__':
    main()
