import re, uuid, sys, json, math
FAB = "/Users/fhaddad/Documents/KiCad/10.0/3rdparty/fab/footprints/fab.pretty/"
KF  = "/Applications/KiCad/KiCad.app/Contents/SharedSupport/footprints/"
D = sys.argv[1]; NET = sys.argv[2]; PROJECT = "chronotype_typewriter"
ROOT = "7a1c2e5e-0c44-4d4e-9f1a-000000000001"
U = lambda: str(uuid.uuid4())
sch = open(D + f"/{PROJECT}.kicad_sch").read()
sym_uuid = {}
for m in re.finditer(r'\(symbol\s*\(lib_id "[^"]+"\)\s*\(at [^)]*\)\s*\(unit 1\)(?:\s*\([a-z_]+ \w+\))*\s*\(uuid "([^"]+)"\)\s*\(property "Reference" "([^"]+)"', sch):
    sym_uuid[m.group(2)] = m.group(1)
x = open(NET).read()
nets = {}; padnet = {}
for m in re.finditer(r'<net code="(\d+)" name="([^"]+)"[^>]*>(.*?)</net>', x, re.S):
    name = m.group(2).lstrip("/"); code = int(m.group(1)); nets[code] = name
    for r, p in re.findall(r'<node ref="([^"]+)" pin="([^"]+)"', m.group(3)): padnet[(r, p)] = (code, name)
name2code = {n: c for c, n in nets.items()}

# ---------------- geometry plan (mm) ----------------
# board 20..116 x 20..76 ; XIAO rot 90 at (31.5, 37): USB out the left edge
# columns (bus verticals) x: SCK 43, SI 46, READY 49, SO 52, KBACK 55, PWR 58, KBRQ 61 ; platen lanes y 27.1 + 0.82k
# bottom-row runs y: 48.0 + 0.82k ; divider 10k resistors hang above their run at column+1.5
COL = {"SCK":44.5,"SI":48.5,"READY":52.5,"SO":56.5,"KBACK":60.5,"PWR":64.5,"KBRQ":68.5}
LANE = {"SCK":27.1,"SI":27.92,"READY":28.74,"SO":29.56,"KBACK":30.38,"PWR":31.2,"KBRQ":32.02}
PADX = {"SCK":52.0,"SI":54.5,"READY":57.0,"SO":59.5,"KBACK":62.0,"PWR":64.5,"KBRQ":67.0}   # J1 pins 2..8
RUN  = {"SCK":54.0,"SI":54.82,"READY":55.64,"SO":56.46,"KBACK":57.28,"PWR":58.1,"KBRQ":58.92}
XPAD = {"SCK":39.12,"SI":36.58,"READY":34.04,"SO":31.5,"KBACK":28.96,"PWR":26.42,"KBRQ":23.88}  # XIAO bottom row D6..D0
PLACE = [
 ("M1","fab:SeeedStudio_XIAO_RP2040","XIAO RP2040", 31.5, 43.0, 90),
 ("J1","kicad:Connector_JST.pretty/JST_XH_B12B-XH-A_1x12_P2.50mm_Vertical","Typewriter 12-pin", 49.5, 25.5, 0),
 ("J2","fab:PinSocket_01x04_P2.54mm_Vertical_SMD","OLED", 32.77, 23.5, 90),
 ("R1","fab:R_1206","100", COL["SCK"], 45.0, 90), ("R2","fab:R_1206","100", COL["SI"], 45.0, 90), ("R3","fab:R_1206","100", COL["READY"], 45.0, 90),
 ("R4","fab:R_1206","4.99k", COL["SO"], 45.0, 270), ("R6","fab:R_1206","4.99k", COL["KBACK"], 45.0, 270), ("R12","fab:R_1206","4.99k", COL["PWR"], 45.0, 270),
 ("R11","fab:R_1206","0", COL["KBRQ"], 41.5, 270), ("R8","fab:R_1206","4.99k", COL["KBRQ"], 47.5, 270),
 ("R5","fab:R_1206","10k", COL["SO"]+1.5, RUN["SO"]-2.0, 90), ("R7","fab:R_1206","10k", COL["KBACK"]+1.5, RUN["KBACK"]-2.0, 90),
 ("R13","fab:R_1206","10k", COL["PWR"]+1.5, RUN["PWR"]-2.0, 90), ("R9","fab:R_1206","10k", COL["KBRQ"]+1.5, RUN["KBRQ"]-2.0, 90),
 ("R10","fab:R_1206","100", 91.5, 64.0, 180), ("D1","fab:LED_1206","LED blue", 85.5, 64.0, 0),
 ("SW1","fab:Button_CnK_PTS636.0_6x3.5mm","SELECT", 77.0, 65.5, 0),
 ("H1","kicad:MountingHole.pretty/MountingHole_3.2mm_M3","M3", 23.5, 23.5, 0), ("H2","kicad:MountingHole.pretty/MountingHole_3.2mm_M3","M3", 101.5, 23.5, 0),
 ("H3","kicad:MountingHole.pretty/MountingHole_3.2mm_M3","M3", 23.5, 72.5, 0), ("H4","kicad:MountingHole.pretty/MountingHole_3.2mm_M3","M3", 101.5, 72.5, 0),
]
W = 0.4064
ROUTES = []
def seg(net, pts): ROUTES.append((net, pts))
for s in COL:
    px, ly, cx, ry, xp = PADX[s], LANE[s], COL[s], RUN[s], XPAD[s]
    seg(s, [(px, 25.5), (px, ly), (cx, ly)])                         # platen pad -> lane -> column top
    if s in ("SCK","SI","READY"):
        seg(s, [(cx, ly), (cx, 43.5)])                                 # column down to 100 R top pad (pad2, bus)
        seg(s+"_MCU", [(cx, 46.5), (cx, ry), (xp, ry), (xp, 52.5)])    # 100 R bottom pad -> run -> XIAO pad
    elif s == "KBRQ":
        seg("KBRQ", [(cx, ly), (cx, 40.0)])                            # -> R11 top (pad1)
        seg("KBRQ_J", [(cx, 43.0), (cx, 46.0)])                        # R11 bottom -> R8 top
        seg("KBRQ_MCU", [(cx+2.0, ry), (cx, ry)]); seg("KBRQ_MCU", [(cx, 49.0), (cx, ry), (xp, ry), (xp, 52.5)])  # R8 bottom (node) -> run -> XIAO
    else:
        seg(s, [(cx, ly), (cx, 43.5)])                                 # -> 4.99k top (pad1, bus)
        seg(s+"_MCU", [(cx+2.0, ry), (cx, ry)]); seg(s+"_MCU", [(cx, 46.5), (cx, ry), (xp, ry), (xp, 52.5)])    # node -> run (+stub under the 10k pad) -> XIAO
# LED: D7 (top row, x 39.12) -> right -> up -> across the top -> down the right side -> R10
seg("LED_MCU", [(39.6, 34.4), (42.0, 34.4), (42.0, 22.74), (96.5, 22.74), (96.5, 64.0), (93.0, 64.0)])
seg("LED_A", [(90.0, 64.0), (87.2, 64.0)])
# SELECT: D8 (36.58) -> lane above the top row -> up at 41 -> across -> down the right side -> under the button -> pad1
seg("SEL_MCU", [(36.58, 33.1), (36.58, 32.2), (41.0, 32.2), (41.0, 21.92), (97.5, 21.92), (97.5, 67.4), (73.0, 67.4), (73.0, 65.5)])
# breakout: D9 (34.04), D10 (31.5), 3V3 (28.96) straight up into J2 pads (x 34.06, 31.52, 28.98 at y 23.5)
seg("3V3", [(28.96, 33.1), (28.96, 28.0), (31.5, 28.0), (31.5, 25.15)]); seg("BK_D10", [(31.5, 33.1), (31.5, 29.5), (34.04, 29.5), (34.04, 21.85)]); seg("BK_D9", [(34.04, 33.1), (34.04, 31.0), (36.58, 31.0), (36.58, 25.15)])
# GND stitches: XIAO pin13 (26.42 top) -> pad19 (25.54, 38.73 under module) ; pad19 -> pad15 (40.39, 38.7)
seg("GND", [(26.42, 34.0), (26.42, 40.9), (25.54, 41.73), (40.39, 41.73)])

fps = []
for ref, fpspec, value, px, py, rot in PLACE:
    kind, name = fpspec.split(":", 1)
    path = (FAB + name + ".kicad_mod") if kind == "fab" else (KF + name + ".kicad_mod")
    libnick = "PCM_fab" if kind == "fab" else name.split(".pretty/")[0]
    fpname = name.split("/")[-1]
    t = open(path).read()
    t = re.sub(r'^\(footprint "[^"]+"\s*\(version \d+\)\s*\(generator "[^"]+"\)\s*(?:\(generator_version "[^"]+"\)\s*)?\(layer "F.Cu"\)',
               f'(footprint "{libnick}:{fpname}"\n\t(layer "F.Cu")\n\t(uuid "{U()}")\n\t(at {px} {py} {rot})\n\t(path "/{ROOT}/{sym_uuid.get(ref, U())}")', t, count=1)
    assert t.startswith('(footprint "' + libnick), fpname
    if rot:  # KiCad stores pad and text angles as absolute: add the footprint rotation
        def rot_at(m):
            a = float(m.group(3)) if m.group(3) else 0.0
            return f'(at {m.group(1)} {m.group(2)} {(a + rot) % 360:g})'
        t = re.sub(r'\(pad "[^"]*"[^\n]*\n\s*\(at ([-\d.]+) ([-\d.]+)(?: ([-\d.]+))?\)', lambda m: m.group(0)[:m.group(0).rfind("(at")] + rot_at(m), t)
    t = re.sub(r'\(property "Reference" "[^"]*"', f'(property "Reference" "{ref}"', t, count=1)
    t = t.replace('${KICAD9_3RD_PARTY}/3dmodels/org_fabcloud_gitlab_electronics_kicad/fab.3dshapes/', '/Users/fhaddad/Documents/KiCad/10.0/3rdparty/fab/3dmodels/fab.3dshapes/')
    t = re.sub(r'(\(property "Reference" "[^"]*"\s*\(at [^)]*\)\s*(?:\(unlocked yes\)\s*)?\(layer "F\.SilkS"\))', r'\1 (hide yes)', t, count=1)
    t = re.sub(r'\(property "Value" "[^"]*"', f'(property "Value" "{value}"', t, count=1)
    pos = 0; outp = []
    while True:
        i = t.find('(pad "', pos)
        if i < 0: outp.append(t[pos:]); break
        j = t.find('(layers ', i); k = t.find(')', j) + 1
        num = re.match(r'\(pad "([^"]*)"', t[i:]).group(1); n = padnet.get((ref, num))
        outp.append(t[pos:k] + (f'\n\t\t(net {n[0]} "{n[1]}")' if n else "")); pos = k
    fps.append("".join(outp))

segs = []
for net, pts in ROUTES:
    code = name2code[net]
    for (x1,y1),(x2,y2) in zip(pts, pts[1:]):
        if (x1,y1)==(x2,y2): continue
        segs.append(f'\t(segment (start {x1} {y1}) (end {x2} {y2}) (width {W}) (layer "F.Cu") (net {code}) (uuid "{U()}"))')
netdecl = "\n".join(f'\t(net {c} "{n}")' for c, n in sorted(nets.items()))

# ---------------- outline: rounded rectangle ----------------
ox, oy, BW, BH, R = 20.0, 20.0, 85.0, 56.0, 4.0
def gl(x1,y1,x2,y2,layer="Edge.Cuts",w=0.1): return f'\t(gr_line (start {x1} {y1}) (end {x2} {y2}) (stroke (width {w}) (type default)) (layer "{layer}") (uuid "{U()}"))\n'
def garc(sx,sy,mx,my,ex,ey,layer="Edge.Cuts",w=0.1): return f'\t(gr_arc (start {sx} {sy}) (mid {mx} {my}) (end {ex} {ey}) (stroke (width {w}) (type default)) (layer "{layer}") (uuid "{U()}"))\n'
c = math.sqrt(0.5)*R
outline = (gl(ox+R, oy, ox+BW-R, oy) + gl(ox+BW, oy+R, ox+BW, oy+BH-R) + gl(ox+BW-R, oy+BH, ox+R, oy+BH) + gl(ox, oy+BH-R, ox, oy+R)
 + garc(ox+BW-R, oy, ox+BW-R+c, oy+R-c, ox+BW, oy+R) + garc(ox+BW, oy+BH-R, ox+BW-R+c, oy+BH-R+c, ox+BW-R, oy+BH)
 + garc(ox+R, oy+BH, ox+R-c, oy+BH-R+c, ox, oy+BH-R) + garc(ox, oy+R, ox+R-c, oy+R-c, ox+R, oy))

# ---------------- copper art: keys, space bar, text ----------------
art = ""
def grect(x1,y1,x2,y2,w=W): return f'\t(gr_rect (start {x1} {y1}) (end {x2} {y2}) (stroke (width {w}) (type default)) (fill no) (layer "F.Cu") (uuid "{U()}"))\n'
def stext(t,x,y,size=0.8,th=0.15):
    return f'\t(gr_text "{t}" (at {x} {y} 0) (layer "F.SilkS") (uuid "{U()}") (effects (font (size {size} {size}) (thickness {th}))))\n'
def gtext(t,x,y,size=2.0,th=0.4,just="center"):
    return f'\t(gr_text "{t}" (at {x} {y} 0) (layer "F.Cu") (uuid "{U()}") (effects (font (size {size} {size}) (thickness {th})) {"" if just=="center" else "(justify "+just+")"}))\n'





# space bar: top and bottom lines, left end closed, right end open for the LED/SELECT traces
# space bar: one closed key-cap outline, 30..94.6 x 61.6..69.4, with two breaks on the right edge for the LED and SELECT traces
art += gl(30.0, 61.6, 94.6, 61.6, "F.Cu", W) + gl(30.0, 69.4, 94.6, 69.4, "F.Cu", W) + gl(30.0, 61.6, 30.0, 69.4, "F.Cu", W)
art += gl(94.6, 61.6, 94.6, 62.2, "F.Cu", W) + gl(94.6, 65.4, 94.6, 66.0, "F.Cu", W)
art += gtext("IF-60 MODULE", 46.0, 65.5, 2.4, 0.4)
art += gtext("SEL", 77.0, 59.8, 1.6, 0.35)
art += gtext("ONLINE", 86.5, 59.8, 1.6, 0.35)
art += gtext("FG HADDAD - HTMAA 2026", 62.5, 72.8, 1.4, 0.3)
art += f'\t(gr_poly (pts (xy 46.3 24.8) (xy 47.3 25.5) (xy 46.3 26.2)) (stroke (width 0) (type default)) (fill yes) (layer "F.Cu") (uuid "{U()}"))\n'


# ---------------- silkscreen documentation (not fabricated on a milled board) ----------------
for i,(nm) in enumerate(["GND","SCK","SI","RDY","SO","ACK","5V","RQ","nc","GND","GND","nc"]): art += stext(nm, 49.5+2.5*i, 23.5, 0.6, 0.12)
art += stext("J1 TYPEWRITER BUS (JST XH 12p)", 72.0, 29.9, 0.9, 0.15)
for x,nm in [(28.96,"GND"),(31.5,"VDD"),(34.04,"SCL"),(36.58,"SDA")]: art += stext(nm, x, 27.3, 0.6, 0.12)
art += stext("J2 OLED", 23.6, 29.3, 0.8, 0.15)
for i,(x,nm) in enumerate([(39.12,"D6 SCK"),(36.58,"D5 SI"),(34.04,"D4 RDY"),(31.5,"D3 SO"),(28.96,"D2 ACK"),(26.42,"D1 5V"),(23.88,"D0 RQ")]): art += stext(nm, x, 49.4 - 0.8*(i%2), 0.5, 0.1)
for i,(x,nm) in enumerate([(39.12,"D7 LED"),(36.58,"D8 SEL"),(34.04,"D9 SDA"),(31.5,"D10 SCL"),(28.96,"3V3"),(26.42,"GND")]): art += stext(nm, x, 36.8 + 0.8*(i%2), 0.5, 0.1)
for x,y,dy in [(23.5,23.5,4.1),(101.5,23.5,4.1),(23.5,72.5,-4.1),(101.5,72.5,-4.1)]: art += stext("M3", x, y+dy, 0.8, 0.15)
art += stext("85 x 56 mm  single side  16 mil", 44.0, 71.8, 0.8, 0.15)


# ---------------- typewriter keys (copper outlines + letters), right patch ----------------
KW, KP = 4.6, 5.6
for row, (x0, y0, keys) in enumerate([(71.8, 33.6, "CHRO"), (72.8, 41.0, "NO^<"), (73.8, 48.4, "TYPE")]):
    for i, ch in enumerate(keys):
        kx = x0 + i*KP
        art += grect(kx, y0, kx+KW, y0+KW)
        art += gtext(ch, kx+KW/2, y0+KW/2, 2.4, 0.4)

# ---------------- ground pour ----------------
zone = f'''\t(zone (net {name2code["GND"]}) (net_name "GND") (layer "F.Cu") (uuid "{U()}") (hatch edge 0.5)
\t\t(connect_pads yes (clearance {W}))
\t\t(min_thickness {W}) (filled_areas_thickness no)
\t\t(fill yes (thermal_gap 0.5) (thermal_bridge_width 0.5) (island_removal_mode 0) (island_area_min 10))
\t\t(polygon (pts (xy {ox+0.5} {oy+0.5}) (xy {ox+BW-0.5} {oy+0.5}) (xy {ox+BW-0.5} {oy+BH-0.5}) (xy {ox+0.5} {oy+BH-0.5})))
\t)
'''
layers = "\n".join(f"\t\t({n} \"{name}\" {kind}{(' \"'+alias+'\"') if alias else ''})" for n,name,kind,alias in [
 (0,"F.Cu","signal",""),(31,"B.Cu","signal",""),(32,"B.Adhes","user","B.Adhesive"),(33,"F.Adhes","user","F.Adhesive"),(34,"B.Paste","user",""),(35,"F.Paste","user",""),
 (36,"B.SilkS","user","B.Silkscreen"),(37,"F.SilkS","user","F.Silkscreen"),(38,"B.Mask","user",""),(39,"F.Mask","user",""),(40,"Dwgs.User","user","User.Drawings"),
 (41,"Cmts.User","user","User.Comments"),(44,"Edge.Cuts","user",""),(45,"Margin","user",""),(46,"B.CrtYd","user","B.Courtyard"),(47,"F.CrtYd","user","F.Courtyard"),(48,"B.Fab","user",""),(49,"F.Fab","user","")])
pcb = f'''(kicad_pcb
\t(version 20240108)
\t(generator "pcbnew")
\t(generator_version "8.0")
\t(general
\t\t(thickness 1.6)
\t\t(legacy_teardrops no)
\t)
\t(paper "A4")
\t(layers
{layers}
\t)
\t(setup
\t\t(pad_to_mask_clearance 0)
\t\t(allow_soldermask_bridges_in_footprints no)
\t\t(pcbplotparams (layerselection 0x00010fc_ffffffff) (plot_on_all_layers_selection 0x0000000_00000000) (disableapertmacros no) (usegerberextensions no) (usegerberattributes yes) (usegerberadvancedattributes yes) (creategerberjobfile yes) (dashed_line_dash_ratio 12.000000) (dashed_line_gap_ratio 3.000000) (svgprecision 4) (plotframeref no) (mode 1) (useauxorigin no) (hpglpennumber 1) (hpglpenspeed 20) (hpglpendiameter 15.000000) (pdf_front_fp_property_popups yes) (pdf_back_fp_property_popups yes) (dxfpolygonmode no) (dxfimperialunits yes) (dxfusepcbnewfont yes) (psnegative no) (psa4output no) (plot_black_and_white yes) (sketchpadsonfab no) (plotpadnumbers no) (hidednponfab no) (subtractmaskfromsilk no) (outputformat 1) (mirror no) (drillshape 1) (scaleselection 1) (outputdirectory ""))
\t)
\t(net 0 "")
{netdecl}
{chr(10).join(fps)}
{chr(10).join(segs)}
{outline}{art}{zone})
'''
open(D + f"/{PROJECT}.kicad_pcb", "w").write(pcb)
pro = {
 "board": {"design_settings": {"rules": {
    "min_clearance": W, "min_track_width": W, "min_copper_edge_clearance": 0.5,
    "min_hole_clearance": 0.4, "min_via_diameter": 0.8, "min_via_annular_width": 0.3,
    "solder_mask_clearance": 0.0, "min_connection": 0.0, "min_text_height": 0.5, "min_text_thickness": 0.08},
   "rule_severities": {"solder_mask_bridge": "ignore", "silk_over_copper": "ignore", "silk_overlap": "warning", "lib_footprint_mismatch": "ignore"},
   "defaults": {"board_outline_line_width": 0.1, "silk_line_width": 0.15, "silk_text_size_h": 1.0, "silk_text_size_v": 1.0, "silk_text_thickness": 0.15}}},
 "meta": {"filename": f"{PROJECT}.kicad_pro", "version": 3},
 "net_settings": {"classes": [{"name": "Default", "clearance": W, "track_width": W, "via_diameter": 0.8, "via_drill": 0.4,
                               "diff_pair_gap": 0.25, "diff_pair_via_gap": 0.25, "diff_pair_width": 0.2, "microvia_diameter": 0.3, "microvia_drill": 0.1,
                               "bus_width": 12, "line_style": 0, "wire_width": 6, "pcb_color": "rgba(0, 0, 0, 0.000)", "schematic_color": "rgba(0, 0, 0, 0.000)", "priority": 2147483647}],
                  "meta": {"version": 4}},
 "pcbnew": {"last_paths": {"gencad": "", "idf": "", "netlist": "", "plot": "", "pos_files": "", "specctra_dsn": "", "step": "", "svg": "", "vrml": ""}, "page_layout_descr_file": ""},
 "schematic": {"legacy_lib_dir": "", "legacy_lib_list": []},
 "sheets": [[ROOT, "Root"]], "text_variables": {}
}
json.dump(pro, open(D + f"/{PROJECT}.kicad_pro", "w"), indent=2)
print("pcb written:", len(fps), "footprints,", len(nets), "nets,", len(segs), "segments")

