"""Parse McKay's planar_code file of the 6 non-Hamiltonian 38-vertex
cubic planar graphs, verify them, and build their duals (21-vertex
triangulations)."""
import networkx as nx


def parse_planar_code(path):
    """Parse a >>planar_code<< file. Returns list of (G, embedding-order)."""
    with open(path, 'rb') as f:
        data = f.read()
    header = b'>>planar_code<<'
    assert data.startswith(header), data[:20]
    pos = len(header)
    graphs = []
    while pos < len(data):
        n = data[pos]
        pos += 1
        adj = {i: [] for i in range(n)}
        for v in range(n):
            while True:
                w = data[pos]
                pos += 1
                if w == 0:
                    break
                adj[v].append(w - 1)  # 1-indexed -> 0-indexed
        G = nx.Graph()
        G.add_nodes_from(range(n))
        for v in range(n):
            for w in adj[v]:
                G.add_edge(v, w)
        graphs.append((G, adj))
    return graphs


def is_hamiltonian(G, time_limit_nodes=10_000_000):
    """Backtracking Hamiltonicity check (good for cubic graphs)."""
    nodes = list(G.nodes())
    n = len(nodes)
    adj = {v: list(G.neighbors(v)) for v in nodes}
    start = nodes[0]
    visited = {start}
    count = [0]

    def bt(v, depth):
        count[0] += 1
        if count[0] > time_limit_nodes:
            raise TimeoutError
        if depth == n:
            return start in adj[v]
        for w in adj[v]:
            if w not in visited:
                visited.add(w)
                if bt(w, depth + 1):
                    return True
                visited.discard(w)
        return False

    return bt(start, 1)


if __name__ == '__main__':
    graphs = parse_planar_code('/tmp/nonham38m4.pc')
    print(f'Parsed {len(graphs)} graphs from McKay planar_code file')
    for i, (G, adj) in enumerate(graphs):
        n = G.number_of_nodes()
        m = G.number_of_edges()
        degs = sorted(set(dict(G.degree()).values()))
        planar = nx.check_planarity(G)[0]
        try:
            ham = is_hamiltonian(G)
        except TimeoutError:
            ham = 'timeout'
        print(f'  graph {i}: n={n}, m={m}, degrees={degs}, '
              f'planar={planar}, hamiltonian={ham}')
