"""Tree structure sweep specifically on minimum-counterexample-eligible
graphs: cubic plane graphs whose primal triangulation is internally
6-connected.

By Birkhoff's theorem, any minimum 4CT counterexample is internally
6-connected.  Translated to the cubic dual: no separating cycle of
length ≤ 4 in the primal, only separating 5-cycles isolate a single
vertex.

Eligible test graphs:
  - Dodecahedron (cubic dual of icosahedron; verified internally
    6-connected via primal check).
  - BuckyBall / truncated icosahedron (cubic dual of pentakis
    dodecahedron; geodesic fullerene, all faces pentagonal or
    hexagonal).
  - Goldberg-Coxeter fullerenes if generable.

For each, find all 6-edge cuts and verify the cut-tire forest holds.
"""
import os
import sys
from itertools import combinations
from collections import defaultdict

from sage.all import Graph, graphs

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from cut_depth_label import (
    apply_procedure, compute_nice_layout,
)
from cut_tire_tree import build_tree
from tree_structure_sweep import all_six_edge_cuts, is_tree


def verify_primal_internally_6_conn(G_primal):
    """Verify the primal triangulation is internally 6-connected:
    every 5-vertex cut isolates exactly one vertex.  Returns True if
    so, False if any non-trivial 5-cut found."""
    n = G_primal.order()
    if n > 30:
        print(f'    primal has {n} vertices; skipping exhaustive '
              f'internally-6-conn check (would take too long).')
        return None  # skip
    for S in combinations(G_primal.vertices(), 5):
        H = G_primal.copy()
        H.delete_vertices(S)
        if not H.is_connected():
            ccs = H.connected_components()
            sizes = sorted(len(c) for c in ccs)
            if sizes[0] > 1:
                return False
    return True


def verify_tree_on_cut(G, cut, side_label, S_side, base_pos,
                       pendant_start_id):
    from cut_tire_tree import build_tree
    try:
        H, pos, ed, _, _, _ = apply_procedure(
            G, S_side, cut, base_pos, side_label,
            pendant_start_id=pendant_start_id)
    except Exception as e:
        return {'error': str(e)}
    if not ed:
        return {'error': 'empty ed'}
    faces_by_depth, parent_of, _ = build_tree(H, ed)
    tree_ok, cycle_at = is_tree(parent_of)
    return {
        'tree_ok': tree_ok,
        'cycle_at': cycle_at if not tree_ok else None,
        'n_tires': sum(len(f) for f in faces_by_depth.values()),
        'max_depth': max(ed.values()),
    }


def main():
    test_graphs = []

    # Dodecahedron (already known eligible)
    G = graphs.DodecahedralGraph()
    G.is_planar(set_embedding=True)
    test_graphs.append(('Dodecahedron', G, graphs.IcosahedralGraph()))

    # BuckyBall = truncated icosahedron
    G = graphs.BuckyBall()
    G.is_planar(set_embedding=True)
    # Its primal would be pentakis dodecahedron, but Sage may not have
    # it directly.  We can check planarity & cubic-ness.
    test_graphs.append(('BuckyBall', G, None))

    # Truncated octahedron — cubic planar
    try:
        G = graphs.TruncatedTetrahedralGraph()
        G.is_planar(set_embedding=True)
        if all(d == 3 for d in G.degree()):
            test_graphs.append(('TruncatedTet', G, None))
    except Exception:
        pass

    # Tutte graph — 46 vertex cubic planar
    try:
        G = graphs.TutteGraph()
        G.is_planar(set_embedding=True)
        if all(d == 3 for d in G.degree()):
            test_graphs.append(('Tutte', G, None))
    except Exception:
        pass

    print(f'Testing tree structure on {len(test_graphs)} graphs', flush=True)

    overall = {'total_cuts': 0, 'total_tires': 0, 'tree_failures': 0,
               'failures': []}

    for name, G, G_primal in test_graphs:
        print(f'\n=== {name}: V={G.order()}, E={G.size()}, '
              f'girth={G.girth()} ===', flush=True)
        if G_primal is not None:
            eligible = verify_primal_internally_6_conn(G_primal)
            if eligible is True:
                print(f'  Primal internally 6-connected: YES', flush=True)
            elif eligible is False:
                print(f'  Primal internally 6-connected: NO', flush=True)
            else:
                print(f'  Primal check skipped (too large).', flush=True)
        cuts = all_six_edge_cuts(G, max_cuts=300)
        print(f'  Found {len(cuts)} distinct 6-edge cuts', flush=True)
        base_pos = compute_nice_layout(G)
        n_cuts_tree = 0
        for cut_idx, (S, cut_edges) in enumerate(cuts):
            S0, S1 = S, frozenset(G.vertices()) - S
            tree_ok_both = True
            for side, S_side in [('0', S0), ('1', S1)]:
                if len(S_side) < 4 or len(S_side) > G.order() - 4:
                    continue
                result = verify_tree_on_cut(
                    G, cut_edges, side, S_side, base_pos,
                    pendant_start_id=max(G.vertices()) + 1 +
                    (0 if side == '0' else 200))
                overall['total_cuts'] += 1
                if 'error' in result:
                    continue
                overall['total_tires'] += result['n_tires']
                if not result['tree_ok']:
                    tree_ok_both = False
                    overall['tree_failures'] += 1
                    overall['failures'].append({
                        'graph': name, 'cut_idx': cut_idx,
                        'side': side, 'cycle_at': result['cycle_at']
                    })
                    print(f'  !!! CYCLE: cut #{cut_idx}, side {side}: '
                          f'{result["cycle_at"]}', flush=True)
            if tree_ok_both:
                n_cuts_tree += 1
        print(f'  Cuts producing trees on both sides: '
              f'{n_cuts_tree}/{len(cuts)}', flush=True)

    print(f'\n=== Summary ===', flush=True)
    print(f'Total (graph, cut, side) triples: {overall["total_cuts"]}',
          flush=True)
    print(f'Total cut tires examined: {overall["total_tires"]}', flush=True)
    print(f'Tree-structure failures: {overall["tree_failures"]}', flush=True)


if __name__ == '__main__':
    main()
