#!/usr/bin/env python3
"""dia2piel: las formas de Dia de Simusol como piel de netlistsvg.

    dia2piel                 escribe pieles/simusol.svg
    dia2piel -o otra.svg DIR...

Toma la piel analogica de netlistsvg (resistencias, capacitores, fuentes,
masa: lo que dibuja sin formas) y le agrega cada forma de Dia de los
directorios dados (por omision, los de dia-simusol y los del paquete de
Simusol: Termico, Electrico, Generico, Modelos). Cada forma pasa a un tipo
de celda con el nombre de la forma sin blancos ("Termico-acumulador"),
escalada para que lo mas grande mida 50 px, con un puerto p0, p1... por
cada punto de conexion y los rotulos del nombre y del valor a la derecha.
El trazo no se escala (vector-effect): queda del grosor de los demas.

Copyright (C) Diego Saravia <dsa@unsa.edu.ar>
GPL version 3 o posterior.
"""
import argparse
import glob
import os
import re
import sys
import xml.etree.ElementTree as ET

AQUI = os.path.dirname(os.path.realpath(__file__))
SIRU = os.path.normpath(os.path.join(AQUI, ".."))
DIRS = [os.path.join(SIRU, "dia-simusol", "NOLOCALdia", "share", "dia", "shapes"),
        os.path.join(SIRU, "pack", "simusol", "tar", "share", "data", "shapes")]
GRUPOS = ("Termico", "Electrico", "Generico", "Modelos")
NS = {"d": "http://www.daa.com.au/~james/dia-shape-ns", "svg": "http://www.w3.org/2000/svg"}
SVGNS = "{http://www.w3.org/2000/svg}"
TAM = 50.0


def tipo_de(nombre):
    """'Termico - acumulador' -> 'Termico-acumulador'"""
    return re.sub(r"\s*-\s*", "-", nombre.strip()).replace(" ", "_")


def formas(dirs):
    """nombre -> archivo; si una forma esta en los dos juegos, la primera"""
    vistas = {}
    for d in dirs:
        for g in GRUPOS:
            for f in sorted(glob.glob(os.path.join(d, g, "*.shape"))):
                try:
                    r = ET.parse(f).getroot()
                except ET.ParseError as e:
                    print("dia2piel: %s: %s" % (f, e), file=sys.stderr)
                    continue
                n = r.findtext("d:name", namespaces=NS)
                if n and n not in vistas:
                    vistas[n] = (f, r)
    return vistas


def numero(x, defecto=0.0):
    try:
        return float(x)
    except (TypeError, ValueError):
        return defecto


def puntos_de_path(d):
    """los puntos (absolutos) de un path con M L H V C S Q T A Z, en
    mayusculas o minusculas: alcanza para el recuadro"""
    toks = re.findall(r"[MmLlHhVvCcSsQqTtAaZz]|-?\d*\.?\d+(?:[eE][-+]?\d+)?", d)
    pts, x, y, x0, y0, cmd, i = [], 0.0, 0.0, 0.0, 0.0, "M", 0
    pares = {"M": 2, "L": 2, "T": 2, "C": 6, "S": 4, "Q": 4, "A": 7}
    while i < len(toks):
        t = toks[i]
        if t.isalpha():
            cmd = t
            i += 1
            if cmd in "Zz":
                x, y = x0, y0
            continue
        rel = cmd.islower()
        c = cmd.upper()
        if c in "HV":
            v = float(t)
            i += 1
            if c == "H":
                x = x + v if rel else v
            else:
                y = y + v if rel else v
            pts.append((x, y))
            continue
        n = pares.get(c, 2)
        nums = [float(z) for z in toks[i:i + n]]
        i += n
        if len(nums) < n:
            break
        if c == "A":
            nums = nums[5:]
        bx, by = (x, y) if rel else (0.0, 0.0)
        for j in range(0, len(nums) - 1, 2):
            pts.append((bx + nums[j], by + nums[j + 1]))
        x, y = pts[-1]
        if c == "M":
            x0, y0 = x, y
            cmd = "l" if rel else "L"
    return pts


def recuadro(svg, conexiones):
    """el recuadro de lo que dibuja la forma y de sus conexiones (las formas
    de Dia no empiezan siempre en 0: las llaves van de x=19 a x=25)"""
    pts = list(conexiones)
    for e in svg.iter():
        n = e.tag.replace(SVGNS, "")
        a = {k.replace(SVGNS, ""): v for k, v in e.attrib.items()}
        if n == "line":
            pts += [(numero(a.get("x1")), numero(a.get("y1"))), (numero(a.get("x2")), numero(a.get("y2")))]
        elif n in ("circle", "ellipse"):
            cx, cy = numero(a.get("cx")), numero(a.get("cy"))
            rx, ry = numero(a.get("rx", a.get("r"))), numero(a.get("ry", a.get("r")))
            pts += [(cx - rx, cy - ry), (cx + rx, cy + ry)]
        elif n == "rect":
            x, y = numero(a.get("x")), numero(a.get("y"))
            pts += [(x, y), (x + numero(a.get("width")), y + numero(a.get("height")))]
        elif n in ("polyline", "polygon"):
            v = [float(z) for z in re.findall(r"-?\d*\.?\d+", a.get("points", ""))]
            pts += list(zip(v[0::2], v[1::2]))
        elif n == "path":
            pts += puntos_de_path(a.get("d", ""))
    if not pts:
        return 0.0, 0.0, numero(svg.get("width"), 10.0), numero(svg.get("height"), 10.0)
    xs, ys = [p[0] for p in pts], [p[1] for p in pts]
    return min(xs), min(ys), max(xs), max(ys)


def dibujo(svg, k, x0, y0):
    """los elementos de la forma, sin el prefijo svg: y sin textos, cada uno
    escalado por k (netlistsvg toma todo <g> de adentro de una celda como un
    puerto: no puede ir un grupo con la escala)"""
    partes = []
    for e in svg.iter():
        if e is svg:
            continue
        nombre = e.tag.replace(SVGNS, "")
        if nombre not in ("path", "line", "rect", "circle", "ellipse", "polyline", "polygon"):
            continue
        attrs = {}
        for at, v in e.attrib.items():
            at = at.replace(SVGNS, "")
            if at == "style":
                # sin el grosor: lo pone la piel; sin trazo si dice none
                v = ";".join(p for p in v.split(";") if p.strip() and not p.strip().startswith("stroke-width"))
                if not v:
                    continue
            attrs[at] = v
        attrs["vector-effect"] = "non-scaling-stroke"
        attrs["transform"] = "scale(%g) translate(%g,%g)" % (k, -x0, -y0)
        attrs["class"] = "symbol $cell_id"
        partes.append("<%s %s/>" % (nombre, " ".join('%s="%s"' % (k, v.replace('"', "&quot;")) for k, v in attrs.items())))
    return partes


def celda(nombre, raiz):
    svg = raiz.find("svg:svg", NS)
    if svg is None:
        return None
    puntos = [(numero(p.get("x")), numero(p.get("y"))) for p in raiz.findall("d:connections/d:point", NS)]
    x0, y0, x1, y1 = recuadro(svg, puntos)
    w, h = max(x1 - x0, 1e-6), max(y1 - y0, 1e-6)
    k = TAM / max(w, h)
    W, H = w * k, h * k
    tipo = tipo_de(nombre)
    # netlistsvg busca la celda por el alias, no por s:type
    s = ['<g s:type="%s" s:width="%g" s:height="%g">' % (tipo, W, H),
         '  <s:alias val="%s"/>' % tipo]
    # las masas (una conexion: 0 grados C, tierra) no llevan rotulos
    if not (len(puntos) == 1 and re.search(r"0gradosc|tierra|ground|Generico - 0$", nombre, re.I)):
        s += ['  <text x="%g" y="15" s:attribute="ref" class="$cell_id">X1</text>' % (W + 4),
              '  <text x="%g" y="30" s:attribute="value" class="$cell_id">X</text>' % (W + 4)]
    s += ["  " + p for p in dibujo(svg, k, x0, y0)]
    for i, (x, y) in enumerate(puntos):
        X, Y = (x - x0) * k, (y - y0) * k
        lados = {"top": Y, "bottom": H - Y, "left": X, "right": W - X}
        lado = min(lados, key=lados.get)
        s.append('  <g s:x="%g" s:y="%g" s:pid="p%d" s:position="%s"/>' % (X, Y, i, lado))
    s.append("</g>")
    return "\n".join(s)


def main():
    a = argparse.ArgumentParser(description="Las formas de Dia de Simusol como piel de netlistsvg.")
    a.add_argument("dirs", nargs="*", default=DIRS)
    a.add_argument("-o", "--salida", default=os.path.join(AQUI, "pieles", "simusol.svg"))
    a.add_argument("--base", default=os.path.join(AQUI, "node_modules", "netlistsvg", "lib", "analog.svg"),
                   help="la piel a la que se agregan las formas (la analogica de netlistsvg)")
    o = a.parse_args()
    with open(o.base, encoding="utf-8") as f:
        base = f.read()
    celdas = []
    vistas = formas(o.dirs)
    for nombre in sorted(vistas):
        c = celda(nombre, vistas[nombre][1])
        if c:
            celdas.append("<!-- %s (%s) -->\n%s" % (nombre, os.path.relpath(vistas[nombre][0], SIRU), c))
    fin = base.rindex("</svg>")
    texto = (base[:fin] + "\n<!-- las formas de Dia de Simusol (dia2piel) -->\n" +
             "\n\n".join(celdas) + "\n</svg>\n")
    with open(o.salida, "w", encoding="utf-8") as f:
        f.write(texto)
    print("%s: %d formas" % (o.salida, len(celdas)))


if __name__ == "__main__":
    main()
