"""Test the "single-edge-flip preimage" recipe for md5 triangulations.

For each md5 iso-class T at n:
  1. For each face S = (a, b, c) of T:
       For each edge e in {(a,b), (b,c), (a,c)}:
         Construct G = T with edge e flipped to its apex edge.
         Run the algorithm on (G, S).
         Check whether the algorithm's output equals T (label-exact).

Report success rate per iso-class.
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import networkx as nx
from collections import defaultdict
from triangulation_gen_fast import enumerate_all_triangulations_fast
from level_cycles import get_all_faces
from simple_level_resolution_coverage import (
    simple_level_resolutions, is_triangulation,
)


def is_md5(T):
    return min(T.degree(v) for v in T.nodes()) >= 5


def single_flip(T, u, v):
    """Flip edge (u,v) in T, returning the flipped graph (or None if invalid)."""
    if not T.has_edge(u, v):
        return None
    ip, emb = nx.check_planarity(T)
    if not ip:
        return None
    f1 = emb.traverse_face(u, v)
    f2 = emb.traverse_face(v, u)
    if len(f1) != 3 or len(f2) != 3:
        return None
    w = next(x for x in f1 if x != u and x != v)
    x = next(y for y in f2 if y != u and y != v)
    if w == x or T.has_edge(w, x):
        return None
    G = T.copy()
    G.remove_edge(u, v)
    G.add_edge(w, x)
    return G


def test_iso_class(T, n, idx):
    """Try the single-edge-flip recipe on every face-edge pair."""
    T_edges = set(frozenset(e) for e in T.edges())
    ip, emb = nx.check_planarity(T)
    if not ip:
        return 0, 0
    faces = get_all_faces(emb)
    triangle_faces = [f for f in faces if len(f) == 3]
    attempts = 0
    successes = 0
    success_examples = []
    for face in triangle_faces:
        a, b, c = face
        source_set = {a, b, c}
        for (u, v) in [(a, b), (b, c), (a, c)]:
            G = single_flip(T, u, v)
            if G is None:
                continue
            attempts += 1
            for Gp in simple_level_resolutions(G, source_set):
                if not is_triangulation(Gp, n):
                    continue
                Gp_edges = set(frozenset(e) for e in Gp.edges())
                if Gp_edges == T_edges:
                    successes += 1
                    if len(success_examples) < 3:
                        success_examples.append({
                            'face': tuple(face),
                            'flip_edge': (u, v),
                            'source': sorted(source_set),
                        })
                    break
    return attempts, successes, success_examples


def main(ns):
    for n in ns:
        print(f"\n=== n={n} ===")
        if n >= 11:
            isos = enumerate_all_triangulations_fast(n)
        from triangulation_gen import enumerate_all_triangulations
        if n < 11:
            isos = enumerate_all_triangulations(n)
        md5 = [(i, T) for i, T in enumerate(isos) if is_md5(T)]
        print(f"  {len(md5)} md5 iso-class(es)")
        for idx, T in md5:
            ds = sorted([T.degree(v) for v in T.nodes()], reverse=True)
            result = test_iso_class(T, n, idx)
            if len(result) == 3:
                attempts, successes, examples = result
            else:
                attempts, successes = result
                examples = []
            print(f"  iso[{idx}] ds={ds}:")
            print(f"    attempts={attempts}, successes={successes}")
            if examples:
                for ex in examples[:1]:
                    print(f"    example: face={ex['face']}, "
                          f"flip={ex['flip_edge']}, source={ex['source']}")


if __name__ == "__main__":
    ns = [int(x) for x in sys.argv[1:]] if len(sys.argv) > 1 else [12, 13]
    main(ns)
