"""Verify the directedness of the E/O-switch relation, and its effect on
'derived'. Compute, for T*_9 (n=9, known NOT derived) with a fixed
labelling:
  - forward orbit: states reachable FROM T*_9 (T*_9 ->* H)
  - backward orbit: states that reach T*_9 (H ->* T*_9)
and check each for an Even Level Graph.

'Derived' (directed) = some ELG reaches G = ELG in backward orbit.
"""
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
from triangulation_gen import enumerate_all_triangulations
from test_conjecture import bfs_levels, is_even_level_graph


def sig(G):
    return frozenset(frozenset(e) for e in G.edges())


def forward_neighbors(G, labels):
    ok, emb = nx.check_planarity(G)
    if not ok:
        return
    for u, v in list(G.edges()):
        if labels[u] % 2 != labels[v] % 2:
            continue
        f1 = emb.traverse_face(u, v)
        f2 = emb.traverse_face(v, u)
        if len(f1) != 3 or len(f2) != 3:
            continue
        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 or G.has_edge(w, x):
            continue
        Gp = G.copy(); Gp.remove_edge(u, v); Gp.add_edge(w, x)
        yield Gp


def backward_neighbors(G, labels):
    """States G' with G' ->forward G. G' = G - f + diag_G(f) for each edge
    f of G whose diagonal is same-parity (so flipping it in G' is valid)."""
    ok, emb = nx.check_planarity(G)
    if not ok:
        return
    for u, v in list(G.edges()):
        f1 = emb.traverse_face(u, v)
        f2 = emb.traverse_face(v, u)
        if len(f1) != 3 or len(f2) != 3:
            continue
        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 or G.has_edge(w, x):
            continue
        # diagonal of edge uv is wx; predecessor un-flips: remove uv? No.
        # Predecessor G' has edge wx (same-parity), flipping wx gives uv...
        # We want G = G' - (wx) + (uv). So G' = G - uv + wx, and the flipped
        # edge in G' is wx which must be same-parity.
        if labels[w] % 2 != labels[x] % 2:
            continue
        Gp = G.copy(); Gp.remove_edge(u, v); Gp.add_edge(w, x)
        yield Gp


def orbit(G_start, labels, neighbor_fn, max_states=300000):
    seen = {sig(G_start): G_start}
    frontier = [G_start]
    while frontier and len(seen) < max_states:
        new = []
        for H in frontier:
            for Hp in neighbor_fn(H, labels):
                s = sig(Hp)
                if s not in seen:
                    seen[s] = Hp
                    new.append(Hp)
        frontier = new
    return list(seen.values())


def has_elg(orbit_states, labels):
    """Find an Even Level Graph in the orbit whose BFS-parity (from some
    source) MATCHES the orbit labelling (up to global parity swap).
    Only such a graph is a valid 'derived from this ELG' witness."""
    for H in orbit_states:
        for s in H.nodes():
            ok, lvls = is_even_level_graph(H, frozenset({s}))
            if not ok:
                continue
            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 True, H, s
    return False, None, None


def main():
    tris = enumerate_all_triangulations(9)
    Tstar = next(G for G in tris
                 if sorted([G.degree(v) for v in G.nodes()], reverse=True)
                 == [5, 5, 5, 5, 5, 5, 4, 4, 4])

    src = 0
    labels = bfs_levels(Tstar, frozenset({src}))
    print(f'T*_9, labelling = BFS parity from source {src}')

    fwd = orbit(Tstar, labels, forward_neighbors)
    f_elg, _, _ = has_elg(fwd, labels)
    print(f'  FORWARD orbit (T*_9 ->* H): size {len(fwd)}, contains ELG: {f_elg}')

    bwd = orbit(Tstar, labels, backward_neighbors)
    b_elg, He, se = has_elg(bwd, labels)
    print(f'  BACKWARD orbit (H ->* T*_9): size {len(bwd)}, contains ELG: {b_elg}')
    if b_elg:
        print(f'    -> ELG predecessor exists (src {se}); T*_9 WOULD be derived')
    else:
        print(f'    -> no ELG reaches T*_9; T*_9 is NOT derived (directed)')

    print(f'\nConclusion: directedness matters = {f_elg != b_elg}')


if __name__ == '__main__':
    main()
