"""Draw the dual of the unique 44-vertex non-Hamiltonian cyclically
5-connected cubic planar graph (Holton-McKay Fig. 2.10): a 24-vertex
5-connected triangulation T. Same style as draw_witnesses.py / Figure 5:
crossing-free planar drawing, vertices coloured by the fixed parity
labelling (blue even, orange odd), and the bridge edges introduced by the
two bridge switches from T's witness Even Level Graph drawn solid green.

Writes ../figures/fig210_dual.png.
"""
import sys
import os
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
                'level_resolutions_of_maximal_planar_graphs/experiments')
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import networkx as nx
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from sage.all import Graph  # type: ignore
from tutte_dual_treecolor import dual_triangulation
from test_tutte_bridge import valid_parity_partitions_via_coloring
from test_fig210_dual_bridge import sage_to_nx
from fast_bridge import EdgeCode, parity_bridges
from test_conjecture import is_even_level_graph

HERE = os.path.dirname(os.path.abspath(__file__))
FDIR = os.path.join(HERE, '..', 'figures')
EVEN_C = '#9ecae1'
ODD_C = '#fdae6b'


def build():
    g6 = open(os.path.join(HERE, 'fig210_dual.g6')).read().strip()
    T, _ = dual_triangulation(sage_to_nx(Graph(g6)))
    parts, _ = valid_parity_partitions_via_coloring(T)
    labels = parts[9]                       # the witness-bearing partition
    return T, labels


def neighbors(code, labels, state):
    G = code.graph_of(state)
    ok, emb = nx.check_planarity(G)
    ea = {v: set() for v in code.nodes if labels[v] == 0}
    oa = {v: set() for v in code.nodes if labels[v] == 1}
    for u, v in G.edges():
        if labels[u] == labels[v]:
            (ea if labels[u] == 0 else oa)[u].add(v)
            (ea if labels[u] == 0 else oa)[v].add(u)
    br = parity_bridges(ea) | parity_bridges(oa)
    for u, v in G.edges():
        f1 = emb.traverse_face(u, v)
        if len(f1) != 3:
            continue
        f2 = emb.traverse_face(v, u)
        if len(f2) != 3:
            continue
        w = next(a for a in f1 if a not in (u, v))
        x = next(b for b in f2 if b not in (u, v))
        if w == x or G.has_edge(w, x) or labels[w] != labels[x]:
            continue
        if labels[u] == labels[v] and frozenset((u, v)) not in br:
            continue
        yield (state & ~(1 << code.bit(u, v))) | (1 << code.bit(w, x))


def elg_src(code, labels, state):
    G = code.graph_of(state)
    for s in code.nodes:
        cs = labels[s]
        nb = set(G.neighbors(s))
        if not nb or any(labels[w] == cs for w in nb):
            continue
        ok, lv = is_even_level_graph(G, frozenset({s}))
        if ok and all((lv[v] % 2 == 0) == (labels[v] == cs) for v in code.nodes):
            return s
    return None


def witness_added_edges(T, labels):
    """Backward bridge BFS to the ELG; return (source, [added bridge edges])."""
    code = EdgeCode(T.nodes())
    s0 = code.state_of(T)
    parent = {s0: None}
    frontier = [s0]
    W = None
    while frontier and W is None:
        nf = []
        for st in frontier:
            if elg_src(code, labels, st) is not None:
                W = st
                break
            for ns in neighbors(code, labels, st):
                if ns not in parent:
                    parent[ns] = st
                    nf.append(ns)
        if W:
            break
        frontier = nf
    path = []
    c = W
    while c is not None:
        path.append(c)
        c = parent[c]
    # path[0]=ELG ... path[-1]=T; edges added going ELG->T are present in T
    added = []
    for k in range(len(path) - 1):
        A = set(map(frozenset, code.graph_of(path[k]).edges()))
        B = set(map(frozenset, code.graph_of(path[k + 1]).edges()))
        added.append(tuple(sorted(next(iter(B - A)))))
    return elg_src(code, labels, W), added


def main():
    os.makedirs(FDIR, exist_ok=True)
    T, labels = build()
    src, added = witness_added_edges(T, labels)
    print('ELG source', src, 'bridge edges introduced', added)

    fig, ax = plt.subplots(figsize=(7.5, 7.5))
    pos = nx.planar_layout(T)
    colors = [EVEN_C if labels[v] == 0 else ODD_C for v in T.nodes()]
    hl = {frozenset(e) for e in added}
    plain = [e for e in T.edges() if frozenset(e) not in hl]
    nx.draw_networkx_edges(T, pos, edgelist=plain, ax=ax,
                           edge_color='#b0b0b0', width=0.9)
    nx.draw_networkx_edges(T, pos, edgelist=[tuple(e) for e in hl], ax=ax,
                           edge_color='#2ca02c', width=2.6)
    nx.draw_networkx_nodes(T, pos, node_color=colors, node_size=240,
                           edgecolors='#444444', linewidths=0.6, ax=ax)
    nx.draw_networkx_labels(T, pos, font_size=8, ax=ax)
    ax.margins(0.12)
    ax.axis('off')
    handles = [
        Line2D([0], [0], marker='o', color='w', markerfacecolor=EVEN_C,
               markeredgecolor='#444', markersize=9, label='even parity'),
        Line2D([0], [0], marker='o', color='w', markerfacecolor=ODD_C,
               markeredgecolor='#444', markersize=9, label='odd parity'),
        Line2D([0], [0], color='#2ca02c', lw=2.6, label='bridge edge introduced'),
    ]
    fig.legend(handles=handles, loc='lower center', ncol=3, fontsize=9,
               frameon=False)
    fig.tight_layout(rect=(0, 0.05, 1, 1))
    out = os.path.join(FDIR, 'fig210_dual.png')
    fig.savefig(out, dpi=160)
    plt.close(fig)
    print('wrote', out)


if __name__ == '__main__':
    main()
