"""Demonstrate that the cut tires of G'_i form a tree (or forest)
under the parent-child relation "face f of H_{d+1} is contained in
face g of H_d in the planar embedding."

For each cut tire T_{d+1}^{(f)}, identify its parent T_d^{(g)} (if
any).  Each face f of H_{d+1} is geometrically inside exactly one
face g of H_d (since H_d's faces partition the plane around
H_d's edges).  This gives a unique parent → child relation, hence
a forest.

Empirical test on G'_1 of Holton-McKay #0:
  - List all (d, face) pairs.
  - For each (d+1, face_child), find (d, face_parent) such that
    face_child lies inside face_parent in the planar embedding.
  - Verify uniqueness (counterexample: a face inside multiple).
  - Print the tree.
"""
import os
import sys
from collections import defaultdict

import matplotlib.pyplot as plt
import matplotlib.patches as patches
from sage.all import Graph

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from cut_depth_label import (
    parse_planar_code, HM_FILE, find_six_edge_cut,
    apply_procedure, compute_nice_layout,
)
from cut_tire import cut_tire_at


def compute_H_d_faces(G_i, edge_depth, d):
    """Return faces of H_d (subgraph of G_i with depth-d edges),
    each face represented as a sorted tuple of edges."""
    H_edges = [e for e, ed in edge_depth.items() if ed == d]
    if not H_edges:
        return [], None
    H = Graph([(u, v) for (u, v) in H_edges],
              multiedges=False, loops=False)
    if not H.is_planar(set_embedding=True):
        return [], None
    return H.faces(), H


def edge_in_face(edge_uv, face_walk, H_graph):
    """Check if the edge edge_uv = (u, v) is on the boundary walk of
    face_walk (a list of (u, v) pairs)."""
    e_norm = (min(edge_uv), max(edge_uv))
    for (a, b) in face_walk:
        if (min(a, b), max(a, b)) == e_norm:
            return True
    return False


def find_parent_face(child_face_walk, H_parent, parent_faces, parent_H_graph):
    """For a face of H_{d+1} (child), determine which face of H_d
    (parent) it lies inside.

    Approach: pick a vertex v of child_face_walk that's also a vertex
    of H_parent (= parent_H_graph). Find which face of H_parent has
    v on its boundary AND that the child's edges are "inside" the
    parent's face.

    For now, use a simpler heuristic: pick a vertex of the child
    face; among parent faces having this vertex on their boundary,
    pick the one whose face boundary walk has "small" length (= the
    immediate enclosing face).
    """
    child_vertices = set(u for (u, _) in child_face_walk) | \
                     set(v for (_, v) in child_face_walk)
    # For each parent face, check if it contains some child vertex
    candidates = []
    for p_idx, p_face in enumerate(parent_faces):
        p_vertices = set(u for (u, _) in p_face) | \
                     set(v for (_, v) in p_face)
        overlap = child_vertices & p_vertices
        if overlap:
            candidates.append((p_idx, len(p_face), overlap))
    if not candidates:
        return None, set()
    # Among candidates, pick the one with the SMALLEST face length
    # (= the most "tight" enclosing face)
    candidates.sort(key=lambda c: c[1])
    return candidates[0][0], candidates[0][2]


def build_tree(G_i, edge_depth):
    """Build the tree of cut tires on G_i, indexed by (d, face_idx)."""
    max_d = max(edge_depth.values())
    faces_by_depth = {}  # d -> list of faces (each a list of edges)
    H_graphs = {}  # d -> H_d graph
    for d in range(1, max_d + 1):
        faces, H_d = compute_H_d_faces(G_i, edge_depth, d)
        faces_by_depth[d] = faces
        H_graphs[d] = H_d
    # For each (d+1, face_child), find parent (d, face_parent)
    parent_of = {}  # (d, face_idx) -> (d_parent, p_idx) or None
    for d in range(2, max_d + 1):
        if not faces_by_depth.get(d):
            continue
        if not faces_by_depth.get(d - 1):
            for f_idx in range(len(faces_by_depth[d])):
                parent_of[(d, f_idx)] = None
            continue
        for f_idx, child_face in enumerate(faces_by_depth[d]):
            p_idx, overlap = find_parent_face(
                child_face, H_graphs[d - 1],
                faces_by_depth[d - 1], H_graphs[d - 1])
            parent_of[(d, f_idx)] = (d - 1, p_idx) if p_idx is not None else None
    # Depth-1 cut tires are "root" tires (parent = the cut)
    if 1 in faces_by_depth:
        for f_idx in range(len(faces_by_depth[1])):
            parent_of[(1, f_idx)] = ('cut', None)
    return faces_by_depth, parent_of, H_graphs


def print_tree(faces_by_depth, parent_of):
    """Print tree of cut tires with stats."""
    # Build child list
    children = defaultdict(list)
    for node, parent in parent_of.items():
        if parent is not None:
            children[parent].append(node)
    print('\nCut tire tree:')
    # Print depth-1 cut tires (roots)
    for f_idx in range(len(faces_by_depth.get(1, []))):
        node = (1, f_idx)
        depth, fi = node
        face_len = len(faces_by_depth[depth][fi])
        print(f'  Root: {node} (|f|={face_len})')
        _print_subtree(children, node, faces_by_depth, indent=4)


def _print_subtree(children, node, faces_by_depth, indent):
    for child in sorted(children.get(node, [])):
        d, fi = child
        face_len = len(faces_by_depth[d][fi])
        print(f'{" " * indent}└─ {child} (|f|={face_len})')
        _print_subtree(children, child, faces_by_depth, indent + 4)


def check_unique_parent(faces_by_depth, edge_depth, H_graphs, G_i):
    """Stronger check: for each face of H_{d+1}, count how many faces
    of H_d contain it in the planar embedding.  The user's claim is
    that this count is exactly 1."""
    max_d = max(edge_depth.values())
    violations = []
    for d in range(2, max_d + 1):
        if not faces_by_depth.get(d) or not faces_by_depth.get(d - 1):
            continue
        for f_idx, child_face in enumerate(faces_by_depth[d]):
            child_vertices = set(u for (u, _) in child_face) | \
                             set(v for (_, v) in child_face)
            matching_parents = []
            for p_idx, p_face in enumerate(faces_by_depth[d - 1]):
                p_vertices = set(u for (u, _) in p_face) | \
                             set(v for (_, v) in p_face)
                if child_vertices & p_vertices:
                    matching_parents.append(p_idx)
            if len(matching_parents) > 1:
                violations.append({
                    'child': (d, f_idx),
                    'candidate_parents': matching_parents,
                    'overlap_sizes': [
                        len(child_vertices & set(u for (u, _) in
                                                  faces_by_depth[d-1][p_idx])
                            | set(v for (_, v) in
                                  faces_by_depth[d-1][p_idx]))
                        for p_idx in matching_parents
                    ],
                })
    return violations


def main():
    gs = parse_planar_code(HM_FILE)
    G = gs[0]
    S, cut = find_six_edge_cut(G)
    base_pos = compute_nice_layout(G)
    S1 = frozenset(G.vertices()) - frozenset(S)
    H1, pos1, ed1, _, _, _ = apply_procedure(
        G, S1, cut, base_pos, '1',
        pendant_start_id=max(G.vertices()) + 1 + 6)

    print(f"G'_1 of Holton-McKay #0, depths 0 to {max(ed1.values())}")

    faces_by_depth, parent_of, H_graphs = build_tree(H1, ed1)
    print_tree(faces_by_depth, parent_of)

    # Check for ambiguous parents
    print('\nChecking for ambiguous parents...')
    violations = check_unique_parent(faces_by_depth, ed1, H_graphs, H1)
    if violations:
        print(f'  {len(violations)} children have multiple candidate parents')
        for v in violations[:5]:
            print(f'    {v}')
    else:
        print('  No ambiguity: every child has exactly one parent candidate '
              '(based on vertex sharing).')

    # Now do G'_0 as well
    S0 = frozenset(S)
    H0, pos0, ed0, _, _, _ = apply_procedure(
        G, S0, cut, base_pos, '0',
        pendant_start_id=max(G.vertices()) + 1)
    print(f"\n\nG'_0 of Holton-McKay #0, depths 0 to {max(ed0.values())}")
    faces_by_depth_0, parent_of_0, _ = build_tree(H0, ed0)
    print_tree(faces_by_depth_0, parent_of_0)


if __name__ == '__main__':
    main()
