"""
build_mod_chip.py
Copyright 2015 Adam Greig
Licensed under the MIT licence, see LICENSE file for details.

Create two-terminal chip packages.
"""
from __future__ import print_function, division

# Other constants =============================================================

# Courtyard clearance
# Use 0.25 for IPC nominal and 0.10 for IPC least.
ctyd_gap = 0.25

# Courtyard grid
ctyd_grid = 0.05

# Courtyard line width
ctyd_width = 0.01

# Internal silk clearance from pads
silk_pad_igap = 0.2

# External silk clearance from pads
silk_pad_egap = 0.2

# Silk line width
silk_width = 0.15

# Fab layer line width
fab_width = 0.01

# Ref/Val font size (width x height)
font_size = (1.0, 1.0)

# Ref/Val font thickness
font_thickness = 0.15

# Ref/Val font spacing from centre to top/bottom edge
font_halfheight = 0.7

# End constants ===============================================================

import os
import sys
import time
import math
import argparse
import yaml
import fnmatch

from sexp import parse as sexp_parse, generate as sexp_generate
from kicad_mod import fp_line, fp_text, pad, draw_square, model


def refs(conf):
    """Generate val and ref labels."""
    out = []
    x = conf['pitch']/2 + conf['pad_shape'][0]/2 + ctyd_gap + font_halfheight
    out.append(fp_text("reference", "REF**", (-x, 0, 90), "F.Fab",
                       font_size, font_thickness))
    out.append(fp_text("value", conf['name'], (x, 0, 90), "F.Fab",
                       font_size, font_thickness))
    return out


def fab(conf):
    """Generate a drawing of the chip on the Fab layer."""
    out = []
    cw, ch = conf['chip_shape']
    pw, ph = conf['pin_shape']
    w = fab_width

    # Chip body
    _, _, _, _, sq = draw_square(cw, ch, (0, 0), "F.Fab", w)
    out += sq

    # Pin 1 indicator
    if conf.get('silk') in ("triangle", "internal_pin1", "external_pin1"):
        out.append(fp_line(
            (-cw/4, -ch/2), (-cw/4, ch/2), "F.Fab", w))

    # Left pin
    out.append(fp_line((-cw/2 - pw, -ph/2), (-cw/2 - pw, ph/2), "F.Fab", w))
    if ph != ch:
        out.append(fp_line((-cw/2 - pw, -ph/2), (-cw/2, -ph/2), "F.Fab", w))
        out.append(fp_line((-cw/2 - pw, ph/2), (-cw/2, ph/2), "F.Fab", w))

    # Right pin
    out.append(fp_line((cw/2 + pw, -ph/2), (cw/2 + pw, ph/2), "F.Fab", w))
    if ph != ch:
        out.append(fp_line((cw/2 + pw, -ph/2), (cw/2, -ph/2), "F.Fab", w))
        out.append(fp_line((cw/2 + pw, ph/2), (cw/2, ph/2), "F.Fab", w))

    return out


def internal_silk(conf):
    """Draw internal silkscreen."""
    w = conf['pitch'] - conf['pad_shape'][0] - 2*silk_pad_igap
    h = conf['chip_shape'][1] - silk_width
    _, _, _, _, sq = draw_square(w, h, (0, 0), "F.SilkS", silk_width)
    return sq


def external_silk(conf):
    """Draw external silkscreen."""
    out = []
    return out


def triangle_silk(conf):
    """Draw a triangle silkscreen pointing to pin 1."""
    out = []
    w = conf['pitch'] - conf['pad_shape'][0] - 2*silk_pad_igap
    h = conf['chip_shape'][1] - silk_width
    out.append(fp_line((-w/2, 0), (w/2, -h/2), "F.SilkS", silk_width))
    out.append(fp_line((-w/2, 0), (w/2, +h/2), "F.SilkS", silk_width))
    out.append(fp_line((w/2, -h/2), (w/2, +h/2), "F.SilkS", silk_width))
    return out


def pin1_silk(conf):
    """Draw a small pin1 indicator on the silkscreen."""
    w = conf['pitch'] - conf['pad_shape'][0] - 2*silk_pad_igap
    h = conf['chip_shape'][1] - silk_width
    return [fp_line((-w/4, -h/2), (-w/4, h/2), "F.SilkS", silk_width)]


def silk(conf):
    s = conf.get('silk', 'internal')
    if s == "internal":
        return internal_silk(conf)
    elif s == "external":
        return external_silk(conf)
    elif s == "triangle":
        return triangle_silk(conf)
    elif s == "internal_pin1":
        return internal_silk(conf) + pin1_silk(conf)
    elif s == "external_pin1":
        return external_silk(conf) + pin1_silk(conf)
    else:
        return []


def ctyd(conf):
    """Draw a courtyard around the part."""
    gap = conf.get('courtyard_gap', ctyd_gap)

    # Compute width and height of courtyard
    width = max(conf['pad_shape'][0] + conf['pitch'], conf['chip_shape'][0])
    height = max(conf['pad_shape'][1], conf['chip_shape'][1])

    width += 2 * gap
    height += 2 * gap

    # Ensure courtyard lies on a specified grid
    # (double the grid since we halve the width/height)
    grid = 2*ctyd_grid
    width = grid * int(math.ceil(width / (2*ctyd_grid)))
    height = grid * int(math.ceil(height / (2*ctyd_grid)))

    # Render courtyard
    _, _, _, _, sq = draw_square(width, height, (0, 0), "F.CrtYd", ctyd_width)
    return sq


def pads(conf):
    """Place the part pads."""
    out = []
    x = conf['pitch'] / 2.0
    layers = ["F.Cu", "F.Mask", "F.Paste"]
    out.append(pad(1, "smd", "rect", (-x, 0), conf['pad_shape'], layers))
    out.append(pad(2, "smd", "rect", (+x, 0), conf['pad_shape'], layers))
    return out


def _3d(conf):
    """Add 3d model."""
    if "model" in conf:
        return [model(**conf['model'])]
    else:
        return []


def footprint(conf):
    tedit = format(int(time.time()), 'X')
    sexp = ["module", conf['name'], ("layer", "F.Cu"), ("tedit", tedit)]
    sexp += [["attr", "smd"]]
    sexp += refs(conf)
    sexp += fab(conf)
    sexp += silk(conf)
    sexp += ctyd(conf)
    sexp += pads(conf)
    sexp += _3d(conf)
    return sexp_generate(sexp)


def load_items(modpath):
    config = {}
    for dirpath, dirnames, files in os.walk(modpath):
        dirnames.sort()
        files.sort()
        for fn in fnmatch.filter(files, "*.yaml"):
            path = os.path.join(dirpath, fn)
            with open(path) as f:
                item = yaml.safe_load(f)
                item["path"] = dirpath
                config[item["name"]] = item
    return config


def main(prettypath, modpath, verify=False, verbose=False):
    config = load_items(modpath)
    for name, conf in config.items():
        # Generate footprint
        conf['name'] = name
        fp = footprint(conf)
        path = os.path.join(prettypath, name+".kicad_mod")

        if verify and verbose:
            print("Verifying", path)

        # Check if an identical part already exists
        if os.path.isfile(path):
            with open(path) as f:
                old = f.read()
            old = [n for n in sexp_parse(old) if n[0] != "tedit"]
            new = [n for n in sexp_parse(fp) if n[0] != "tedit"]
            if new == old:
                continue

        # If not, either verification failed or we should output the new fp
        if verify:
            return False
        else:
            with open(path, "w") as f:
                f.write(fp)

    # If we finished and didn't return yet, verification has succeeded.
    if verify:
        return True


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("prettypath", type=str, help=
                        "Path to footprints to process")
    parser.add_argument("modpath", type=str, help=
                        "Path to .yaml files defining footprints")
    parser.add_argument("--verify", action="store_true", help=
                        "Verify libraries are up to date")
    parser.add_argument("--verbose", action="store_true", help=
                        "Print out every library verified")
    args = vars(parser.parse_args())
    result = main(**args)
    if args['verify']:
        if result:
            print("OK: all footprints up-to-date.")
            sys.exit(0)
        else:
            print("Error: footprints not up-to-date.", file=sys.stderr)
            sys.exit(1)
