# Script para copiar roms a partir de un xml de fbneo
# Copia las roms por desarrollador y sin clones

# Por hacer:
# pasar por parametro si se quieren clones

import os
import shutil
import sys
import argparse
import config
from xml.dom import minidom

def normalize_path(path):
    """Elimina los caracteres ilegales de la cadena de texto"""
    # Lista de caracteres ilegales que deben ser eliminados
    illegal_chars = ["<", ">", ":", '"', "/", "\\", "|", "?", "*"]
    # Carácter de reemplazo
    replace_with = "_"
    
    # Reemplaza cada carácter ilegal en la cadena de texto
    for char in illegal_chars:
        path = path.replace(char, replace_with)
    
    return path

def parse_arguments():
    """Parsea los argumentos de la línea de comandos"""
    parser = argparse.ArgumentParser(
        description="Gestiona las roms de fbneo por desarrolladores a partir de un fichero .dat"
    )
    
    # Grupo de argumentos mutuos exclusivos
    group = parser.add_mutually_exclusive_group()
    group.add_argument(
        "-l",
        "--list",
        help="Muestra la lista de desarrolladores o de juegos",
        action="store_true",
    )
    group.add_argument(
        "-c",
        "--copy",
        help="Copia las roms",
        action="store_true"
    )
    group.add_argument(
        "-f",
        "--find",
        help="Busca juegos que contengan la cadena de texto especificada",
    )
    
    # Otros argumentos
    parser.add_argument(
        "-s",
        "--sort",
        help="Ordena los resultados por carpetas de desarrollador al copiar todas las roms",
        action="store_true",
    )
    parser.add_argument(
        "-cf",
        "--create_folder",
        help="Copia las roms dentro de la carpeta del desarrollador",
        action="store_true",
    )
    parser.add_argument(
        "-m",
        "--manufacturer",
        help="Selecciona un desarrollador"
    )
    parser.add_argument(
        "-d",
        "--dat",
        help="Ruta del fichero .dat con información de las roms"
    )
    parser.add_argument(
        "-i",
        "--input",
        help="Ruta donde se encuentran las roms",
        default=""
    )
    parser.add_argument(
        "-o",
        "--output",
        help="Ruta donde depositar las roms",
        default=""
    )
    
    return parser.parse_args()

def load_dat_file(dat_path):
    """Carga y analiza el fichero .dat especificado"""
    if not dat_path:
        print("No se ha especificado un fichero .dat")
        if os.path.exists(config.XML_FILE):
            # Usa el fichero por defecto si no se especifica uno
            print(f"Usando el fichero por defecto: {config.XML_FILE}")
            dat_path = config.XML_FILE
        else:
            # Salir si no se encuentra el fichero por defecto
            sys.exit(2)
    elif not os.path.isfile(dat_path):
        # Salir si el fichero especificado no existe
        print(f"No se encuentra el fichero .dat especificado: {dat_path}")
        sys.exit(2)
    
    print(f"Analizando el fichero {dat_path}")
    return minidom.parse(dat_path)

def list_manufacturers(file):
    """Lista todos los desarrolladores presentes en el fichero .dat"""
    print("Lista de todos los desarrolladores:")
    
    manufacturers = []
    games = file.getElementsByTagName("game")
    
    # Recoge todos los desarrolladores de los juegos
    for game in games:
        manufacturer = game.getElementsByTagName("manufacturer")[0]
        manufacturers.append(manufacturer.firstChild.data)

    # Elimina los duplicados
    manufacturers = list(dict.fromkeys(manufacturers))
    # Ordena alfabéticamente la lista de desarrolladores
    manufacturers.sort()

    # Imprime cada desarrollador en una nueva línea
    for manufacturer in manufacturers:
        print(manufacturer)

def list_games_by_manufacturer(file, manufacturer):
    """Lista todos los juegos de un desarrollador específico"""
    games = file.getElementsByTagName("game")
    filtered_games = []

    # Filtra los juegos según el fabricante especificado y excluye clones y BIOS
    for game in games:
        cloneof = game.getAttribute("cloneof")
        isbios = game.getAttribute("isbios")
        game_manufacturer = game.getElementsByTagName("manufacturer")[0]
        description = game.getElementsByTagName("description")[0]
        if (
            (game_manufacturer.firstChild.data == manufacturer or manufacturer == 'all')
            and not cloneof
            and not isbios
        ):
            filtered_games.append((description.firstChild.data, game.getAttribute('name'), game_manufacturer.firstChild.data))

    # Encuentra el ancho máximo para cada columna
    max_desc_length = max(len("Descripción"), max(len(row[0]) for row in filtered_games))
    max_name_length = max(len("Nombre del archivo"), max(len(row[1] + '.zip') for row in filtered_games))
    max_manufacturer_length = max(len("Desarrollador"), max(len(row[2]) for row in filtered_games))

    # Imprime el encabezado de la tabla
    print(f"Lista de todos los juegos de {manufacturer}: {len(filtered_games)}")
    print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")
    print(f"| {'Descripción':<{max_desc_length}} | {'Nombre del archivo':<{max_name_length}} | {'Desarrollador':<{max_manufacturer_length}} |")
    print(f"|{'-'*(max_desc_length+2)}|{'-'*(max_name_length+2)}|{'-'*(max_manufacturer_length+2)}|")
    
    # Imprime cada juego en la tabla
    for description, name, game_manufacturer in filtered_games:
        print(f"| {description:<{max_desc_length}} | {name + '.zip':<{max_name_length}} | {game_manufacturer:<{max_manufacturer_length}} |")
    
    # Imprime el pie de la tabla
    print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")

def find_games(file, search_string):
    """Busca juegos que contengan la cadena de texto especificada"""
    games = file.getElementsByTagName("game")
    filtered_games = []

    # Filtra los juegos según la cadena de búsqueda especificada
    for game in games:
        description = game.getElementsByTagName("description")[0]
        if search_string.casefold() in description.firstChild.data.casefold():
            game_manufacturer = game.getElementsByTagName("manufacturer")[0]
            filtered_games.append((description.firstChild.data, game.getAttribute('name'), game_manufacturer.firstChild.data))

    if not filtered_games:
        print(f"No se encontraron juegos que contengan '{search_string}'")
        return

    # Encuentra el ancho máximo para cada columna
    max_desc_length = max(len("Descripción"), max(len(row[0]) for row in filtered_games))
    max_name_length = max(len("Nombre del archivo"), max(len(row[1] + '.zip') for row in filtered_games))
    max_manufacturer_length = max(len("Desarrollador"), max(len(row[2]) for row in filtered_games))

    # Imprime el encabezado de la tabla
    print(f"Juegos que contienen '{search_string}': {len(filtered_games)}")
    print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")
    print(f"| {'Descripción':<{max_desc_length}} | {'Nombre del archivo':<{max_name_length}} | {'Desarrollador':<{max_manufacturer_length}} |")
    print(f"|{'-'*(max_desc_length+2)}|{'-'*(max_name_length+2)}|{'-'*(max_manufacturer_length+2)}|")
    
    # Imprime cada juego en la tabla
    for description, name, game_manufacturer in filtered_games:
        print(f"| {description:<{max_desc_length}} | {name + '.zip':<{max_name_length}} | {game_manufacturer:<{max_manufacturer_length}} |")
    
    # Imprime el pie de la tabla
    print(f"{'='*(max_desc_length+max_name_length+max_manufacturer_length+10)}")


def copy_games(file, args, all_games=False):
    """Copia los juegos según los criterios especificados"""
    if args.create_folder and not all_games:
        # Normaliza el nombre del desarrollador para usarlo como nombre de carpeta
        normalized_manufacturer = normalize_path(args.manufacturer)
        os.mkdir(os.path.join(args.output, args.manufacturer))
    
    # Listas para almacenar los juegos no encontrados, ignorados y copiados
    notfound = []
    ignored_games = []
    copied_games = []
    
    games = file.getElementsByTagName("game")
    
    if all_games:
        print(f"Copiando todos los juegos: {len(games)}")
    else:
        print(f"Copiando todos los juegos de {args.manufacturer}: {len(games)}")
    
    for game in games:
        isignored = False
        name = game.getAttribute("name") + ".zip"
        cloneof = game.getAttribute("cloneof")
        isbios = game.getAttribute("isbios")
        romof = game.getAttribute("romof")
        manufacturer = game.getElementsByTagName("manufacturer")[0]
        description = game.getElementsByTagName("description")[0]
        driver = game.getElementsByTagName("driver")
        status = (
            game.getElementsByTagName("driver")[0].getAttribute("status")
            if driver
            else ""
        )

        # Verifica si el juego cumple con los criterios para ser copiado
        if (all_games or manufacturer.firstChild.data == args.manufacturer) and not cloneof and not isbios and status == "good":
            # Verifica si el juego debe ser ignorado
            for ignore_element in config.IGNORE_LIST:
                if ignore_element.casefold() in description.firstChild.data.casefold():
                    ignored_games.append(description.firstChild.data)
                    isignored = True

            for ignore_element in config.IGNORE_SYSTEM:
                if ignore_element.casefold() in romof.casefold():
                    ignored_games.append(description.firstChild.data)
                    isignored = True

            if not isignored:
                # Define la ruta de origen y destino del archivo
                src = os.path.join(args.input, name)
                if args.sort:
                    x = manufacturer.firstChild.data
                    x = x.replace(r"/", r"-")
                    dst = os.path.join(args.output, x, name)
                    if not os.path.isdir(os.path.join(args.output, x)):
                        os.mkdir(os.path.join(args.output, x))
                else:
                    if args.create_folder and not all_games:
                        dst = os.path.join(args.output, normalized_manufacturer, name)
                    else:
                        dst = os.path.join(args.output, name)

                # Copia el archivo si existe en la ruta de origen
                if os.path.isfile(src):
                    shutil.copyfile(src, dst)
                    copied_games.append(description.firstChild.data)
                    print(description.firstChild.data)
                else:
                    notfound.append(description.firstChild.data)

    # Imprime los resultados de los juegos no encontrados, ignorados y copiados
    print(f"\nJuegos faltantes: {len(notfound)}")
    for game in notfound:
        print(game)

    print(f"\nJuegos ignorados: {len(ignored_games)}")
    for game in ignored_games:
        print(game)

    print(f"\nJuegos copiados: {len(copied_games)}")
    print(f"Juegos faltantes: {len(notfound)}")
    print(f"Juegos ignorados: {len(ignored_games)}")

def main():
    """Función principal que coordina la ejecución del script"""
    # Parsear los argumentos de la línea de comandos
    args = parse_arguments()
    # Cargar y analizar el fichero .dat
    file = load_dat_file(dat_path=args.dat)
    
    # Manejar la opción de listar desarrolladores o juegos
    if args.list:
        if args.manufacturer:
            list_games_by_manufacturer(file=file, manufacturer=args.manufacturer)
        else:
            list_manufacturers(file=file)
    
    # Manejar la opción de copiar roms
    if args.copy:
        if args.manufacturer:
            copy_games(file=file, args=args, all_games=False)
        else:
            copy_games(file=file, args=args, all_games=True)
    
    # Manejar la opción de búsqueda de juegos
    if args.find:
        find_games(file=file, search_string=args.find)

if __name__ == "__main__":
    main()




