"""
Coverage analysis under the NEW definition of level resolution:
G' is a level resolution of G via S iff both parity subgraphs of G' (using
G's BFS-from-S parities) are bipartite.

Since the iso-class of G' doesn't depend on the specific labeling (only the
cardinalities |V_e|, |V_o| matter via permutation), G' is reachable iff:
- there exist achievable parity cardinalities (s, n-s) from some (G, S),
- G' admits a 2-partition into bipartite-induced subgraphs of those sizes.
"""
import sys; sys.path.insert(0, '/home/claude/build')
import networkx as nx
import time
from itertools import combinations
from collections import defaultdict
from level_cycles import (
    compute_levels, get_all_faces, level_sources,
)
from triangulation_gen import enumerate_all_triangulations
from triangulation_gen_fast import enumerate_all_triangulations_fast


def min_degree(G):
    return min(d for _, d in G.degree())


def achievable_parity_splits(reps):
    """Return set of (|V_e|, |V_o|) cardinality tuples achievable across all
    (G, S) pairs from the given triangulation reps."""
    splits = set()
    for G in reps:
        ip, emb = nx.check_planarity(G)
        if not ip: continue
        for kind, label, source_set in level_sources(G, emb):
            levels = compute_levels(G, source_set)
            even = sum(1 for v in levels.values() if v % 2 == 0)
            odd  = sum(1 for v in levels.values() if v % 2 == 1)
            splits.add((even, odd))
    return splits


def has_bipartite_partition(G, sizes):
    """Does G admit a 2-partition (S, V\\S) with |S| in `sizes` (or n-|S| in
    sizes) such that both G[S] and G[V\\S] are bipartite?"""
    V = list(G.nodes())
    n = len(V)
    accept_sizes = set(s for s in sizes if 1 <= s <= n - 1)
    accept_sizes |= set(n - s for s in sizes if 1 <= n - s <= n - 1)
    seen_sizes = sorted(accept_sizes)
    # Avoid duplicate work: check size s and size n-s give same partition
    seen = set()
    for size in seen_sizes:
        if size > n - size:
            continue
        for S_tuple in combinations(V, size):
            S = set(S_tuple)
            key = frozenset(S)
            if key in seen: continue
            seen.add(key)
            if nx.is_bipartite(G.subgraph(S)) and \
               nx.is_bipartite(G.subgraph(set(V) - S)):
                return True
    return False


def analyze(n, use_fast=False, verbose=True):
    print(f"\n{'='*60}\nn = {n}\n{'='*60}")
    t0 = time.time()
    reps = (enumerate_all_triangulations_fast(n) if use_fast
            else enumerate_all_triangulations(n))
    md4_idx = set(i for i, G in enumerate(reps) if min_degree(G) >= 4)
    print(f"{len(reps)} iso-classes, {len(md4_idx)} md4")

    splits = achievable_parity_splits(reps)
    achievable_sizes = set(s for s, _ in splits)
    print(f"Achievable parity splits: {sorted(splits)}")
    print(f"Achievable sizes: {sorted(achievable_sizes)}")

    print(f"\nReachability under NEW definition:")
    reachable, unreachable_md4 = [], []
    for i, G in enumerate(reps):
        if has_bipartite_partition(G, achievable_sizes):
            reachable.append(i)
        elif i in md4_idx:
            unreachable_md4.append(i)
        if verbose and i not in reachable and i in md4_idx:
            deg = sorted((d for _, d in G.degree()), reverse=True)
            print(f"  T{i} (md4, deg {deg}): UNREACHABLE")
    not_reachable = set(range(len(reps))) - set(reachable)
    print(f"Reachable: {len(reachable)}/{len(reps)}")
    print(f"md4 reachable: {len(md4_idx & set(reachable))}/{len(md4_idx)}")
    if not_reachable:
        print(f"Unreachable iso-classes: {sorted(not_reachable)}")
        for i in sorted(not_reachable):
            deg = sorted((d for _, d in reps[i].degree()), reverse=True)
            marker = " (md4)" if i in md4_idx else ""
            print(f"  T{i}: degree {deg}{marker}")
    print(f"Time: {time.time()-t0:.1f}s")
    return reachable, unreachable_md4


if __name__ == "__main__":
    import sys
    ns = [int(x) for x in sys.argv[1:]] if len(sys.argv) > 1 \
        else [6, 7, 8, 9, 10, 11]
    for n in ns:
        analyze(n, use_fast=(n >= 11))
