"""Generate small figures for the menagerie note.

Each figure is a small graph drawing.  Edges are colored when a Tait
coloring is being illustrated; otherwise edges are gray.

Figures produced:
  fig_path.png     -- path P_5 with a proper 3-edge-coloring
  fig_cycle.png    -- cycle C_6 with a proper 3-edge-coloring
  fig_corona.png   -- corona C_5 ∘ K_1 with a proper 3-edge-coloring
  fig_star.png     -- star K_{1,3} with a proper 3-edge-coloring
  fig_blocktree.png -- a subcubic outerplanar example showing two
                       cycle-blocks attached at a cut-vertex
"""
import math
import os
import matplotlib.pyplot as plt


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

COLORS = ['#1f77b4', '#d62728', '#2ca02c']   # a, b, c for the 3 colors


def draw_graph(ax, pos, edges, edge_colors=None, vertex_color='black',
               vertex_size=140, edge_width=2.5, show_labels=True):
    """Draw a small graph; pos = dict v->(x,y); edges = list of (u,v);
    edge_colors = list of color strings matching edges, or None for gray."""
    for i, (u, v) in enumerate(edges):
        x1, y1 = pos[u]; x2, y2 = pos[v]
        c = edge_colors[i] if edge_colors else '#888888'
        ax.plot([x1, x2], [y1, y2], color=c, linewidth=edge_width,
                solid_capstyle='round', zorder=1)
    for v, (x, y) in pos.items():
        ax.plot(x, y, 'o', color=vertex_color, markersize=vertex_size**0.5 * 2,
                zorder=2)
        if show_labels:
            ax.annotate(str(v), (x, y), color='white', ha='center',
                        va='center', fontsize=8, fontweight='bold', zorder=3)


def fig_path(filename, n=5):
    """Path P_n with a proper 3-edge-coloring."""
    pos = {i: (i, 0) for i in range(n)}
    edges = [(i, i+1) for i in range(n-1)]
    # Greedy 3-edge-coloring: alternate
    edge_colors = []
    used = None
    for i in range(n-1):
        if i == 0:
            c = 0
        else:
            c = (used + 1) % 3
        edge_colors.append(COLORS[c])
        used = c
    fig, ax = plt.subplots(figsize=(4.2, 1.0))
    draw_graph(ax, pos, edges, edge_colors=edge_colors)
    ax.set_xlim(-0.5, n - 0.5)
    ax.set_ylim(-0.5, 0.5)
    ax.set_aspect('equal'); ax.axis('off')
    plt.savefig(filename, dpi=160, bbox_inches='tight')
    plt.close()


def fig_cycle(filename, n=6):
    """Cycle C_n with a proper 3-edge-coloring (alternating where possible)."""
    pos = {i: (math.cos(2*math.pi*i/n + math.pi/2),
               math.sin(2*math.pi*i/n + math.pi/2)) for i in range(n)}
    edges = [(i, (i+1) % n) for i in range(n)]
    # For even n, alternate 2 colors and one edge gets the 3rd
    if n % 2 == 0:
        ec = []
        for i in range(n):
            ec.append(COLORS[i % 2])
    else:
        ec = [COLORS[i % 3] for i in range(n)]
        # may not be proper for general n; let's adjust manually for n=6
    if n == 6:
        ec = [COLORS[0], COLORS[1], COLORS[0], COLORS[1], COLORS[0], COLORS[1]]
    fig, ax = plt.subplots(figsize=(2.4, 2.4))
    draw_graph(ax, pos, edges, edge_colors=ec)
    ax.set_xlim(-1.3, 1.3); ax.set_ylim(-1.3, 1.3)
    ax.set_aspect('equal'); ax.axis('off')
    plt.savefig(filename, dpi=160, bbox_inches='tight')
    plt.close()


def fig_corona(filename, n=5):
    """Corona C_n ∘ K_1: cycle with one leaf attached to each vertex."""
    pos = {}
    for i in range(n):
        pos[i] = (math.cos(2*math.pi*i/n + math.pi/2),
                  math.sin(2*math.pi*i/n + math.pi/2))
    for i in range(n):
        # leaf at radius 1.6
        pos[n + i] = (1.65 * math.cos(2*math.pi*i/n + math.pi/2),
                       1.65 * math.sin(2*math.pi*i/n + math.pi/2))
    cycle_edges = [(i, (i+1) % n) for i in range(n)]
    leaf_edges = [(i, n + i) for i in range(n)]
    edges = cycle_edges + leaf_edges
    # Color the cycle as alternating (or for odd n, use 3 colors)
    ec = []
    if n % 2 == 0:
        for i in range(n):
            ec.append(COLORS[i % 2])
        # Each leaf gets the 3rd color (since at each vertex cycle uses 2 colors)
        for i in range(n):
            ec.append(COLORS[2])
    else:
        # n=5: cycle needs 3 colors
        # Use a manual valid coloring
        ec = [COLORS[0], COLORS[1], COLORS[0], COLORS[1], COLORS[2]]
        # Each leaf at vertex i gets the third color absent at vertex i
        # Vertex 0: cycle edges colored COLORS[4-1=color of last edge] = COLORS[2], COLORS[0]; missing = COLORS[1]
        # Hmm let me compute per vertex
        cycle_color_at_vertex = [None] * n
        for i, (u, v) in enumerate(cycle_edges):
            cycle_color_at_vertex[u] = ec[i] if cycle_color_at_vertex[u] is None else (cycle_color_at_vertex[u], ec[i])
            cycle_color_at_vertex[v] = ec[i] if cycle_color_at_vertex[v] is None else (cycle_color_at_vertex[v], ec[i])
        # For vertex v: 2 cycle colors, leaf gets 3rd
        leaf_color = []
        for v in range(n):
            used = set()
            for i, (a, b) in enumerate(cycle_edges):
                if v in (a, b):
                    used.add(ec[i])
            third = [c for c in COLORS if c not in used][0]
            leaf_color.append(third)
        ec += leaf_color
    fig, ax = plt.subplots(figsize=(3.6, 3.6))
    draw_graph(ax, pos, edges, edge_colors=ec)
    ax.set_xlim(-2.0, 2.0); ax.set_ylim(-2.0, 2.0)
    ax.set_aspect('equal'); ax.axis('off')
    plt.savefig(filename, dpi=160, bbox_inches='tight')
    plt.close()


def fig_star(filename):
    """Star K_{1,3} = central vertex + 3 leaves."""
    pos = {0: (0, 0),
           1: (1, 0),
           2: (math.cos(2*math.pi/3), math.sin(2*math.pi/3)),
           3: (math.cos(4*math.pi/3), math.sin(4*math.pi/3))}
    edges = [(0, 1), (0, 2), (0, 3)]
    ec = [COLORS[0], COLORS[1], COLORS[2]]   # one of 3! = 6 proper colorings
    fig, ax = plt.subplots(figsize=(2.4, 2.4))
    draw_graph(ax, pos, edges, edge_colors=ec)
    ax.set_xlim(-1.3, 1.3); ax.set_ylim(-1.3, 1.3)
    ax.set_aspect('equal'); ax.axis('off')
    plt.savefig(filename, dpi=160, bbox_inches='tight')
    plt.close()


def fig_blocktree(filename):
    """A subcubic outerplanar with block-cut tree structure: two cycles
    glued at a cut vertex."""
    pos = {
        # left cycle (triangle)
        0: (-2.0, 0.7),
        1: (-2.0, -0.7),
        2: (-1.0, 0.0),
        # right cycle (4-cycle)
        3: (-0.0, 0.7),
        4: (1.0, 0.7),
        5: (1.0, -0.7),
        6: (0.0, -0.7),
    }
    # block 1 is a triangle 0-1-2; vertex 2 is the cut-vertex
    edges_block1 = [(0, 1), (1, 2), (0, 2)]
    # block 2 is a 4-cycle 2-3-4-5-6-2; oh wait vertex 2 should connect into it
    # Let me redo: block 2 vertices are 2, 3, 4, 5, 6 forming a 5-cycle
    pos[2] = (-1.0, 0.0)
    pos[3] = (-0.3, 0.7)
    pos[4] = (0.8, 0.7)
    pos[5] = (0.8, -0.7)
    pos[6] = (-0.3, -0.7)
    edges_block2 = [(2, 3), (3, 4), (4, 5), (5, 6), (6, 2)]
    edges = edges_block1 + edges_block2
    # Proper 3-edge-coloring: tricky for triangle + 5-cycle
    # Triangle: needs 3 distinct colors
    # 5-cycle: 3-edge-coloring possible (3 colors arranged like 0-1-0-1-2 or similar)
    # At cut vertex 2: incident to 2 edges from each block (triangle: 1-2 and 0-2; 5-cycle: 2-3 and 6-2)
    # 2's incident edges: (0,2), (1,2), (2,3), (6,2) — 4 edges. Δ = 4!
    # So this example has Δ=4 at vertex 2. Not subcubic.
    # Let me redo with Δ ≤ 3.
    # Maybe block 2 should be a triangle too, joined at a vertex.
    # Triangle 0-1-2 + Triangle 2-3-4, sharing vertex 2.
    pos = {
        0: (-2.0, 0.7),
        1: (-2.0, -0.7),
        2: (-1.0, 0.0),
        3: (0.0, 0.7),
        4: (0.0, -0.7),
    }
    edges = [(0, 1), (1, 2), (0, 2),
             (2, 3), (3, 4), (2, 4)]
    # At vertex 2: 4 edges (1-2, 0-2, 2-3, 2-4) — STILL Δ=4!
    # Two triangles sharing a vertex = bowtie; degree at shared vertex = 4. NOT subcubic.
    # For subcubic + outerplanar with 2 cycle-blocks at a cut-vertex: impossible
    # because the cut-vertex has degree >= 4 (2 from each block).
    # So actually for subcubic outerplanar, cut-vertices can have AT MOST one
    # cycle-block incident.  i.e. 2 cycle-blocks share an EDGE (forming a single
    # 2-connected block), not just a vertex.
    # Let me instead show: a triangle with 3 pendants (corona-like) but as a
    # tree-of-blocks: triangle + tree pendant.
    pos = {
        0: (-1.0, 0.7),
        1: (-1.0, -0.7),
        2: (0.0, 0.0),
        3: (1.0, 0.5),
        4: (1.6, 1.0),
        5: (1.0, -0.5),
        6: (1.6, -1.0),
    }
    edges = [(0, 1), (1, 2), (0, 2),     # triangle block
             (2, 3), (3, 4),               # tree pendant 1
             (2, 5), (5, 6)]               # tree pendant 2
    # NOW: deg at 2 = 4. Still not Δ ≤ 3.
    # Hmm. Triangle + 2 pendants at one vertex: 2+2 = 4 at shared vertex.
    # For subcubic: at most 1 pendant at any triangle vertex.
    pos = {
        0: (-1.0, 0.7),
        1: (-1.0, -0.7),
        2: (0.0, 0.0),
        3: (1.0, 0.0),
        4: (2.0, 0.5),
        5: (2.0, -0.5),
    }
    edges = [(0, 1), (1, 2), (0, 2),     # triangle
             (2, 3),                      # pendant edge to 3
             (3, 4), (3, 5)]              # 3 branches into 2 leaves
    # vertex 2 degree: 3 (to 0, 1, 3). ✓
    # vertex 3 degree: 3 (to 2, 4, 5). ✓
    # All others ≤ 2. So Δ = 3. ✓
    # And it's outerplanar (planar with all vertices on outer face).
    ec = [COLORS[0], COLORS[1], COLORS[2],   # triangle: 3 distinct colors
          COLORS[0],                          # 2-3
          COLORS[1], COLORS[2]]               # 3-4, 3-5 (must differ from 2-3 and each other)
    fig, ax = plt.subplots(figsize=(5.0, 2.4))
    draw_graph(ax, pos, edges, edge_colors=ec)
    ax.set_xlim(-1.6, 2.5); ax.set_ylim(-1.2, 1.2)
    ax.set_aspect('equal'); ax.axis('off')
    plt.savefig(filename, dpi=160, bbox_inches='tight')
    plt.close()


def main():
    out_dir = HERE
    fig_path(os.path.join(out_dir, 'fig_path.png'), n=5)
    fig_cycle(os.path.join(out_dir, 'fig_cycle.png'), n=6)
    fig_corona(os.path.join(out_dir, 'fig_corona.png'), n=5)
    fig_star(os.path.join(out_dir, 'fig_star.png'))
    fig_blocktree(os.path.join(out_dir, 'fig_blocktree.png'))
    print(f"Wrote figures to {out_dir}")


if __name__ == '__main__':
    main()
