#!/usr/bin/env python3
"""Write the MeerK40t calibration job files (black construction paper, ComMarker B4).
  python3 gen_mk_cal.py --lens 131.5 --out DIR
Writes per sheet S1/S2/S3: <name>.svg (MeerK40t project with operations), <name>_plain.svg (plain SVG, mm),
<name>_preview.png, plus the power/speed ladder job. Grok 2026-10-31. NEVER burns anything."""
import argparse, json, math, os
import numpy as np
from mk_cal_design import geometry, text_items, OUTER

U = 65535 / 25.4          # MeerK40t native units per mm (2580.118...)


def text_polys(text, x0, yb, height):
    from matplotlib.textpath import TextPath
    from matplotlib.font_manager import FontProperties
    tp = TextPath((0, 0), text, size=1.0, prop=FontProperties(family='DejaVu Sans', weight='bold'))
    polys = tp.to_polygons(closed_only=True)
    s = height / 0.73     # cap height of DejaVu ~0.73 em
    out = []
    for p in polys:
        out.append([(x0 + s * x, yb - s * y) for x, y in p])
    return out


def d_of(poly):
    return 'M ' + ' L '.join(f'{x:.4f},{y:.4f}' for x, y in poly)


OP_TMPL = ('\t<g type="op engrave" label="{label}" allowed_attributes="[\'stroke\']" color="{color}" '
           'speed="{speed:.1f}" power="{power:.1f}" frequency="{freq:.1f}" pulse_width_enabled="True" '
           'pulse_width="{pw}" passes_custom="True" passes="{passes}" output="{output}" dangerous="False" '
           'id="{id}" lock="False" references="{refs}" />\n')


def write_project(path, lens, ops, elems):
    """ops: list of dict(label,color,speed,power_pct,freq,pw,passes,output,id) ; elems: list of (opid, color, polyline)"""
    c = lens / 2
    vb = round(lens * U)
    refs = {o['id']: [] for o in ops}
    body = []
    for i, (opid, color, poly) in enumerate(elems):
        eid = f'{opid}_e{i}'
        refs[opid].append(eid)
        body.append(f'\t\t<path d="{d_of(poly)}" transform="matrix({U:.7f}, 0.0, 0.0, {U:.7f}, {c*U:.4f}, {c*U:.4f})" '
                    f'stroke_scale="False" type="elem path" id="{eid}" lock="False" hidden="False" label_display="False" '
                    f'stroke-linecap="butt" stroke-linejoin="miter" fill-rule="nonzero" stroke="{color}" '
                    f'stroke-width="258.0" fill="none" />\n')
    s = ('<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" '
         'xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:meerk40t="https://github.com/meerk40t/meerk40t/wiki/Namespace" '
         f'width="{lens}mm" height="{lens}mm" viewBox="0 0 {vb} {vb}" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">\n')
    for o in ops:
        s += OP_TMPL.format(label=o['label'], color=o['color'] + 'ff', speed=o['speed'], power=o['power_pct'] * 10,
                            freq=o['freq'], pw=o['pw'], passes=o['passes'], output=str(o['output']), id=o['id'],
                            refs=' '.join(refs[o['id']]))
    s += f'\t<g inkscape:label="{os.path.basename(path)}">\n' + ''.join(body) + '\t</g>\n</svg>\n'
    open(path, 'w').write(s)


def write_plain(path, lens, elems):
    c = lens / 2
    s = (f'<svg xmlns="http://www.w3.org/2000/svg" width="{lens}mm" height="{lens}mm" viewBox="0 0 {lens} {lens}">\n'
         f'<g transform="translate({c},{c})" fill="none" stroke-width="0.1">\n')
    for opid, color, poly in elems:
        s += f'<path d="{d_of(poly)}" stroke="{color}"/>\n'
    open(path, 'w').write(s + '</g>\n</svg>\n')


def write_preview(path, lens, elems, title):
    import cv2
    ppm = 8.0
    n = int(lens * ppm)
    img = np.full((n + 60, n, 3), 255, np.uint8)
    c = lens / 2
    cv2.rectangle(img, (0, 0), (n - 1, n - 1), (200, 200, 200), 1)
    for opid, color, poly in elems:
        col = tuple(int(color[i:i + 2], 16) for i in (5, 3, 1))
        pts = np.array([[(x + c) * ppm, (y + c) * ppm] for x, y in poly], np.float64)
        cv2.polylines(img, [np.round(pts * 16).astype(np.int32)], False, col, 1, cv2.LINE_AA, shift=4)
    cv2.putText(img, title, (8, n + 40), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 1, cv2.LINE_AA)
    cv2.imwrite(path, img)


def geometry_job(outdir, lens, sheet, power, speed, freq, pw):
    name = f'mk_cal_S{sheet}_L{lens:g}'
    label = f'S{sheet} MK L{lens:g}' if sheet == 1 else f'S{sheet} MK'
    ops = [dict(label=f'1 GEOM lines ({power:g}%, {speed:g}mm/s, {freq:g}kHz, {pw}ns, x1)', color='#0000ff', speed=speed,
                power_pct=power, freq=freq, pw=pw, passes=1, output=True, id='GEOM'),
           dict(label=f'2 TEXT labels (outline text, same setting)', color='#00a000', speed=speed, power_pct=power,
                freq=freq, pw=pw, passes=1, output=True, id='TEXT'),
           dict(label='3 FRAME 100mm reference only - OUTPUT OFF', color='#00a0ff', speed=speed, power_pct=power,
                freq=freq, pw=pw, passes=1, output=False, id='FRAME')]
    col = {'GEOM': '#0000ff', 'TEXT': '#00a000', 'FRAME': '#00a0ff'}
    elems = []
    for layer, poly in geometry():
        opid = {'geom': 'GEOM', 'text': 'TEXT', 'frame': 'FRAME'}[layer]
        elems.append((opid, col[opid], poly))
    for t, x0, yb, h in text_items(label):
        for p in text_polys(t, x0, yb, h):
            elems.append(('TEXT', col['TEXT'], p))
    write_project(os.path.join(outdir, name + '.svg'), lens, ops, elems)
    write_plain(os.path.join(outdir, name + '_plain.svg'), lens, elems)
    write_preview(os.path.join(outdir, name + '_preview.png'), lens, elems,
                  f'{name}: blue=GEOM green=TEXT cyan=FRAME(off). {power:g}% {speed:g}mm/s {freq:g}kHz {pw}ns x1')
    return name, ops


LADDER_POW = [3, 5, 8, 12, 16, 20]          # %
LADDER_SPD = [2000, 1500, 1000]             # mm/s


def ladder_job(outdir, lens, freq, pw):
    """6 powers x 3 speeds; each cell = 6 mm square + X, lines only. Ops ordered lowest energy first."""
    name = f'mk_cal_S1_ladder_L{lens:g}'
    cell, pitch = 6.0, 9.0
    ncol, nrow = len(LADDER_POW), len(LADDER_SPD)
    x0 = -(ncol - 1) * pitch / 2
    y0 = -(nrow - 1) * pitch / 2 + 2.0
    cells = []
    for r, spd in enumerate(LADDER_SPD):
        for cidx, pw_ in enumerate(LADDER_POW):
            e = pw_ * 0.6 / spd    # J/mm line energy (60 W source)
            cells.append((e, r, cidx, spd, pw_))
    cells.sort()
    ops, elems = [], []
    for k, (e, r, cidx, spd, pwr) in enumerate(cells):
        cx, cy = x0 + cidx * pitch, y0 + r * pitch
        h = cell / 2
        oid = f'L{k+1:02d}'
        ops.append(dict(label=f'{k+1:02d} R{r+1}C{cidx+1} {pwr}% {spd}mm/s ({e*1000:.1f} mJ/mm)', color='#ff0000', speed=spd,
                        power_pct=pwr, freq=freq, pw=pw, passes=1, output=True, id=oid))
        elems.append((oid, '#ff0000', [(cx - h, cy - h), (cx + h, cy - h), (cx + h, cy + h), (cx - h, cy + h), (cx - h, cy - h)]))
        elems.append((oid, '#ff0000', [(cx - h, cy - h), (cx + h, cy + h)]))
    # labels: power above columns, speed left of rows, orientation 'L' mark at top-left; lowest setting that is likely visible
    ops.append(dict(label='99 LABELS (8%, 2000mm/s)', color='#00a000', speed=2000, power_pct=8, freq=freq, pw=pw, passes=1,
                    output=True, id='LAB'))
    ops.append(dict(label='FRAME reference only - OUTPUT OFF', color='#00a0ff', speed=2000, power_pct=8, freq=freq, pw=pw,
                    passes=1, output=False, id='FRAME'))
    for cidx, pwr in enumerate(LADDER_POW):
        for p in text_polys(f'{pwr}', x0 + cidx * pitch - 1.6, y0 - cell / 2 - 1.2, 2.2):
            elems.append(('LAB', '#00a000', p))
    for r, spd in enumerate(LADDER_SPD):
        for p in text_polys(f'{spd // 100}', x0 - cell / 2 - 5.2, y0 + r * pitch + 1.1, 2.2):
            elems.append(('LAB', '#00a000', p))
    for p in text_polys('% / v x100', x0 - 7.5, y0 - cell / 2 - 5.0, 2.0):
        elems.append(('LAB', '#00a000', p))
    fx0, fx1 = x0 - cell / 2 - 7, x0 + (ncol - 1) * pitch + cell / 2 + 1
    fy0, fy1 = y0 - cell / 2 - 8, y0 + (nrow - 1) * pitch + cell / 2 + 1
    elems.append(('FRAME', '#00a0ff', [(fx0, fy0), (fx1, fy0), (fx1, fy1), (fx0, fy1), (fx0, fy0)]))
    write_project(os.path.join(outdir, name + '.svg'), lens, ops, elems)
    write_plain(os.path.join(outdir, name + '_plain.svg'), lens, elems)
    write_preview(os.path.join(outdir, name + '_preview.png'), lens, elems,
                  f'{name}: cols % {LADDER_POW}, rows mm/s {LADDER_SPD}, {freq:g}kHz {pw}ns x1, burns low->high energy')
    return name, ops, cells


if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument('--lens', type=float, default=131.5)
    ap.add_argument('--out', default='.')
    ap.add_argument('--power', type=float, default=8.0, help='GEOM/TEXT power %% (change to the ladder winner)')
    ap.add_argument('--speed', type=float, default=2000.0)
    ap.add_argument('--freq', type=float, default=50.0)
    ap.add_argument('--pw', type=int, default=200)
    a = ap.parse_args()
    os.makedirs(a.out, exist_ok=True)
    info = {'lens_mm': a.lens, 'geometry': {}}
    for s in (1, 2, 3):
        n, ops = geometry_job(a.out, a.lens, s, a.power, a.speed, a.freq, a.pw)
        info['geometry'][n] = ops
    n, ops, cells = ladder_job(a.out, a.lens, a.freq, a.pw)
    info['ladder'] = {n: ops}
    json.dump(info, open(os.path.join(a.out, f'jobs_L{a.lens:g}.json'), 'w'), indent=1)
    print('written to', a.out)
