"""
Complete enumeration of triangulations via flip closure.
Start from stacked triangulations (vertex insertion), then apply all
possible edge flips until closed under isomorphism. The flip graph of
triangulations on n labeled vertices is known to be connected, so this
covers all iso-classes.
"""
import networkx as nx
from collections import deque
import time

def get_all_faces(emb):
    seen, faces = set(), []
    for v in emb.nodes():
        for w in emb[v]:
            if (v, w) not in seen:
                face = emb.traverse_face(v, w)
                for i in range(len(face)):
                    seen.add((face[i], face[(i+1) % len(face)]))
                faces.append(tuple(face))
    return faces

def insert_vertex_in_face(G, face, new_v):
    Gp = G.copy(); Gp.add_node(new_v)
    for v in face: Gp.add_edge(new_v, v)
    return Gp

def edge_flips(G):
    """Yield all triangulations obtainable by a single edge flip from G."""
    ip, emb = nx.check_planarity(G)
    if not ip: return
    yielded_signatures = set()
    for u, v in G.edges():
        f1 = emb.traverse_face(u, v); f2 = emb.traverse_face(v, u)
        if len(f1) != 3 or len(f2) != 3: continue
        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 G.has_edge(w, x): continue
        Gp = G.copy()
        Gp.remove_edge(u, v); Gp.add_edge(w, x)
        # signature for dedup of yielded flips
        sig = frozenset(frozenset(e) for e in Gp.edges())
        if sig in yielded_signatures: continue
        yielded_signatures.add(sig)
        yield Gp

def enumerate_all_triangulations(n):
    """All non-isomorphic triangulations on n vertices."""
    if n < 4: return []
    # Seed with stacked triangulations
    G = nx.complete_graph(4)
    current = [G]
    for k in range(4, n):
        next_set = []
        for T in current:
            _, emb = nx.check_planarity(T)
            for face in get_all_faces(emb):
                Tp = insert_vertex_in_face(T, face, k)
                if all(not nx.is_isomorphic(Tp, q) for q in next_set):
                    next_set.append(Tp)
        current = next_set
    # Flip closure
    reps = list(current)
    frontier = list(current)
    while frontier:
        new_frontier = []
        for T in frontier:
            for Tp in edge_flips(T):
                if all(not nx.is_isomorphic(Tp, r) for r in reps):
                    reps.append(Tp); new_frontier.append(Tp)
        frontier = new_frontier
    return reps

if __name__ == "__main__":
    for n in [4, 5, 6, 7, 8]:
        t0 = time.time()
        tris = enumerate_all_triangulations(n)
        print(f"n={n}: {len(tris)} triangulations ({time.time()-t0:.1f}s)")
