#!/usr/bin/env python3

import re
import math
from dataclasses import replace

import gerbonara.cad.kicad.pcb as pcb
import gerbonara.cad.primitives as cad_pr


r = 85
cx, cy = 150, 100

prev_next_cycle = lambda l: zip(l[-1:] + l[:-1], l, l[1:] + l[:1])

si_prefixes = {'k': 1e3, 'M': 1e6, 'G': 1e9, 'm': 1e-3, 'u': 1e-6, 'µ': 1e-6, 'n': 1e-9, 'p': 1e-12, '.': 1, 'r': 1}
def parse_si(s, unit=''):
    if not (match := re.fullmatch(f'([0-9]*)(([.pPnNuUµmrRkKMgG])([0-9]*))?([pPnNuUµmrRkKMgG]?)({unit})?', s)):
        raise ValueError(f'{s} is not a valid number with an SI prefix')
    
    left, _1, dot, right, prefix, _unit = match.groups()
    prefix = prefix or dot or '.'
    multiplier = si_prefixes.get(prefix, si_prefixes.get(prefix.upper(), si_prefixes.get(prefix.lower())))

    return float(f'{left}.{right or ""}0') * multiplier
    

b = pcb.Board.open('self-balancing-test-a.kicad_pcb')
b.unfill_zones()

matches = list(b.find_footprints(name='Package_TO_SOT_SMD:SOT-23', sheetfile='resistor_bank.kicad_sch'))

components = []
for transistor in sorted(matches, key=lambda fp: fp.parsed_reference[1]):
    r1, r2 = transistor.pads_by_number[3].find_connected(name='Resistor_SMD.*')
    if parse_si(r1.value) > parse_si(r2.value):
        r1, r2 = r2, r1

    cap, = transistor.pads_by_number[3].find_connected(name='Capacitor_SMD.*')

    components.append((transistor, r1, r2, cap))


num_lgs = 8
lg_pitch = 1
    
for i, (fp, res1, res2, cap) in enumerate(components):
    alpha = 2*math.pi * i/len(matches)

    fp.at.x, fp.at.y = cx+r, cy
    fp.set_rotation(0)
    for text in fp.texts:
        text.at.rotation = 0
    for prop in fp.properties:
        prop.at.rotation = 0

    cap.at.x, cap.at.y  = cx+r+5, cy
    cap.side = 'back'
    cap.face('top', pad=1)

    res1.at.x, res1.at.y = cx+r+5, cy+2
    res1.face('right', net='*/V+')

    res2.at.x, res2.at.y = cx+r+5, cy-2
    res2.face('right', net='*/V+')

    gnd_via = pcb.Via(at=pcb.XYCoord(fp.pad(2)).with_offset(y=1.5),
                  size=1.2,
                  drill=0.6,
                  net=fp.pad(2).net.number
                  )
    b.add(gnd_via)

    lg = int(re.search(r'[0-9]*$', fp.pad(1).net.name).group(0))
    lg_via = pcb.Via(at=pcb.XYCoord(fp.pad(1)).with_offset(x=-2-lg_pitch*lg),
                  size=0.8,
                  drill=0.4,
                  net=fp.pad(1).net.number
                  )
    b.add(lg_via)

    x1, y1, _r, _f = res1.pad(2).abs_pos
    x2, y2, _r, _f = res2.pad(2).abs_pos
    x3, y3, _r, _f = res1.pad(1).abs_pos
    x4, y4, _r, _f = res2.pad(1).abs_pos
    mp = (x1+x2)/2, (y1+y2)/2
    for tr, net_name in [
        (cad_pr.Trace(1.5, res1.pad(2), res2.pad(2), orientation=['ccw']), fp.pad(3).net.name),
        (cad_pr.Trace(0.5, fp.pad(3), mp, orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, mp, (mp[0]+1.5, mp[1]), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x1, y1), (x1, y1+2), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x1-1, y1+2), (x1+1.6, y1+2), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x2, y2), (x2, y2-2), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x2-1, y2-2), (x2+1.6, y2-2), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x3, y3), (x3+1.5, y3), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x4, y4), (x4+1.5, y4), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x3+1.5, y3-0.8), (x3+1.5, y3+0.8), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x4+1.5, y4-0.8), (x4+1.5, y4+0.8), orientation=['cw']), fp.pad(3).net.name),
        (cad_pr.Trace(0.5, res1.pad(1), res2.pad(1)), res1.pad(1).net.name),
        (cad_pr.Trace(0.5, fp.pad(2), gnd_via), fp.pad(2).net.name),
        (cad_pr.Trace(0.5, fp.pad(1), lg_via), fp.pad(1).net.name),
        (cad_pr.Trace(1.5, (x1-1, y1+2), (x1+1.6, y1+2), orientation=['cw'], side='bottom'), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x2-1, y2-2), (x2+1.6, y2-2), orientation=['cw'], side='bottom'), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, mp, (mp[0]+1.5, mp[1]), orientation=['cw'], side='bottom'), fp.pad(3).net.name),
        (cad_pr.Trace(1.5, (x1+1.6, y1+2), (x2+1.6, y2-2), orientation=['cw'], side='bottom'), fp.pad(3).net.name),
        ]:
        for obj in b.map_gn_cad(tr, net_name=net_name):
            obj.rotate(alpha, cx, cy)
            b.add(obj)

    for j in range(-1, 2):
        for i in range(3):
            therm_via = pcb.Via(at=pcb.XYCoord(mp).with_offset(x=(i-1)*1.3 + 0.3, y=j*4),
                          size=1.2,
                          drill=0.6,
                          net=fp.pad(3).net.number
                          )
            b.add(therm_via)
            therm_via.rotate(alpha, cx, cy)
    
    for x, y in (x3, y3), (x4, y4):
        for i in -1, 1:
            therm_via = pcb.Via(at=(x+1.5, y+i*0.8),
                          size=1.2,
                          drill=0.6,
                          net=fp.pad(3).net.number
                          )
            b.add(therm_via)
            therm_via.rotate(alpha, cx, cy)

    for i in range(num_lgs):
        p1 = pcb.XYCoord(fp.pad(1)).with_offset(x=-2-lg_pitch*i)
        arc = pcb.TrackArc(
                start=p1,
                end=p1.with_rotation(2*math.pi / len(matches), cx, cy),
                center=(cx, cy),
                layer='B.Cu',
                width=0.5,
                net=b.net_id(f'LG{i}'),
                )
        arc.rotate(alpha, cx, cy)
        b.add(arc)

    gnd_via.rotate(alpha, cx, cy)
    lg_via.rotate(alpha, cx, cy)
    fp.rotate(alpha, cx, cy)
    res1.rotate(alpha, cx, cy)
    res2.rotate(alpha, cx, cy)
    cap.rotate(alpha, cx, cy)

for i, ((prev_q, prev_r1, prev_r2, prev_cap), (fp, res1, res2, cap), (next_q, next_r1, next_r2, next_cap)) in enumerate(prev_next_cycle(components)):
    if res1.pad(1).net == next_r2.pad(1).net:
        arc = pcb.TrackArc(
                end=res1.pad(1),
                center=(cx, cy),
                start=next_r2.pad(1),
                width=0.5,
                net=res2.pad(1).net.number,
                )
        b.add(arc)

b.write('self-balancing-test-a.testout.kicad_pcb')
