"""Draw the n=10 R2-violating example with a planar layout that puts
source v_0=4 in the centre.  We pick one of the depth-2 face triangles
({2,8,9} or {6,7,10}) as the outer face, which forces sage's planar
layout to put it on the outside and v_0 in the interior."""
import os
import sys
from collections import defaultdict

from sage.all import Graph

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

import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.path import Path

EDGES = [(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9),
         (2, 3), (2, 8), (2, 9), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8),
         (3, 10), (4, 5), (5, 6), (6, 7), (6, 10), (7, 8), (7, 10), (8, 9)]
SOURCE = 4
DEPTH = 1


def main():
    G = Graph(EDGES)
    G.is_planar(set_embedding=True)
    emb = G.get_embedding()
    dist = G.shortest_path_lengths(SOURCE)

    # Use sage's planar layout with external_face = the depth-2 face {2, 8, 9}.
    # The 'external_face' parameter in sage takes a face (list of edges).
    # We supply edges (2,8), (8,9), (9,2) in cyclic order.
    # Force external face to be the depth-2 triangle {2, 8, 9} by
    # giving an edge of that triangle.
    pos = G.layout(layout='planar', external_face=(2, 8))
    # Normalise positions
    xs = [p[0] for p in pos.values()]
    ys = [p[1] for p in pos.values()]
    cx, cy = (max(xs) + min(xs)) / 2, (max(ys) + min(ys)) / 2
    scale = max(max(xs) - min(xs), max(ys) - min(ys))
    pos = {v: ((p[0] - cx) / scale, (p[1] - cy) / scale)
           for v, p in pos.items()}

    # Compute depth-1 component
    faces = G.faces(embedding=emb)
    face_depths = []
    for face in faces:
        verts = set()
        for u, v in face:
            verts.add(u); verts.add(v)
        d = min(dist[v] for v in verts)
        face_depths.append((face, d, verts))
    depth1_faces = [fd for fd in face_depths if fd[1] == DEPTH]

    # Boundary edges
    edge_count = defaultdict(int)
    for face, _, _ in depth1_faces:
        for u, v in face:
            edge_count[frozenset({u, v})] += 1
    bdry_edges = {e for e, c in edge_count.items() if c == 1}

    bdry_graph = defaultdict(set)
    for e in bdry_edges:
        u, v = list(e)
        bdry_graph[u].add(v)
        bdry_graph[v].add(u)
    seen = set()
    bdry_cycles = []
    for v in bdry_graph:
        if v in seen: continue
        cv = set(); stack = [v]
        while stack:
            x = stack.pop()
            if x in seen: continue
            seen.add(x); cv.add(x)
            for y in bdry_graph[x]:
                if y not in seen: stack.append(y)
        bdry_cycles.append(cv)
    # Sort: source-side first (= contains a level-1 vertex), then by min vertex
    def cyc_key(c):
        levels = {dist[v] for v in c}
        return (min(levels), min(c))
    bdry_cycles.sort(key=cyc_key)

    print(f"Boundary cycles: {bdry_cycles}")

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

    # Fill the depth-1 face union with one polygon: use the boundary curves
    # Construct the outer boundary curve and the inner hole curves.
    # The outer-facing boundary cycle (level d, source side) — but with
    # external_face being a level-2 triangle, the "outer face" of the
    # picture is the depth-2 triangle, and the depth-1 region is between.
    # Easier: just fill each depth-1 triangle separately.
    for face, _, _ in depth1_faces:
        verts = [e[0] for e in face]
        xy = [pos[v] for v in verts]
        poly = plt.Polygon(xy, facecolor='#fff3cd', edgecolor='#e8d8a8',
                           linewidth=0.4, alpha=0.85, zorder=1)
        ax.add_patch(poly)

    # Draw ALL non-boundary edges of G (interior and outside) in light grey
    for u, v in G.edges(labels=False):
        if frozenset({u, v}) in bdry_edges:
            continue
        x1, y1 = pos[u]; x2, y2 = pos[v]
        ax.plot([x1, x2], [y1, y2], color='lightgray', linewidth=0.9,
                zorder=2)

    # Boundary cycles in different colors
    bdry_colors = ['#d62728', '#1f77b4', '#2ca02c']
    bdry_labels = [
        f'source-side boundary on $L_{{1}}$ (= $\\{{1, 3, 5\\}}$)',
        f'inner boundary A on $L_{{2}}$',
        f'inner boundary B on $L_{{2}}$',
    ]
    for i, cyc in enumerate(bdry_cycles):
        for e in bdry_edges:
            if e <= cyc:
                u, v = list(e)
                x1, y1 = pos[u]; x2, y2 = pos[v]
                ax.plot([x1, x2], [y1, y2], color=bdry_colors[i],
                        linewidth=3.5, zorder=3)

    # Draw vertices
    level_colors = {0: 'black', 1: '#1f77b4', 2: '#d62728'}
    for v in G.vertices():
        x, y = pos[v]
        c = level_colors.get(dist[v], 'gray')
        ax.plot(x, y, 'o', color=c, markersize=22, zorder=4)
        ax.annotate(f"{v}", (x, y), color='white', ha='center', va='center',
                    fontsize=11, fontweight='bold', zorder=5)

    # Source label
    sx, sy = pos[SOURCE]
    ax.annotate(f'source $v_0={SOURCE}$\n($\\ell=0$)',
                xy=(sx, sy), xytext=(sx, sy - 0.10),
                fontsize=10, ha='center', va='top', color='black', zorder=6,
                bbox=dict(boxstyle='round,pad=0.25',
                          facecolor='white', edgecolor='gray', alpha=0.95))

    legend_items = [
        plt.Line2D([], [], marker='o', color='w', markerfacecolor='black',
                   markersize=11, label=f'source ($\\ell=0$)'),
        plt.Line2D([], [], marker='o', color='w', markerfacecolor='#1f77b4',
                   markersize=11, label='$\\ell=1$'),
        plt.Line2D([], [], marker='o', color='w', markerfacecolor='#d62728',
                   markersize=11, label='$\\ell=2$'),
        patches.Patch(facecolor='#fff3cd', edgecolor='#e8d8a8',
                      label=f'$R_{{C^\\prime}}$ (depth-{DEPTH} component, '
                            f'{len(depth1_faces)} triangles)'),
    ]
    for i, lbl in enumerate(bdry_labels):
        legend_items.append(plt.Line2D([], [], color=bdry_colors[i],
                                       linewidth=3.5, label=lbl))
    ax.legend(handles=legend_items, loc='upper right', fontsize=9,
              framealpha=0.95)

    ax.set_title(
        f'(R2) violation: depth-1 component with 3 boundary cycles\n'
        f'planar embedding of n=10 maximal planar graph, '
        f'source $v_0={SOURCE}$ at centre',
        fontsize=12)
    ax.set_aspect('equal')
    ax.axis('off')
    pad = 0.08
    ax.set_xlim(min(p[0] for p in pos.values()) - pad,
                max(p[0] for p in pos.values()) + pad)
    ax.set_ylim(min(p[1] for p in pos.values()) - pad,
                max(p[1] for p in pos.values()) + pad)

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


if __name__ == '__main__':
    main()
