"""
Count Even Level Graphs (ELGs) for small n.

An ELG is a pair (G, S) where G is a plane triangulation on n vertices,
S = {v} a single source vertex, and every level subgraph L_k (vertices at
BFS-distance k from v) is bipartite -- equivalently, every level cycle is
even (Definition: even-level-graph, sibling paper).

We report per n:
  - tri      : # iso-classes of plane triangulations (maximal planar graphs)
  - elg_iso  : # iso-classes of ELG *pairs* (G,v), i.e. valid sources counted
               up to Aut(G)
  - elg_root : # flag-rooted ELGs = sum over iso-classes of 4E/|Aut(G)| * s(G),
               E = 3n-6, s(G) = # valid sources. Aut(G) acts freely on the 4E
               flags, so each term is an exact integer -- the small-integer,
               automorphism-free count (the natural one for closed forms).
"""
import time
import networkx as nx
from networkx.algorithms.isomorphism import GraphMatcher

from triangulation_gen import enumerate_all_triangulations


def is_even_level_graph(G, source):
    """Every level subgraph L_k (BFS distance k from source) is bipartite,
    equivalently every level cycle is even. Mirrors test_conjecture.py in
    the even_level_graph_generators paper."""
    levels = {v: float("inf") for v in G.nodes()}
    from collections import deque
    dq = deque()
    for s in source:
        levels[s] = 0
        dq.append(s)
    while dq:
        v = dq.popleft()
        for w in G[v]:
            if levels[w] > levels[v] + 1:
                levels[w] = levels[v] + 1
                dq.append(w)
    if any(l == float("inf") for l in levels.values()):
        return False, None  # disconnected: source can't reach all vertices
    for k in range(max(levels.values()) + 1):
        L_k = G.subgraph([v for v in G.nodes() if levels[v] == k])
        if not nx.is_bipartite(L_k):
            return False, None
    return True, levels


def automorphisms(G):
    """All automorphisms of G as node->node dict mappings."""
    return list(GraphMatcher(G, G).isomorphisms_iter())


def valid_sources(G):
    """Vertices v such that (G, {v}) is an ELG."""
    return [v for v in G.nodes()
            if is_even_level_graph(G, frozenset({v}))[0]]


def source_orbits(G, sources, autos):
    """Number of Aut(G)-orbits among the given source vertices."""
    src = set(sources)
    seen, orbits = set(), 0
    for v in sources:
        if v in seen:
            continue
        orbits += 1
        for a in autos:
            seen.add(a[v])
    return orbits


def count_for_n(n):
    tris = enumerate_all_triangulations(n)
    flags = 4 * (3 * n - 6)   # # flags of any n-vertex triangulation, E = 3n-6
    elg_iso = 0
    elg_root = 0
    n_elg_tris = 0          # triangulations admitting at least one ELG source
    for G in tris:
        srcs = valid_sources(G)
        if not srcs:
            continue
        n_elg_tris += 1
        autos = automorphisms(G)
        aut_size = len(autos)
        elg_iso += source_orbits(G, srcs, autos)
        # Aut acts freely on flags, so flags//aut_size is exact per class.
        assert flags % aut_size == 0, (n, aut_size, flags)
        elg_root += (flags // aut_size) * len(srcs)
    return {
        "tri": len(tris),
        "tri_with_elg": n_elg_tris,
        "elg_iso": elg_iso,
        "elg_root": elg_root,
    }


if __name__ == "__main__":
    import sys
    ns = [int(x) for x in sys.argv[1:]] or [4, 5, 6, 7, 8]
    print(f"{'n':>3} {'tri':>6} {'tri+ELG':>8} {'elg_iso':>8} "
          f"{'elg_root':>9}  {'time':>6}")
    for n in ns:
        t0 = time.time()
        r = count_for_n(n)
        print(f"{n:>3} {r['tri']:>6} {r['tri_with_elg']:>8} "
              f"{r['elg_iso']:>8} {r['elg_root']:>9}  "
              f"{time.time()-t0:>5.1f}s")
