"""Test the disjunction on the Tutte graph dual: a 25-vertex
triangulation whose dual is a Tait counterexample. By the
intertwining-tree⇔dual-Hamiltonian equivalence, the dual is not
intertwining tree. So the conjecture requires it to be a derived level
graph.

Approach: backward BFS from G in the E/O-switch graph. A predecessor of
H is H' = H - wx + uv where uv is the diagonal of wx's quadrilateral in
H and uv has same-parity endpoints (so the forward switch on uv in H' is
valid).

For each candidate vertex-labelling of G (each potential level source
in some Even Level Graph), we BFS backward and check if any reached state
is itself an Even Level Graph for that labelling.
"""
import sys
import os
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
                'level_resolutions_of_maximal_planar_graphs/experiments')
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import networkx as nx
import time
from tutte_dual_treecolor import dual_triangulation
from test_conjecture import (
    bfs_levels, is_even_level_graph,
)


def is_tree(subg):
    return nx.is_tree(subg) if subg.number_of_nodes() > 0 else True


def is_intertwining_tree(G, time_limit=10.0):
    """Search for a 2-partition (A, B) such that G[A] and G[B] are trees.
    Returns the partition or None. Time-limited."""
    nodes = list(G.nodes())
    n = len(nodes)
    t0 = time.time()
    for mask in range(1, 2 ** (n - 1)):
        if time.time() - t0 > time_limit:
            return None  # timed out, unknown
        A = [nodes[0]] + [nodes[i + 1] for i in range(n - 1) if (mask >> i) & 1]
        B = [nodes[i + 1] for i in range(n - 1) if not ((mask >> i) & 1)]
        if not A or not B:
            continue
        if is_tree(G.subgraph(A)) and is_tree(G.subgraph(B)):
            return (tuple(A), tuple(B))
    return False  # no partition works


def planar_diagonal(G, u, v):
    """Given edge uv in triangulation G, return the diagonal of the
    quadrilateral around uv (= the two third vertices of the triangles
    at uv)."""
    ok, emb = nx.check_planarity(G)
    if not ok:
        return None
    f1 = emb.traverse_face(u, v)
    f2 = emb.traverse_face(v, u)
    if len(f1) != 3 or len(f2) != 3:
        return None
    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:
        return None
    return (w, x)


def backward_predecessors(G, labels):
    """Yield (G', uv, wx) where G' →forward G via switching uv (E/O in
    labels). For each edge wx of G whose diagonal uv has same-parity
    endpoints AND uv is not already an edge of G."""
    for u, v in list(G.edges()):
        diag = planar_diagonal(G, u, v)
        if diag is None:
            continue
        w, x = diag
        if labels[w] % 2 != labels[x] % 2:
            continue  # diagonal not E/O, can't be reverse-switched
        if G.has_edge(w, x):
            continue  # would create multi-edge
        Gp = G.copy()
        Gp.remove_edge(u, v)
        Gp.add_edge(w, x)
        yield Gp, (u, v), (w, x)


def find_elg_via_backward_bfs(G_start, labels, max_states=50000,
                              time_limit=120.0):
    """BFS backward from G_start. If we hit a triangulation that is an
    Even Level Graph for the same labelling (specifically, where the
    labels match BFS levels from some source in this triangulation),
    return it."""
    t0 = time.time()
    seen = {frozenset(frozenset(e) for e in G_start.edges())}
    frontier = [G_start]
    rounds = 0
    while frontier and len(seen) < max_states:
        if time.time() - t0 > time_limit:
            return None, len(seen), rounds, 'timed out'
        new = []
        for H in frontier:
            # Check if H is an Even Level Graph for some source
            # whose BFS levels match `labels` mod 2.
            for v in H.nodes():
                is_elg, lvls = is_even_level_graph(H, frozenset({v}))
                if not is_elg or lvls is None:
                    continue
                # Check if lvls parities match labels mod 2 (up to swap)
                same = all(lvls[u] % 2 == labels[u] % 2 for u in H.nodes())
                opp = all(lvls[u] % 2 != labels[u] % 2 for u in H.nodes())
                if same or opp:
                    return H, len(seen), rounds, ('match' if same else 'swapped')
            for Hp, uv, wx in backward_predecessors(H, labels):
                sig = frozenset(frozenset(e) for e in Hp.edges())
                if sig in seen:
                    continue
                seen.add(sig)
                new.append(Hp)
                if len(seen) >= max_states:
                    break
            if len(seen) >= max_states:
                break
        frontier = new
        rounds += 1
    return None, len(seen), rounds, 'exhausted' if not frontier else 'capped'


def main():
    G_orig = nx.tutte_graph()
    D, _ = dual_triangulation(G_orig)
    n = D.number_of_nodes()
    print(f'Tutte graph dual: n={n}, edges={D.number_of_edges()}')

    # Confirm not intertwining tree (we already know this, but verify)
    print('Verifying NOT intertwining tree (time-limited)...')
    result = is_intertwining_tree(D, time_limit=60.0)
    if result is False:
        print('  confirmed: no 2-tree partition exists')
    elif result is None:
        print('  search timed out; cannot confirm')
    else:
        print(f'  SURPRISE: is intertwining tree with partition {result}')

    # Try various labellings: BFS from each vertex of D, take levels.
    # For each labelling, do backward BFS to find ELG.
    print('\nSearching for backward path to an Even Level Graph...')
    for src in list(D.nodes())[:5]:  # first 5 sources
        labels = bfs_levels(D, frozenset({src}))
        print(f'  source={src}, label distribution: '
              f'even={sum(1 for l in labels.values() if l % 2 == 0)}, '
              f'odd={sum(1 for l in labels.values() if l % 2 == 1)}')
        result, n_states, rnds, status = find_elg_via_backward_bfs(
            D, labels, max_states=20000, time_limit=60.0)
        if result is not None:
            print(f'    FOUND ELG ancestor ({status}, '
                  f'{n_states} states, {rnds} rounds)')
            return
        else:
            print(f'    no ELG ancestor found ({status}, '
                  f'{n_states} states, {rnds} rounds)')

    print('No ELG found in backward orbit with the labellings tried.')


if __name__ == '__main__':
    main()
