"""Build a maximal-outerplanar L_k whose unique depth-2 face has NO
balanced surface switch, then test whether preprocessing reaches one.

Dual-tree blueprint:

         F (depth 2, degree 3)
        /|\\
     F1' F2' F3'   each depth 1, degree 3
     /\\  /\\  /\\
   E_i G_i      (per arm: E_i depth-0 ear, G_i depth-1 degree-3 node)
        /\\
      E'_i H_i   (G_i's two non-F'_i children: both depth-0 ears)

Inner-face count: 1 + 3 + 3 + 3 + 3 + 3 = 16. So polygon has n = 18.

For each F'_i: non-F neighbours are E_i (depth 0) and G_i (depth 1).
NOT balanced (G_i not depth 0). Hence F has no balanced surface switch.

Concrete chord construction: place vertices 0..17 around the outer cycle.
Allocate one "arm" of 6 outer-cycle vertices per F'_i, plus three vertices
for F.

Arm i (i = 0,1,2) covers outer-cycle positions [6i, 6i+5]; the F vertices
are positions {0, 6, 12}.
"""
import os
import math
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon

OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)

n = 18
POS = {i: (math.cos(math.radians(90 - i * 360 / n)),
           math.sin(math.radians(90 - i * 360 / n))) for i in range(n)}


def outer_edges(num):
    return [(i, (i + 1) % num) for i in range(num)]


def face_edges(f):
    return {frozenset((f[0], f[1])), frozenset((f[1], f[2])),
            frozenset((f[0], f[2]))}


def compute_depths(faces, outer_edge_set):
    D = nx.Graph()
    D.add_nodes_from(range(len(faces)))
    for i, fi in enumerate(faces):
        for j, fj in enumerate(faces):
            if i < j and face_edges(fi) & face_edges(fj):
                D.add_edge(i, j)
    B = [i for i, f in enumerate(faces)
         if len(face_edges(f) & outer_edge_set) >= 1]
    if not B:
        return {i: float('inf') for i in range(len(faces))}, D
    depth = {i: min(nx.shortest_path_length(D, i, b) for b in B)
             for i in range(len(faces))}
    return depth, D


# Apex vertices of F at outer positions 0, 6, 12.
# Per arm i: outer positions p = [a, a+1, a+2, a+3, a+4, a+5] where a = 6i.
# Reading positions: a = u_i, a+1 = e1, a+2 = m, a+3 = e2, a+4 = h, a+5 = v_i = u_{i+1}
#
# Inside the polygon arc (u_i ... u_{i+1}) we want to triangulate so that
# the chord u_i--u_{i+1} corresponds to F'_i in the dual tree, with:
#   - F'_i adjacent to the apex chord
#   - non-apex side of F'_i forks into an ear E_i (depth 0) and the
#     degree-3 node G_i (depth 1)
#   - G_i further forks into two ears
#
# A clean way to realise this: pick F'_i = (u_i, m, u_{i+1}), then
#   E_i = (u_i, e1, m)    -- needs chord u_i--m
#   G_i = (m, e2, ...)    -- hmm, we need G_i to be degree-3 sharing one
#                            edge with F'_i (chord m--something).
#
# Simpler: pick F'_i = (u_i, e2, u_{i+1}). Then F'_i has chords
#   u_i--e2  and  e2--u_{i+1}.
# Side u_i..e2 (covers e1, m): triangulate via chord u_i--m.
#   E_i = (u_i, e1, m) ear (outer edges u_i--e1, e1--m), depth 0.
#   X_i = (u_i, m, e2): edges u_i--m, m--e2 (chord? outer? m--e2 outer
#         since m, e2 are adjacent on outer cycle), u_i--e2 chord.
#         1 outer edge: m--e2. Depth 0.
# Hmm X_i is depth 0, not depth 1 as required for G_i.
# This doesn't yet build the 4-deep structure I want. Let me redo.
#
# Required structure per arm to get a depth-1 face G_i:
#   need G_i with no outer edges. So all three of G_i's edges are chords.
#   each chord shared with another inner face (so G_i has 3 dual-neighbors).
#   For G_i depth 1: its three neighbours must include at least one depth-0
#   face. With G_i having 3 chord edges, it has 3 dual-neighbors.
#
# To realise G_i with 3 chord edges, we need >=4 outer-cycle vertices
# inside G_i's region.

# Let me redesign with more vertices per arm: 7 per arm instead of 6.
# Then n = 3*7 + 3 = 24? Or with apex shared, n = 3*7 = 21.

# Use n = 21. Apex at positions 0, 7, 14. Per arm i (i=0,1,2):
#   positions a = 7i, a+1, a+2, a+3, a+4, a+5, a+6 where a+7 = next apex.
# So outer-cycle vertices in arm i: [u_i = 7i, 7i+1, ..., 7i+6, u_{i+1} = 7(i+1)].
# That's 7 strict-interior vertices plus the two endpoints.
# Wait, 7 outer-cycle positions from u_i to u_{i+1} (inclusive of both).
#
# Let me use simpler indexing. Apex U_0=0, U_1=7, U_2=14 (n=21).
# Arm 0 covers outer positions 0..7 (inclusive), with internal vertices 1..6.

print('Recomputing with n=21 layout...')
n = 21
POS = {i: (math.cos(math.radians(90 - i * 360 / n)),
           math.sin(math.radians(90 - i * 360 / n))) for i in range(n)}

OUTER_EDGES = outer_edges(n)
outer_set = {frozenset(e) for e in OUTER_EDGES}

# Apex vertices of F:
U0, U1, U2 = 0, 7, 14
# Edges of F (chords)
F_chords = [(U0, U1), (U1, U2), (U0, U2)]


def arm_chords(a, b):
    """For an arm from apex a (=7i) to apex b (=7(i+1) mod 21), with
    internal outer vertices a+1, a+2, ..., a+6 (6 internal verts), produce:
      F'_i = (a, mid, b) for some mid
      then sub-triangulate the (a, ..., mid) side and (mid, ..., b) side.
    Pick mid = a+4 (middle, gives 3 vertices on each side).
    a-side (a, a+1, a+2, a+3, mid=a+4): 4 strict-interior, need triangulating
      Add chord a--(a+2) and chord a--(a+4) [already F'_i].
      Triangles: (a, a+1, a+2) ear; (a, a+2, a+3); (a, a+3, a+4).
      But (a, a+2, a+3) has outer edge (a+2,a+3); depth 0.
      (a, a+3, a+4) has outer edge (a+3, a+4) wait that's not necessarily outer.
      Actually a+3, a+4 ARE outer-adjacent. So (a, a+3, a+4) has outer
      edge (a+3, a+4); 1 outer edge, depth 0.
    Hmm need to engineer the G_i = depth-1 face.

    Try mid = a+3. Then a-side has 2 internal vertices (a+1, a+2);
    b-side has 3 (a+4, a+5, a+6).
      a-side triangulation: chord a--(a+2). Triangles:
        (a, a+1, a+2) ear; (a, a+2, a+3) = E_i?
        (a, a+2, a+3): outer edge (a+2, a+3); 1 outer; depth 0.
      So E_i = (a, a+2, a+3) depth 0 with chord a-(a+2) leading to
      ear (a, a+1, a+2).

      b-side (a+3, a+4, a+5, a+6, b): 4 internal (a+4, a+5, a+6) wait
      that's 3. We need a depth-1 G_i in here. Triangulate with chord
      (a+3)--(a+5): triangles (a+3, a+4, a+5) ear; (a+3, a+5, b) and
      (a+5, a+6, b).
        (a+3, a+5, b): edges (a+3,a+5) chord, (a+5, b) chord, (a+3, b)
        which is F'_i edge. 0 outer edges. Depth >= 1.
        (a+5, a+6, b): edges (a+5, a+6) outer, (a+6, b) outer, (a+5, b)
        chord. 2 outer edges; ear; depth 0.

      So (a+3, a+5, b) has 0 outer edges, depth ?. Its neighbours:
        across (a+3, a+5): ear (a+3, a+4, a+5) depth 0
        across (a+5, b): ear (a+5, a+6, b) depth 0
        across (a+3, b): F'_i
      So depth = 1 (via two depth-0 neighbours).

      F'_i = (a, a+3, b). Edges (a, a+3) chord, (a+3, b) chord, (a, b)
      apex-chord (shared with F). 0 outer. Neighbours:
        across (a, a+3): E_i = (a, a+2, a+3) depth 0
        across (a+3, b): (a+3, a+5, b) depth 1 = G_i
        across (a, b): F depth ?
      depth(F'_i) = 1 + min(0, 1) = 1. ✓

      Non-(a,b) neighbours of F'_i: E_i depth 0 ✓ and G_i depth 1 ✗.
      LOPSIDED, hence unbalanced. ✓

    chords for arm a..b (= a + 7):
       (a, a+2), (a, a+3), (a+3, a+5), (a+3, b), (a+5, b)
    """
    return [(a, a + 2), (a, a + 3),
            (a + 3, a + 5), (a + 3, b), (a + 5, b)]


def arm_faces(a, b):
    return [
        (a, a + 1, a + 2),         # ear of arm
        (a, a + 2, a + 3),         # E_i: 1 outer edge -> depth 0
        (a + 3, a + 4, a + 5),     # ear
        (a + 3, a + 5, b),         # G_i: 0 outer edges
        (a + 5, a + 6, b),         # ear
        (a, a + 3, b),             # F'_i
    ]


CHORDS = list(F_chords)
FACES = [(U0, U1, U2)]   # F itself

for (a, b) in [(0, 7), (7, 14), (14, 0)]:
    CHORDS.extend(arm_chords(a, b))
    FACES.extend(arm_faces(a, b))

depth, D = compute_depths(FACES, outer_set)
print(f'Total faces: {len(FACES)}')
for i, f in enumerate(FACES):
    print(f'  {f} -> depth {depth[i]}')
print(f'B (depth-0 faces): {[FACES[i] for i in range(len(FACES)) if depth[i] == 0]}')

# Identify the depth-2 face
d2_faces = [i for i in range(len(FACES)) if depth[i] == 2]
print(f'Depth-2 faces: {[FACES[i] for i in d2_faces]}')


def check_balanced(F_idx, faces, depth_, outer_edge_set):
    """Check if face F_idx admits a balanced surface switch on some edge."""
    F = faces[F_idx]
    fe = face_edges(F)
    for e in fe:
        if e in outer_edge_set:
            continue
        # Find the inner face sharing e with F
        candidates = [j for j in range(len(faces))
                      if j != F_idx and e in face_edges(faces[j])]
        if not candidates:
            continue
        Fp_idx = candidates[0]
        if depth_[Fp_idx] != depth_[F_idx] - 1:
            continue
        # Found a depth-(d-1) neighbour F'. Check balancedness.
        Fp = faces[Fp_idx]
        fpe = face_edges(Fp)
        other_edges = [e2 for e2 in fpe if e2 != e]
        d = depth_[F_idx]
        ok = True
        for e2 in other_edges:
            if e2 in outer_edge_set:
                continue  # outer-cycle edge is fine
            # find inner face across e2
            others = [j for j in range(len(faces))
                      if j != Fp_idx and e2 in face_edges(faces[j])]
            if not others:
                ok = False
                break
            other_face = others[0]
            if depth_[other_face] != d - 2:
                ok = False
                break
        if ok:
            return True, F_idx, Fp_idx, e
    return False, None, None, None


for F_idx in d2_faces:
    ok, _, fp, e = check_balanced(F_idx, FACES, depth, outer_set)
    print(f'F = {FACES[F_idx]}: balanced switch exists? {ok}')
