#!/usr/bin/env python3
"""Make review candidates with matching project instances and local symbol libraries."""
import hashlib
import json
import re
import shutil
import subprocess
from pathlib import Path

ROOT = Path(__file__).resolve().parent
BASE = ROOT.parent
CLI = '/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli'
SPECS = {
    'vacuum': ('vacuum_board_a0_validation/paired/vacuum_control_evt_a0',
               'vacuum_board_a0_validation/paired/vacuum_control_evt_a0.kicad_sch',
               'vacuum_control_evt_a1_metadata'),
    'drill': ('drill_motor_run_gate_r1/drill_motor/evt/drill_motor_evt_a1_run_gate',
              'drill_motor_run_gate_r1/drill_motor/drill_motor.kicad_sch',
              'drill_motor_evt_a2_metadata'),
}

def end_form(text, start):
    depth = 0
    quoted = escaped = False
    for i in range(start, len(text)):
        ch = text[i]
        if quoted:
            if escaped: escaped = False
            elif ch == '\\': escaped = True
            elif ch == '"': quoted = False
        elif ch == '"': quoted = True
        elif ch == '(': depth += 1
        elif ch == ')':
            depth -= 1
            if depth == 0: return i + 1
    raise ValueError('Unbalanced form')

def forms(text, tag):
    token = re.compile(r'\(\s*' + re.escape(tag) + r'\s+')
    pos = 0
    while (m := token.search(text, pos)):
        end = end_form(text, m.start())
        yield m.start(), end, text[m.start():end]
        pos = end

def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest()

def run(args, log):
    p = subprocess.run([CLI, *args], text=True, capture_output=True)
    log.write_text(p.stdout + '\nSTDERR:\n' + p.stderr)
    return p.returncode

def main():
    evidence = {}
    for key, (board_stem, sch_rel, name) in SPECS.items():
        dst = ROOT / key
        dst.mkdir(parents=True, exist_ok=True)
        src_board = BASE / (board_stem + '.kicad_pcb')
        src_pro = BASE / (board_stem + '.kicad_pro')
        src_sch = BASE / sch_rel
        s = src_sch.read_text()
        old_instances = sorted(set(re.findall(r'\(project\s+"([^"]+)"', s)))
        aligned = re.sub(r'\(project\s+"([^"]+)"', lambda m: '(project ' + json.dumps(name), s)
        # Embedded symbol definitions remain unchanged. Register a local library
        # using exactly those definitions, rather than suppressing ERC checks.
        lib_block = list(forms(s, 'lib_symbols'))
        assert len(lib_block) == 1
        lib = re.sub(r'^\(\s*lib_symbols\s+', '', lib_block[0][2])[:-1]
        lib = re.sub(r'(\(symbol\s+")Project:', r'\1', lib)
        (dst / 'Project.kicad_sym').write_text('(kicad_symbol_lib (version 20241209) (generator "kicad_symbol_editor")\n' + lib + '\n)\n')
        (dst / 'sym-lib-table').write_text('(sym_lib_table (version 7) (lib (name "Project") (type "KiCad") (uri "${KIPRJMOD}/Project.kicad_sym") (options "") (descr "Exact embedded project symbols")))\n')
        pcb = dst / (name + '.kicad_pcb')
        sch = dst / (name + '.kicad_sch')
        pro = dst / (name + '.kicad_pro')
        shutil.copyfile(src_board, pcb)
        shutil.copyfile(src_pro, pro)
        sch.write_text(aligned)
        if key == 'vacuum':
            shutil.copyfile(src_board.parent / 'fp-lib-table', dst / 'fp-lib-table')
            shutil.copytree(src_board.parent / 'Vacuum_Shunt.pretty', dst / 'Vacuum_Shunt.pretty', dirs_exist_ok=True)
        rc_net = run(['sch','export','netlist','--format','kicadxml','--output',str(dst/'netlist.xml'),str(sch)], dst/'netlist.log')
        rc_erc = run(['sch','erc','--severity-all','--format','json','--output',str(dst/'erc.json'),str(sch)], dst/'erc.log')
        rc_drc = run(['pcb','drc','--severity-all','--all-track-errors','--schematic-parity','--format','json','--output',str(dst/'drc-parity.json'),str(pcb)], dst/'drc.log')
        d = json.loads((dst/'drc-parity.json').read_text())
        e = json.loads((dst/'erc.json').read_text())
        vs = [v for sh in e.get('sheets',[]) for v in sh.get('violations',[])]
        evidence[key] = {'source_board':str(src_board.relative_to(BASE)), 'source_sha256':sha(src_board), 'candidate_sha256':sha(pcb), 'old_instance_names':old_instances, 'new_instance_name':name, 'board_byte_identical_before_normalization':sha(src_board)==sha(pcb), 'netlist_rc':rc_net,'erc_rc':rc_erc,'drc_rc':rc_drc,'erc_violations':len(vs),'erc_types':sorted(set(v['type'] for v in vs)),'drc_violations':len(d.get('violations',[])), 'unconnected':len(d.get('unconnected_items',[])), 'parity_count':len(d.get('schematic_parity',[])), 'parity_types':sorted(set(v['type'] for v in d.get('schematic_parity',[])))}
    (ROOT/'initial_checks.json').write_text(json.dumps(evidence, indent=2)+'\n')
    print(json.dumps(evidence,indent=2))

if __name__ == '__main__': main()
