#!/usr/bin/env python3
"""
Genera una QSL por cada QSO de un fichero ADIF.

Por cada contacto añade, debajo de la QSL original, una tabla con
indicativo, fecha, hora (UTC) y frecuencia, y guarda el resultado como
<INDICATIVO>-ea5kaf.jpeg

Uso:
    python generar_qsl.py qsos.adi mi_qsl.jpg
    python generar_qsl.py qsos.adi mi_qsl.jpg -o nombre_carpeta_salida --sufijo ea5kaf

Requisitos:
    pip install pillow
"""
import argparse
import os
import re
import sys

from PIL import Image, ImageDraw, ImageFont

# ---------------------------------------------------------------- ADIF ----
FIELD_RE = re.compile(r"<([A-Za-z0-9_]+):(\d+)(?::[A-Za-z])?>", re.IGNORECASE)


def parse_adif(texto):
    """Devuelve una lista de diccionarios (uno por QSO) con campos en mayúsculas."""
    # Descartar la cabecera si existe
    m = re.search(r"<EOH>", texto, re.IGNORECASE)
    if m:
        texto = texto[m.end():]

    qsos = []
    for bloque in re.split(r"<EOR>", texto, flags=re.IGNORECASE):
        campos = {}
        pos = 0
        while True:
            m = FIELD_RE.search(bloque, pos)
            if not m:
                break
            nombre = m.group(1).upper()
            longitud = int(m.group(2))
            valor = bloque[m.end(): m.end() + longitud]
            campos[nombre] = valor.strip()
            pos = m.end() + longitud
        if "CALL" in campos:
            qsos.append(campos)
    return qsos


def formatea_fecha(f):
    return f"{f[6:8]}/{f[4:6]}/{f[0:4]}" if len(f) == 8 else f


def formatea_hora(h):
    if len(h) >= 4:
        return f"{h[0:2]}:{h[2:4]} UTC"
    return h


def formatea_freq(qso):
    freq = qso.get("FREQ", "")
    if freq:
        try:
            return f"{float(freq):.3f} MHz"
        except ValueError:
            return freq
    return qso.get("BAND", "")


# ------------------------------------------------------------- DIBUJO -----
def carga_fuente(tam, negrita=False):
    candidatas = [
        "DejaVuSans-Bold.ttf" if negrita else "DejaVuSans.ttf",
        "arialbd.ttf" if negrita else "arial.ttf",
        "Arial Bold.ttf" if negrita else "Arial.ttf",
        "/usr/share/fonts/truetype/dejavu/"
        + ("DejaVuSans-Bold.ttf" if negrita else "DejaVuSans.ttf"),
    ]
    for c in candidatas:
        try:
            return ImageFont.truetype(c, tam)
        except OSError:
            continue
    return ImageFont.load_default()


def crea_qsl(base, qso):
    """Devuelve una imagen nueva: QSL original + tabla con los datos del QSO."""
    ancho, alto = base.size

    cabeceras = ["Indicativo", "Fecha", "Hora", "Frecuencia"]
    valores = [
        qso.get("CALL", "").upper(),
        formatea_fecha(qso.get("QSO_DATE", "")),
        formatea_hora(qso.get("TIME_ON", "")),
        formatea_freq(qso),
    ]

    # Tamaños proporcionales al ancho de la QSL
    margen = int(ancho * 0.03)
    tam_fuente = max(14, int(ancho * 0.028))
    f_cab = carga_fuente(tam_fuente, negrita=True)
    f_val = carga_fuente(tam_fuente)
    alto_fila = int(tam_fuente * 2.1)
    alto_tabla = alto_fila * 2

    lienzo = Image.new("RGB", (ancho, alto + alto_tabla + margen * 2), "white")
    lienzo.paste(base.convert("RGB"), (0, 0))
    d = ImageDraw.Draw(lienzo)

    x0, x1 = margen, ancho - margen
    y0 = alto + margen
    col_ancho = (x1 - x0) / len(cabeceras)

    # Fondo de la cabecera
    d.rectangle([x0, y0, x1, y0 + alto_fila], fill=(30, 60, 110))

    for i, (cab, val) in enumerate(zip(cabeceras, valores)):
        cx = x0 + col_ancho * i + col_ancho / 2
        d.text((cx, y0 + alto_fila / 2), cab, font=f_cab,
               fill="white", anchor="mm")
        d.text((cx, y0 + alto_fila * 1.5), val, font=f_val,
               fill="black", anchor="mm")

    # Rejilla
    color = (30, 60, 110)
    for fila in range(3):
        y = y0 + alto_fila * fila
        d.line([(x0, y), (x1, y)], fill=color, width=2)
    for i in range(len(cabeceras) + 1):
        x = x0 + col_ancho * i
        d.line([(x, y0), (x, y0 + alto_tabla)], fill=color, width=2)

    return lienzo


def nombre_seguro(indicativo):
    """EA1ABC/P -> EA1ABC-P (las barras no valen en nombres de fichero)."""
    return re.sub(r"[^A-Za-z0-9\-]", "-", indicativo.upper())


# --------------------------------------------------------------- MAIN -----
def main():
    ap = argparse.ArgumentParser(description="Genera QSLs desde un fichero ADIF")
    ap.add_argument("adif", help="fichero de texto con los QSO en formato ADIF")
    ap.add_argument("qsl", help="imagen de la QSL original")
    ap.add_argument("-o", "--salida", default=".", help="carpeta de salida")
    ap.add_argument("--sufijo", default="ea5kaf",
                    help="sufijo del nombre (por defecto: ea5kaf)")
    args = ap.parse_args()

    with open(args.adif, "r", encoding="utf-8", errors="replace") as f:
        qsos = parse_adif(f.read())

    if not qsos:
        sys.exit("No se ha encontrado ningún QSO en el fichero ADIF.")

    os.makedirs(args.salida, exist_ok=True)
    base = Image.open(args.qsl)
    usados = {}

    for qso in qsos:
        nombre = nombre_seguro(qso["CALL"])
        # Si un indicativo aparece varias veces, no sobrescribir
        usados[nombre] = usados.get(nombre, 0) + 1
        extra = "" if usados[nombre] == 1 else f"_{usados[nombre]}"
        ruta = os.path.join(args.salida, f"{nombre}{extra}-{args.sufijo}.jpeg")

        crea_qsl(base, qso).save(ruta, "JPEG", quality=95)
        print(f"Generada: {ruta}")

    print(f"\n{len(qsos)} QSL generadas.")


if __name__ == "__main__":
    main()
