from itertools import chain, takewhile, dropwhile, filterfalse

SOURCE_SIGNALS = {'VDD', 'VCC', 'VCCIO', 'VCORE', 'VPHY', 'VPLL'}
SINK_SIGNALS = {'GND', 'AGND'}
POWER_SIGNALS = SOURCE_SIGNALS | SINK_SIGNALS

def split_power(lines):
    source = []
    sink = []
    other = []
    for pin, signal, type in lines:
        if signal in SOURCE_SIGNALS:
            source.append((pin, signal, type))
        elif signal in SINK_SIGNALS:
            sink.append((pin, signal, type))
        else:
            other.append((pin, signal, type))

    return source, sink, other

def make_pin_left(lines, half_width):
    for ix, (number, name, type) in enumerate(lines):
        x = u(-half_width)
        y = u((-ix + 0) * 2)
        r = 0
        yield pin(x, y, r, number, name, type)

def make_pin_right(lines, half_width):
    for ix, (number, name, type) in enumerate(lines):
        x = u(half_width)
        y = u((-ix + 0) * 2)
        r = 180
        yield pin(x, y, r, number, name, type)

def make_pin_top(lines):
    for ix, (number, name, type) in enumerate(lines):
        x = u((ix - (len(lines) / 2)) * 2)
        y = u(6)
        r = 270
        yield pin(x, y, r, number, name, type)

def make_pin_bottom(lines, height):
    for ix, (number, name, type) in enumerate(lines):
        x = u((ix - (len(lines) / 2))  * 2)
        y = u(-((height + 2) * 2))
        r = 90
        yield pin(x, y, r, number, name, type)

input_p = lambda lnt: lnt[2] in {"input", "power_in"}

def make_pins(lines, half_width, split_p=input_p):
    pin_sort = lambda l: pin_sort_key(l[1])
    lines = sorted(lines, key=pin_sort)
    left = list(filter(split_p, lines))
    right = list(filterfalse(split_p, lines))
    pins = chain(
        make_pin_left(left, half_width),
        make_pin_right(right, half_width)
    )
    return pins, max(len(left), len(right))

def pin_sort_key(s):
    is_not_numeric = lambda c: not (ord(c) >= ord('0') and ord(c) <= ord('9'))
    h = ''.join(takewhile(is_not_numeric, s))
    t = ''.join(dropwhile(is_not_numeric, s))
    return h, int(t) if t else -1

def assert_valid(lines):
    pins = set()
    signals = set()

    types = set([
        "power_in",
        "power_out",
        "bidirectional",
        "input",
        "output"
    ])

    for line in lines:
        pin, signal, type = line[:3]
        assert type in types, type
        assert pin not in pins, pins
        pins.add(pin)
        if signal not in POWER_SIGNALS:
            assert signal not in signals, signal
            signals.add(signal)

def read_txt(filename):
    with open(filename) as f:
        lines = [l.split() for l in f.read().split('\n') if l]
    assert_valid(lines)
    return lines

_lib = """
(kicad_symbol_lib (version 20220914) (generator kicad_sym)
  {symbols}
)
""".strip('\n')

_symbol = """
  (symbol "{name}" (in_bom yes) (on_board yes)
    (property "Reference" "U" (at 0 -7.62 0)
      (effects (font (size 1.27 1.27)))
    )
    (property "Value" "{value}" (at 0 -10.16 0)
      (effects (font (size 1.27 1.27)))
    )
    (property "Footprint" "" (at 0 0 0)
      (effects (font (size 1.27 1.27)) hide)
    )
    (property "Datasheet" "" (at 0 0 0)
      (effects (font (size 1.27 1.27)) hide)
    )
    (symbol "{name}_0_1"
      (rectangle (start {x0} {y0}) (end {x1} {y1})
        (stroke (width 0) (type default))
        (fill (type none))
      )
    )
    (symbol "{name}_1_1"
      {pins}
    )
  )
""".strip('\n')

_pin = """
      (pin {type} line (at {x:.02f} {y:.02f} {r}) (length {length})
        (name "{name}" (effects (font (size 1.27 1.27))))
        (number "{number}" (effects (font (size 1.27 1.27))))
      )
""".strip('\n')

def lib(symbols):
    return _lib.format(
        symbols="\n".join(symbols)
    )

def symbol(name, value, pins, x0, y0, x1, y1):
    return _symbol.format(
        name=name,
        value=value,
        pins="\n".join(pins),
        x0=x0,
        y0=y0,
        x1=x1,
        y1=y1
    )

def pin(x, y, r, number, name, type, length=4):
    return _pin.format(
        x=x - u(length) if x < 0 else x + u(length),
        y=y,
        r=r,
        name=name,
        type=type,
        number=number,
        length=u(length)
    )

def u(n):
    mul = 1.27
    return n * mul
