#!/usr/bin/env python3
"""
svg_to_shp.py - Conversor de jailgames.svg a archivos .shp individuales
Extrae letras del SVG y las convierte al formato .shp del juego Orni Attack

Uso: python3 svg_to_shp.py <input.svg> <output_dir>
"""

import sys
import os
import re
import xml.etree.ElementTree as ET


def parse_transform_matrix(transform_str):
    """
    Parsea string "matrix(a,b,c,d,e,f)" → tupla (a,b,c,d,e,f)
    """
    match = re.search(r'matrix\(([\d\.\-,\s]+)\)', transform_str)
    if not match:
        return (1, 0, 0, 1, 0, 0)  # Identidad por defecto

    values = re.split(r'[,\s]+', match.group(1).strip())
    values = [float(v) for v in values if v]

    if len(values) == 6:
        return tuple(values)
    return (1, 0, 0, 1, 0, 0)


def apply_transform(punto, matrix):
    """
    Aplica transformación matricial 2D a un punto (x, y)
    matrix = (a, b, c, d, e, f)
    x' = a*x + c*y + e
    y' = b*x + d*y + f
    """
    x, y = punto
    a, b, c, d, e, f = matrix
    x_new = a * x + c * y + e
    y_new = b * x + d * y + f
    return (x_new, y_new)


def parse_svg_path(d_attr):
    """
    Convierte comandos SVG path (M y L) a lista de polylines separadas.
    Cada comando M inicia una nueva polyline.
    Retorna: lista de listas de puntos [[(x,y), ...], [(x,y), ...], ...]
    """
    # Reemplazar comas por espacios para facilitar parsing
    d_attr = d_attr.replace(',', ' ')

    # Split por comandos M y L
    # Añadir marcador antes de cada comando
    d_attr = re.sub(r'([ML])', r'|\1', d_attr)
    commands = [c.strip() for c in d_attr.split('|') if c.strip()]

    polylines = []  # Lista de polylines
    current_polyline = []  # Polyline actual

    for cmd in commands:
        if not cmd:
            continue

        # Extraer letra de comando y coordenadas
        cmd_letter = cmd[0]
        coords_str = cmd[1:].strip()

        if not coords_str:
            continue

        # Parsear pares de coordenadas
        coords = coords_str.split()

        # Si es comando M (MoveTo), empezar nueva polyline
        if cmd_letter == 'M':
            # Guardar polyline anterior si tiene puntos
            if current_polyline:
                polylines.append(current_polyline)
            current_polyline = []

        # Procesar en pares (x, y)
        i = 0
        while i < len(coords) - 1:
            try:
                x = float(coords[i])
                y = float(coords[i + 1])
                current_polyline.append((x, y))
                i += 2
            except (ValueError, IndexError):
                i += 1

    # No olvidar la última polyline
    if current_polyline:
        polylines.append(current_polyline)

    return polylines


def rect_to_points(rect_elem):
    """
    Convierte elemento <rect> a lista de puntos (rectángulo cerrado)
    """
    x = float(rect_elem.get('x', 0))
    y = float(rect_elem.get('y', 0))
    width = float(rect_elem.get('width', 0))
    height = float(rect_elem.get('height', 0))

    # Rectángulo cerrado: 5 puntos (último = primero)
    return [
        (x, y),
        (x + width, y),
        (x + width, y + height),
        (x, y + height),
        (x, y)  # Cerrar
    ]


def calc_bounding_box(polylines):
    """
    Calcula bounding box de una o varias polylines
    polylines puede ser:
      - lista de puntos [(x,y), ...]
      - lista de polylines [[(x,y), ...], [(x,y), ...]]
    Retorna: (min_x, max_x, min_y, max_y, ancho, alto)
    """
    # Aplanar si es lista de polylines
    puntos = []
    if polylines and isinstance(polylines[0], list):
        for polyline in polylines:
            puntos.extend(polyline)
    else:
        puntos = polylines

    if not puntos:
        return (0, 0, 0, 0, 0, 0)

    xs = [p[0] for p in puntos]
    ys = [p[1] for p in puntos]

    min_x = min(xs)
    max_x = max(xs)
    min_y = min(ys)
    max_y = max(ys)

    ancho = max_x - min_x
    alto = max_y - min_y

    return (min_x, max_x, min_y, max_y, ancho, alto)


def normalizar_letra(nombre, polylines, altura_objetivo=100.0):
    """
    Escala y traslada letra para que tenga altura_objetivo pixels
    y esté centrada en origen (0, 0) en esquina superior izquierda

    polylines: lista de polylines [[(x,y), ...], [(x,y), ...]]
    Retorna: dict con polylines normalizadas, centro, ancho, alto
    """
    if not polylines:
        return None

    min_x, max_x, min_y, max_y, ancho, alto = calc_bounding_box(polylines)

    if alto == 0:
        print(f"  [WARN] Letra {nombre}: altura cero")
        return None

    # Factor de escala basado en altura
    escala = altura_objetivo / alto

    # Normalizar cada polyline:
    # 1. Trasladar a origen (restar min_x, min_y)
    # 2. Aplicar escala
    # 3. Cerrar polyline (último punto = primer punto)
    polylines_norm = []
    total_puntos = 0

    for polyline in polylines:
        polyline_norm = []
        for x, y in polyline:
            x_norm = (x - min_x) * escala
            y_norm = (y - min_y) * escala
            polyline_norm.append((x_norm, y_norm))

        # Cerrar polyline si no está cerrada
        if polyline_norm and polyline_norm[0] != polyline_norm[-1]:
            polyline_norm.append(polyline_norm[0])

        polylines_norm.append(polyline_norm)
        total_puntos += len(polyline_norm)

    # Calcular dimensiones finales
    ancho_norm = ancho * escala
    alto_norm = alto * escala

    # Centro de la letra
    centro = (ancho_norm / 2.0, alto_norm / 2.0)

    return {
        'nombre': nombre,
        'polylines': polylines_norm,
        'centro': centro,
        'ancho': ancho_norm,
        'alto': alto_norm,
        'total_puntos': total_puntos
    }


def generar_shp(letra_norm, output_dir):
    """
    Genera archivo .shp con formato del juego Orni Attack

    Formato:
    name: letra_x
    scale: 1.0
    center: cx, cy
    polyline: x1,y1  x2,y2  x3,y3 ...
    polyline: x1,y1  x2,y2  x3,y3 ...  (si hay múltiples formas)
    """
    if not letra_norm:
        return

    nombre_archivo = f"letra_{letra_norm['nombre'].lower()}.shp"
    filepath = os.path.join(output_dir, nombre_archivo)

    with open(filepath, 'w', encoding='utf-8') as f:
        # Header con comentarios
        f.write(f"# {nombre_archivo}\n")
        f.write(f"# Generado automáticamente desde jailgames.svg\n")
        f.write(f"# Dimensiones: {letra_norm['ancho']:.2f} x {letra_norm['alto']:.2f} px\n")
        f.write(f"\n")

        # Metadatos
        f.write(f"name: letra_{letra_norm['nombre'].lower()}\n")
        f.write(f"scale: 1.0\n")
        f.write(f"center: {letra_norm['centro'][0]:.2f}, {letra_norm['centro'][1]:.2f}\n")
        f.write(f"\n")

        # Generar una línea polyline por cada forma
        for polyline in letra_norm['polylines']:
            puntos_str = "  ".join([f"{x:.2f},{y:.2f}" for x, y in polyline])
            f.write(f"polyline: {puntos_str}\n")

    print(f"  ✓ {nombre_archivo:20} ({len(letra_norm['polylines'])} formas, "
          f"{letra_norm['total_puntos']:3} puntos, "
          f"{letra_norm['ancho']:6.2f} x {letra_norm['alto']:6.2f} px)")


def parse_svg(filepath):
    """
    Parsea jailgames.svg y extrae las 9 letras con sus puntos transformados

    Retorna: lista de dicts con {nombre, puntos}
    """
    tree = ET.parse(filepath)
    root = tree.getroot()

    # Namespace SVG
    ns = {'svg': 'http://www.w3.org/2000/svg'}

    # Buscar grupo con transform
    groups = root.findall('.//svg:g[@transform]', ns)
    if not groups:
        print("[ERROR] No se encontró grupo con transform")
        return []

    group = groups[0]
    transform_str = group.get('transform', '')
    transform_matrix = parse_transform_matrix(transform_str)

    print(f"[INFO] Transform matrix: {transform_matrix}")

    # Extraer paths y rects (buscar recursivamente en todos los descendientes)
    paths = group.findall('.//svg:path', ns)
    rects = group.findall('.//svg:rect', ns)

    print(f"[INFO] Encontrados {len(paths)} paths y {len(rects)} rects")

    # Nombres de las letras para "ORNI ATTACK!"
    # Grupo 1 (top): O, R, N, I (3 paths + 1 rect)
    # Grupo 2 (bottom): A, T, T, A, C, K, ! (6 paths + 1 path para !)
    # Total: 9 paths + 1 rect
    # Asumiendo orden de aparición en SVG:
    nombres_paths = ['O', 'R', 'N', 'A', 'T', 'T', 'A', 'C', 'K', 'EXCLAMACION']

    letras = []

    # Procesar paths
    for i, path in enumerate(paths):
        if i >= len(nombres_paths):
            break

        d_attr = path.get('d')
        if not d_attr:
            continue

        # Parsear polylines del path (ahora retorna lista de polylines)
        polylines = parse_svg_path(d_attr)

        # Aplicar transformación a cada polyline
        polylines_transformed = []
        for polyline in polylines:
            polyline_transformed = [apply_transform(p, transform_matrix) for p in polyline]
            polylines_transformed.append(polyline_transformed)

        letras.append({
            'nombre': nombres_paths[i],
            'polylines': polylines_transformed
        })

    # Procesar rects (la letra I es un rect)
    for rect in rects:
        puntos = rect_to_points(rect)

        # Aplicar transformación
        puntos_transformed = [apply_transform(p, transform_matrix) for p in puntos]

        # Rect es una sola polyline
        letras.append({
            'nombre': 'I',
            'polylines': [puntos_transformed]
        })

    return letras


def main():
    if len(sys.argv) != 3:
        print("Uso: python3 svg_to_shp.py <input.svg> <output_dir>")
        print("Ejemplo: python3 svg_to_shp.py jailgames.svg data/shapes/")
        sys.exit(1)

    svg_path = sys.argv[1]
    output_dir = sys.argv[2]

    # Verificar que el SVG existe
    if not os.path.exists(svg_path):
        print(f"[ERROR] No se encuentra el archivo: {svg_path}")
        sys.exit(1)

    # Crear directorio de salida si no existe
    os.makedirs(output_dir, exist_ok=True)

    print(f"\n{'='*70}")
    print(f"  SVG → .shp Converter para Orni Attack")
    print(f"{'='*70}\n")
    print(f"Input:  {svg_path}")
    print(f"Output: {output_dir}/\n")

    # Parsear SVG
    print("[1/3] Parseando SVG...")
    letras = parse_svg(svg_path)

    if not letras:
        print("[ERROR] No se pudieron extraer letras del SVG")
        sys.exit(1)

    print(f"  ✓ Extraídas {len(letras)} letras\n")

    # Filtrar duplicados (solo una 'A')
    print("[2/3] Filtrando duplicados...")
    letras_unicas = {}
    for letra in letras:
        nombre = letra['nombre']
        if nombre not in letras_unicas:
            letras_unicas[nombre] = letra

    print(f"  ✓ {len(letras_unicas)} letras únicas: {', '.join(sorted(letras_unicas.keys()))}\n")

    # Normalizar y generar .shp
    print("[3/3] Generando archivos .shp (altura objetivo: 100px)...\n")

    for nombre in sorted(letras_unicas.keys()):
        letra = letras_unicas[nombre]
        letra_norm = normalizar_letra(nombre, letra['polylines'], altura_objetivo=100.0)

        if letra_norm:
            generar_shp(letra_norm, output_dir)

    print(f"\n{'='*70}")
    print(f"  ✓ Conversión completada: {len(letras_unicas)} archivos .shp generados")
    print(f"{'='*70}\n")


if __name__ == '__main__':
    main()
