"""Find a working preimage for each md5 iso-class.

For each md5 target T at n, search (G, S) and branching choices to find
any G that the algorithm transforms to T (label-exact). Report:
  - the preimage G
  - the source S
  - the edit distance from G to T (number of edges differing)
  - what the differing edges look like
"""
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import networkx as nx
from itertools import permutations
from triangulation_gen_fast import enumerate_all_triangulations_fast
from triangulation_gen import enumerate_all_triangulations
from level_cycles import compute_levels, level_sources
from simple_level_resolution_coverage import (
    simple_level_resolutions, is_triangulation, iso_index,
)


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


def find_one_preimage(target, isos, n):
    """Find any (G, S) producing target (label-exact). Try all relabelings."""
    target_edges = set(frozenset(e) for e in target.edges())
    target_nodes = list(target.nodes())
    for G in isos:
        if n > 7:
            # Don't try all permutations for big n; just try a few.
            from random import shuffle, seed
            seed(42)
            perms = []
            for _ in range(50):
                p = list(target_nodes)
                shuffle(p)
                perms.append(tuple(p))
        else:
            perms = list(permutations(target_nodes, n))
        for perm in perms:
            relabel = {orig: perm[i]
                       for i, orig in enumerate(sorted(G.nodes()))}
            H = nx.relabel_nodes(G, relabel, copy=True)
            ip, emb = nx.check_planarity(H)
            if not ip:
                continue
            for kind, lab, source_set in level_sources(H, emb):
                count = 0
                for Hp in simple_level_resolutions(H, source_set):
                    count += 1
                    if count > 50:
                        break
                    if not is_triangulation(Hp, n):
                        continue
                    Hp_edges = set(frozenset(e) for e in Hp.edges())
                    if Hp_edges == target_edges:
                        return (H, source_set, Hp, kind, lab)
    return None


def edit_distance(G1, G2):
    """Symmetric difference of edge sets."""
    e1 = set(frozenset(e) for e in G1.edges())
    e2 = set(frozenset(e) for e in G2.edges())
    return e1 ^ e2


def main(ns):
    for n in ns:
        print(f"\n=== n={n} ===")
        if n >= 11:
            isos = enumerate_all_triangulations_fast(n)
        else:
            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)
            print(f"\n  iso[{idx}] ds={ds}")
            print(f"    T edges: {sorted(T.edges())[:8]}...")
            result = find_one_preimage(T, isos, n)
            if result is None:
                print(f"    NO preimage found in search")
                continue
            H, source_set, Hp, kind, lab = result
            diff = edit_distance(H, T)
            print(f"    preimage source kind={kind}, label={lab}")
            print(f"    source set: {sorted(source_set)}")
            print(f"    edit distance H -> T: {len(diff) // 2} edge flip(s)")
            print(f"    edges only in H: "
                  f"{sorted(e for e in diff if e in [frozenset(x) for x in H.edges()])[:10]}")
            print(f"    edges only in T: "
                  f"{sorted(e for e in diff if e in [frozenset(x) for x in T.edges()])[:10]}")
            # Inspect: are the differing edges on the source-face boundary?
            a, b, c = tuple(source_set) if len(source_set) == 3 else \
                       (None, None, None)
            if a is not None:
                src_face_edges = {frozenset([a, b]), frozenset([b, c]),
                                   frozenset([a, c])}
                src_face_in_diff = src_face_edges & diff
                print(f"    src-face edges in diff: {sorted(src_face_in_diff)}")


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