"""
Switchboard: an upright panel like an old telephone exchange, for an
installation that visitors play. Eight columns of eight sockets, one column
for each loop, and along the bottom a socket for each element with its cord.
Plugging an element's cord into a column sets that element's next loop. The
bridge box sits behind, on a foot, where only whoever looks after the
installation reaches its stop button.

Run from the cad/ folder:

    uv run switchboard/model.py

It writes, for the site:
    site/public/files/switchboard/stl/*.stl           the four panel tiles, laid flat
    site/public/files/switchboard/switchboard-stl.zip
    site/public/files/switchboard/switchboard.step    the whole board, for editing
    site/public/files/switchboard/switchboard.glb     the 3D model shown on the page
    site/public/files/switchboard/switchboard-cad.py  this script, and console.py beside it
    site/src/content/instruments/switchboard/drawings/switchboard-render.svg

Coordinates, in millimeters: X across the board from its left edge, Y from
the panel's face toward the back (the visitor stands at negative Y), Z up
from the floor.

The bridge box is the Bridge's own recipe, drawn here where it sits. All
numbers are a first design, untested in print.
"""

from pathlib import Path
import importlib.util
import sys

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parent))
import console as c  # noqa: E402

spec = importlib.util.spec_from_file_location("bridge_box", HERE.parent / "bridge-box" / "model.py")
bridge = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bridge)

# ---------------------------------------------------------------------------
# The design, in one place
# ---------------------------------------------------------------------------

W, H = 400.0, 420.0  # the frame, outside
T = 4.0  # panel tiles; a 1/4 inch jack's nut clamps up to about 4 mm
JACK_HOLE = 9.6  # a 1/4 inch panel jack's 3/8 inch thread
LOOP_X = [50.0, 90.0, 130.0, 170.0, 230.0, 270.0, 310.0, 350.0]  # a column per loop, clear of the middle upright
LOOP_Z = [380.0 - 36.0 * j for j in range(8)]  # eight sockets in each column, all joined
ELEMENT_Z = 70.0  # a socket for each element, under its own column
FOOT = (-110.0, 170.0)  # the feet run front to back under the side uprights
BRIDGE_AT = (390.0, 100.0)  # the bridge box sits on the right foot, behind the panel

# Three cords plugged in, the rest hanging: element column -> (loop column, row)
PLUGGED = {0: (1, 2), 3: (4, 4), 6: (5, 1)}


def tiles():
    holes = [(x, z) for x in LOOP_X for z in LOOP_Z] + [(x, ELEMENT_Z) for x in LOOP_X]
    screws = [(10, 60), (10, 180), (10, 260), (10, 380), (390, 60), (390, 180), (390, 260), (390, 380),
              (100, 30), (300, 30), (100, 410), (300, 410)]
    out = []
    for i, (x0, x1) in enumerate([(0, 200), (200, 400)]):
        for j, (z0, z1) in enumerate([(0, 210), (210, H)]):
            t = c.box(x0, x1, -T, 0, z0, z1)
            for x, z in holes:
                if x0 < x < x1 and z0 < z < z1:
                    t -= c.cyl_y(x, z, JACK_HOLE / 2, -T - 1, 1)
            for x, z in screws:
                if x0 < x < x1 and z0 < z < z1:
                    t -= c.cyl_y(x, z, c.M5, -T - 1, 1)
            out.append((f"panel-{'lower' if j == 0 else 'upper'}-{'left' if i == 0 else 'right'}", t, c.Rot(90, 0, 0)))
    return out


def frame():
    v = c.vslot
    return [
        ("foot-left", v(0, 20, *FOOT, 0, 20)),
        ("foot-right", v(380, 400, *FOOT, 0, 20)),
        ("upright-left", v(0, 20, 0, 20, 20, H)),
        ("upright-right", v(380, 400, 0, 20, 20, H)),
        ("bar-bottom", v(20, 380, 0, 20, 20, 40)),
        ("bar-top", v(20, 380, 0, 20, H - 20, H)),
        ("upright-middle", v(190, 210, 0, 20, 40, H - 20)),
    ]


def jack_at(x, z):
    return c.Pos(x, -T, z) * c.Rot(90, 0, 0) * c.Pos(0, 0, 0) * c.jack()


def plug(x, z):
    """A patch cord's plug in a socket, its boot pointing out toward the visitor."""
    return c.cyl_y(x, z, 3.2, -T - 30, -T) + c.cyl_y(x, z, 6, -T - 60, -T - 8)


def cords():
    shapes = []
    for e, x in enumerate(LOOP_X):
        shapes.append(plug(x, ELEMENT_Z))
        start = (x, -T - 60, ELEMENT_Z)
        if e in PLUGGED:
            col, row = PLUGGED[e]
            gx, gz = LOOP_X[col], LOOP_Z[row]
            shapes.append(plug(gx, gz))
            low = min(ELEMENT_Z, gz) - 45
            pts = [start, (x, -T - 85, ELEMENT_Z - 20), ((x + gx) / 2, -T - 110, max(low, 25)), (gx, -T - 85, gz - 20), (gx, -T - 60, gz)]
        else:
            pts = [start, (x, -T - 80, ELEMENT_Z - 25), (x + 6, -T - 70, 12), (x + 12, -T - 50, 6)]  # its free end on the floor
        shapes.append(c.cord(pts, 3.0))
    return shapes


def bridge_parts():
    """The bridge box on the right foot, lid up, floor screwed to the foot's top slot."""
    cx, cy = BRIDGE_AT
    place = c.Pos(cx + bridge.CY, cy - bridge.L / 2, 20) * c.Rot(0, 0, 90)
    parts = [("bridge-box", place * (bridge.tub() + bridge.lid()), (0.86, 0.42, 0.28))]
    for name, shape, rgb in bridge.bought_parts():
        if name != "usb-cable":
            parts.append((f"bridge-{name}", place * shape, rgb))
    return parts


def bought_parts():
    parts = [(name, shape, (0.78, 0.8, 0.83)) for name, shape in frame()]
    jacks = jack_at(LOOP_X[0], ELEMENT_Z)
    for x in LOOP_X:
        for z in LOOP_Z + [ELEMENT_Z]:
            if (x, z) != (LOOP_X[0], ELEMENT_Z):
                jacks += jack_at(x, z)
    parts.append(("jacks", jacks, (0.75, 0.68, 0.45)))
    parts.append(("cords", c.Compound(children=cords()), (0.12, 0.12, 0.12)))
    parts += bridge_parts()
    return parts


def main():
    c.export(
        "switchboard",
        __file__,
        tiles(),
        bought_parts(),
        [(None, (-520, -900, 760), (W / 2, 0, H / 2), "THE BOARD FROM THE FRONT: A COLUMN OF SOCKETS FOR EACH LOOP, A CORD FOR EACH ELEMENT")],
        "Switchboard: the board",
    )


if __name__ == "__main__":
    main()
