"""Audit: how many (G, v, i) triples are covered by the
structurally-TIGHT subset of the partial proof (dropping the
n_i = 6 case which has a gap in sub-case (ii.B) of the flank-covering
lemma)?

Tight cases:
  (a') n_i = 5 (= F_flank length 4, P adjacent to both A_i and A_{i+1});
  (b') n_{i+1} = 5 (symmetric);
  (c)  n_{i+2} = n_{i+4} = 5 (= F_outer length 7, both intermediates
       adjacent to A_{i+3}, A_{i+4} ∈ V(K_b) ∩ V(K_c) via merged).

Loose cases (the proof formally claims these but has a gap on
sub-case (ii.B)):
  (a) n_i = 6
  (b) n_{i+1} = 6

This script reports per (G, v, i) whether tight-only coverage suffices.

Run with: sage experiments/audit_tight_coverage.py
"""
import os
import sys
import time

from sage.all import Graph
from sage.graphs.graph_generators import graphs

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)


def main(max_n=20, time_budget_per_n=1200):
    print("Audit of structurally TIGHT coverage of the deciding-face\n"
          "partial proof. Restricts to cases (a' = n_i = 5),\n"
          "(b' = n_{i+1} = 5), (c = n_{i+2} = n_{i+4} = 5);\n"
          "drops the (a)/(b) cases at n_k = 6 due to a gap in\n"
          "Lemma 'Flank covering, n_i = 6' (sub-case ii.B).\n")
    print(f"n_G in [12, {max_n}]\n")

    grand_triples = 0
    grand_loose_covered = 0     # by full partial proof (with gap)
    grand_tight_covered = 0     # by tight-only subset
    grand_loose_uncovered = 0   # empirical bad triples
    grand_loose_not_tight = 0   # loose-covered but not tight (= n_i = 6 reliance)
    grand_examples = []

    for n in range(12, max_n + 1):
        start = time.time()
        try:
            triangulations = list(graphs.triangulations(n, minimum_degree=5))
        except Exception as ex:
            print(f"n={n}: cannot enumerate ({ex})")
            continue
        n_t = 0; n_loose = 0; n_tight = 0; n_diff = 0
        for tri_idx, G in enumerate(triangulations):
            if time.time() - start > time_budget_per_n:
                print(f"  n={n}: timeout at tri {tri_idx}/{len(triangulations)}")
                break
            G.is_planar(set_embedding=True)
            emb = G.get_embedding()
            for v in G.vertex_iterator():
                if G.degree(v) != 5: continue
                cyc = [G.degree(u) for u in emb[v]]
                for i in range(5):
                    n_t += 1
                    n_i = cyc[(i + 1) % 5]
                    n_ip1 = cyc[(i + 2) % 5]
                    n_ip2 = cyc[(i + 3) % 5]
                    n_ip3 = cyc[(i + 4) % 5]
                    n_ip4 = cyc[(i + 5) % 5]
                    loose_a = (n_i in (5, 6))
                    loose_b = (n_ip1 in (5, 6))
                    case_c = (n_ip2 == 5 and n_ip4 == 5)
                    tight_a = (n_i == 5)
                    tight_b = (n_ip1 == 5)
                    loose = loose_a or loose_b or case_c
                    tight = tight_a or tight_b or case_c
                    if loose: n_loose += 1
                    if tight: n_tight += 1
                    if loose and not tight: n_diff += 1
                    if loose and not tight and len(grand_examples) < 10:
                        grand_examples.append({
                            'n_G': n, 'tri_idx': tri_idx, 'v': v, 'i': i,
                            'cyc': cyc,
                            'n_tuple': (n_i, n_ip1, n_ip2, n_ip3, n_ip4),
                        })
        elapsed = time.time() - start
        print(f"n={n}: {n_t} triples, loose-covered={n_loose} "
              f"({100*n_loose/max(n_t,1):.1f}%), "
              f"tight-only={n_tight} ({100*n_tight/max(n_t,1):.1f}%); "
              f"loose-not-tight={n_diff} [{elapsed:.0f}s]")
        sys.stdout.flush()
        grand_triples += n_t
        grand_loose_covered += n_loose
        grand_tight_covered += n_tight
        grand_loose_not_tight += n_diff

    grand_loose_uncovered = grand_triples - grand_loose_covered
    grand_tight_uncovered = grand_triples - grand_tight_covered
    print()
    print("=" * 70)
    print(f"Grand totals across n_G in [12, {max_n}]:")
    print(f"  (G, v, i) triples:                       {grand_triples}")
    print(f"  loose-covered (full partial proof):      "
          f"{grand_loose_covered} "
          f"({100*grand_loose_covered/max(grand_triples,1):.2f}%)")
    print(f"  loose-uncovered (= empirical bad):       "
          f"{grand_loose_uncovered} "
          f"({100*grand_loose_uncovered/max(grand_triples,1):.2f}%)")
    print(f"  tight-covered (drop n_i = 6 gap-case):   "
          f"{grand_tight_covered} "
          f"({100*grand_tight_covered/max(grand_triples,1):.2f}%)")
    print(f"  tight-uncovered (needs n_i = 6 lemma):   "
          f"{grand_tight_uncovered} "
          f"({100*grand_tight_uncovered/max(grand_triples,1):.2f}%)")
    print(f"  loose-not-tight (needs n_i = 6 lemma):   "
          f"{grand_loose_not_tight}")
    if grand_examples:
        print()
        print("  Sample triples that need the n_i = 6 lemma "
              "(NOT covered by tight subset):")
        for ex in grand_examples[:10]:
            print(f"    n={ex['n_G']}, tri#{ex['tri_idx']}, "
                  f"v={ex['v']}, i={ex['i']}: cyc={ex['cyc']}, "
                  f"(n_i,n_{{i+1}},n_{{i+2}},n_{{i+3}},n_{{i+4}})={ex['n_tuple']}")


if __name__ == '__main__':
    main()
