"""Draw a 5-connected non-Hamiltonian-dual core (= the dual of a non-Hamiltonian
cyclically 5-connected cubic planar graph) as a parity-coloured planar graph
with the bridge edges introduced by its witness Even Level Graph highlighted.
Style matches Fig. 6 (the n=24 core).

The script picks the first valid parity partition of minimal total Betti for
which a backward bridge-orbit search returns an Even Level Graph, and prints
the witness data (source, added bridge edges).

Usage:
  sage -python draw_core_witness.py <input_g6_file> <output_png_path>
"""
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

EVEN_C = '#9ecae1'
ODD_C = '#fdae6b'


def betti(T, labels):
    A = [v for v in T if labels[v] == 0]
    B = [v for v in T if labels[v] == 1]
    GA, GB = T.subgraph(A), T.subgraph(B)
    return (GA.number_of_edges() - len(A) + nx.number_connected_components(GA)) + \
           (GB.number_of_edges() - len(B) + nx.number_connected_components(GB))


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 find_witness(T, labels, cap):
    code = EdgeCode(T.nodes())
    s0 = code.state_of(T)
    parent = {s0: None}
    frontier = [s0]
    W = None
    while frontier and W is None and len(parent) < cap:
        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 len(parent) >= cap:
                        break
            if W or len(parent) >= cap:
                break
        if W:
            break
        frontier = nf
    if W is None:
        return None
    path = []
    c = W
    while c is not None:
        path.append(c)
        c = parent[c]
    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, len(path) - 1


def main():
    if len(sys.argv) < 3:
        print('usage: draw_core_witness.py <input_g6_file> <output_png_path>')
        sys.exit(1)
    in_g6_path, out_png = sys.argv[1], sys.argv[2]
    g6 = open(in_g6_path).read().strip()
    T, _ = dual_triangulation(sage_to_nx(Graph(g6)))
    parts, _ = valid_parity_partitions_via_coloring(T)
    order = sorted(range(len(parts)), key=lambda k: betti(T, parts[k]))
    chosen = None
    for k in order[:200]:
        r = find_witness(T, parts[k], cap=40000)
        if r is not None:
            chosen = (k, parts[k], r)
            break
    if chosen is None:
        print('no witness found in scanned partitions; aborting')
        sys.exit(2)
    k, labels, (src, added, depth) = chosen
    print('partition=%d  source=%d  depth=%d  added=%s' % (k, src, depth, added))

    fig, ax = plt.subplots(figsize=(8, 8))
    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))
    os.makedirs(os.path.dirname(out_png), exist_ok=True)
    fig.savefig(out_png, dpi=160)
    plt.close(fig)
    print('wrote', out_png)


if __name__ == '__main__':
    main()
