import glob
import platform
import sys
import os
import subprocess
import tempfile

from engine.FileLib import *
from engine.RegistryLib import *
from engine.SystemLib import *
from engine.FileSystemLib import *

def ogGetArch():
    if len(sys.argv) > 1 and sys.argv[1] == "help":
        ogHelp(sys.argv[0], sys.argv[0], sys.argv[0] + "  =>  x86_64")
        return

    if platform.machine().endswith("64"):
        print("x86_64")
    else:
        print("i386")

def ogGetOsType():
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) > 1 and sys.argv[1] == "help":
        ogHelp(sys.argv[0], sys.argv[0] + " int_ndisk int_npartition", sys.argv[0] + " 1 2  =>  Linux")
        return
    ogGetOsVersion(sys.argv[1:]).split(':')[0]

def ogGetOsUuid():
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) > 1 and sys.argv[1] == "help":
        ogHelp(sys.argv[0], sys.argv[0] + " int_ndisk int_nfilesys", sys.argv[0] + " 1 2  =>  540e47c6-8e78-4178-aa46-042e4803fb16")
        return

    # Error si no se reciben 2 parametros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Montar la particion, si no lo estaba previamente.
    MNTDIR = ogMount(sys.argv[1], sys.argv[2])
    if not MNTDIR:
        return

    # Obtener UUID según el tipo de sistema operativo.
    os_type = ogGetOsType(sys.argv[1], sys.argv[2])
    if os_type == "Linux":
        # Leer el UUID del sistema de ficheros raíz o el fichero de identificador.
        uuid = subprocess.check_output(["findmnt", "-no", "UUID", MNTDIR], stderr=subprocess.DEVNULL).decode().strip() or open(os.path.join(MNTDIR, "etc", "machine-id")).read().strip()
        print(uuid)
    elif os_type == "Windows":
        # Leer identificador en clave de registro.
        uuid = ogGetRegistryValue(MNTDIR, "SOFTWARE", "\\Microsoft\\Cryptography\\MachineGuid")
        print(uuid)
        
def ogGetSerialNumber():
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) > 1 and sys.argv[1] == "help":
        ogHelp(sys.argv[0], sys.argv[0], sys.argv[0] + "  =>  123456")
        return

    # Obtener nº de serie (ignorar los no especificados).
    SERIALNO = subprocess.check_output(["dmidecode", "-s", "system-serial-number"]).decode().strip()
    SERIALNO = re.sub(r"(not specified|to be filled|invalid entry|default string)", "", SERIALNO, flags=re.IGNORECASE)
    SERIALNO = SERIALNO.replace(" ", "")
    SERIALNO = SERIALNO[:25] if len(SERIALNO) > 25 else SERIALNO
    if SERIALNO:
        print(SERIALNO)

    return 0

def ogIsEfiActive():
    return os.path.isdir("/sys/firmware/efi")

def ogListHardwareInfo():
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) > 1 and sys.argv[1] == "help":
        ogHelp(sys.argv[0], sys.argv[0])
        return

    # Recopilación de dispositivos procesando la salida de \c lshw
    ogEcho("info", MSG_HARDWAREINVENTORY)
    output = subprocess.check_output('echo "cha=$(dmidecode -s chassis-type)" | grep -v "Other"', shell=True).decode().strip()
    print(output)
    if os.path.isdir("/sys/firmware/efi"):
        print("boo=UEFI")
    else:
        print("boo=BIOS")
    subprocess.call(["lshw", "|", "awk", "'BEGIN {type=\"mod\";}",
                        "/product:/ {sub(/ *product: */,\"\", prod=$0);}",
                        "/vendor:/  {sub(/ *vendor: */,\"\", vend=$0);}",
                        "/version:/ {sub(/ *version: */,\"v.\", vers=$0);}",
                        "/size:/    {size=$2;}",
                        "/clock:/   {clock=$2;}",
                        "/slot:/    {sub(/ *slot: */,\"\", slot=$0);}",
                        "/\\*-/      {if (type==\"mem\"){",
                        "if (size!=\"\"){",
                        "numbank++;",
                        "print type\"=\"vend,prod,size,clock\" (\"slot\")\";}",
                        "}else{",
                        "if (type==\"totalmem\"){",
                        "if (size!=\"\"){",
                        "totalmemory=\"mem=\"size;}",
                        "}else{",
                        "if (type!=\"\" && prod!=\"\"){",
                        "if (prod==\"v.\"vers)",
                        "vers=\"\";",
                        "print type\"=\"vend,prod,size,vers;}}}",
                        "type=prod=vend=vers=size=clock=slot=\"\";}",
                        "$1~/-core/    {type=\"boa\";}",
                        "$1~/-firmware/ {type=\"bio\";}",
                        "$1~/-cpu/     {type=\"cpu\";}",
                        "$1~/-bank/    {type=\"mem\";}",
                        "$1~/-memory/  {type=\"totalmem\";}",
                        "$1~/-ide/     {type=\"ide\";}",
                        "$1~/-storage/ {type=\"sto\";}",
                        "$1~/-disk/    {type=\"dis\";}",
                        "$1~/-cdrom/   {type=\"cdr\";}",
                        "$1~/-display/ {type=\"vga\";}",
                        "$1~/-network/ {type=\"net\";}",
                        "$1~/-multimedia/ {type=\"mul\";}",
                        "$1~/-usb/     {type=\"usb\";}",
                        "$1~/-firewire/ {type=\"fir\";}",
                        "$1~/-serial/  {type=\"bus\";}",
                        "END           {if (type!=\"\" && prod!=\"\")",
                        "print type\"=\"vend,prod,size,vers;",
                        "if (length(numbank)==0 && length(totalmemory)>=4)",
                        "print totalmemory; }'",
                        ])
    # */ (comentario para Doxygen)

def ogListSoftware():
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) > 1 and sys.argv[1] == "help":
        ogHelp(sys.argv[0], sys.argv[0] + " 1 1")
        return

    # Error si no se reciben 2 parametros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener tipo de sistema de archivos y montarlo.
    MNTDIR = ogMount(sys.argv[1], sys.argv[2])
    if not MNTDIR:
        return
    TYPE = ogGetOsType(sys.argv[1], sys.argv[2])
    if not TYPE:
        return

    # Ficheros temporales.
    APPS = tempfile.mktemp()
    TMPFILE = tempfile.mktemp()
    try:
        with open(APPS, "w") as apps_file:
            if TYPE == "Linux":
                # Procesar paquetes dpkg.
                PKGDIR = os.path.join(MNTDIR, "var", "lib", "dpkg")
                if os.path.exists(PKGDIR):
                    # Proceso de fichero en sistemas de 64 bits.
                    subprocess.call(["awk", "/Package:/ {if (pack!=\"\") print pack,vers;\
                                                sub(/-dev$/,\"\",$2);\
                                                pack=$2}\
                                        /Version:/ {sub(/^.*:/,\"\",$2); sub(/-.*$/,\"\",$2);\
                                                    vers=$2}\
                                        /Status:/  {if ($2!=\"install\") pack=vers=\"\"}\
                                        END        {if (pack!=\"\") print pack,vers}", os.path.join(PKGDIR, "status")], stdout=apps_file)
                
                # Procesar paquetes RPM.
                PKGDIR = os.path.join(MNTDIR, "var", "lib", "rpm")
                if os.path.exists(PKGDIR):
                    # Listar si está instalado el paquete "rpm" en el cliente.
                    if shutil.which("rpm"):
                        subprocess.call(["rm", "-f", os.path.join(PKGDIR, "__db.*")])
                        subprocess.call(["rpm", "--dbpath", PKGDIR, "-qa", "--qf", "%{NAME} %{VERSION}\n"], stdout=apps_file, stderr=subprocess.DEVNULL)
                        subprocess.call(["rm", "-f", os.path.join(PKGDIR, "__db.*")])
                    else:
                        # Obtener el nombre de cada paquete en la BD de RPM.
                        subprocess.call(["python", "-c", f"import re; import bsddb; db=bsddb.hashopen('{os.path.join(PKGDIR, 'Name')}','r');\
                                                            for k in db.keys():\
                                                                print(re.sub('-devel$','',k));"], stdout=apps_file)
                
                # Procesar paquetes pacman.
                PKGDIR = os.path.join(MNTDIR, "var", "lib", "pacman", "local")
                if os.path.exists(PKGDIR):
                    subprocess.call(["ls", PKGDIR], stdout=subprocess.PIPE)
                    subprocess.call(["awk", "-F-", "/-/ {print gensub(/-/, \" \", NF-2);}", os.path.join(PKGDIR, "*")], stdout=apps_file)
                
                # Procesar aplicaciones Snappy.
                PKGDIR = os.path.join(MNTDIR, "snap")
                subprocess.call(["find", PKGDIR, "-name", "snap.yaml", "-exec", "awk", "/name:/ {pack=$2}\
                                                                                        /version:/ {vers=$2}\
                                                                                        END {if (pack!=\"\") print pack,\"(snap)\",vers}", "{}", "+", "2>/dev/null"], stdout=apps_file)
                
                # Procesar aplicaciones Flatpak.
                PKGDIR = os.path.join(MNTDIR, "var", "lib", "flatpak")
                subprocess.call(["ls", "-1", os.path.join(PKGDIR, "app", "*", "current", "active", "deploy")], stderr=subprocess.DEVNULL)
                subprocess.call(["python", "-c", "import sys; for f in sys.stdin:\
                                                    p = open(f.strip()).read().split('\0');\
                                                    try:\
                                                        if(p[0] != 'flathub'):\
                                                            raise ValueError;\
                                                        print('{} (flatpak) {}'.format(p[p.index('appdata-name') + 4], p[p.index('appdata-version') + 1]));\
                                                    except ValueError:\
                                                        pass;"], stdout=apps_file)
                
            elif TYPE == "Windows":
                # Comprobar tipo de proceso del registro de Windows.
                if shutil.which("hivexregedit"):
                    # Nuevo proceso más rápido basado en "hivexregedit".
                    HIVE = ogGetHivePath(MNTDIR, "software")
                    if HIVE:
                        # Claves de registro para programas instalados.
                        subprocess.call(["hivexregedit", "--unsafe-printable-strings", "--export", HIVE, "\\Microsoft\\Windows\\CurrentVersion\\Uninstall"], stdout=TMPFILE, stderr=subprocess.DEVNULL)
                        subprocess.call(["hivexregedit", "--unsafe-printable-strings", "--export", HIVE, "\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"], stdout=TMPFILE, stderr=subprocess.DEVNULL)
                        # Mostrar los valores "DisplayName" y "DisplayVersion" para cada clave.
                        subprocess.call(["awk", "-F\"", "$1~/^\\[/ {n=\"\"}\
                                                            $2~/DisplayName/ {n=$4}\
                                                            $2~/DisplayVersion/ {print n,$4}", TMPFILE], stdout=apps_file)
                else:
                    # Compatibilidad con clientes ogLive antiguos.
                    # Claves de registro para programas instalados: formato "{clave}".
                    KEYS = ogListRegistryKeys(MNTDIR, "software", "\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
                    KEYS32 = ogListRegistryKeys(MNTDIR, "software", "\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
                    # Mostrar los valores "DisplayName" y "DisplayVersion" para cada clave.
                    for k in KEYS:
                        PROG = ogGetRegistryValue(MNTDIR, "software", f"\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{k}\\DisplayName")
                        if PROG:
                            VERS = ogGetRegistryValue(MNTDIR, "software", f"\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{k}\\DisplayVersion")
                            apps_file.write(f"{PROG} {VERS}\n")
                    for k in KEYS32:
                        PROG = ogGetRegistryValue(MNTDIR, "software", f"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{k}\\DisplayName")
                        if PROG:
                            VERS = ogGetRegistryValue(MNTDIR, "software", f"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{k}\\DisplayVersion")
                            apps_file.write(f"{PROG} {VERS}\n")
                
            elif TYPE == "MacOS":
                # Listar directorios de aplicaciones e intentar obtener la versión del fichero .plist (tanto original como descomprimido).
                for root, dirs, files in os.walk(os.path.join(MNTDIR, "Applications")):
                    for dir in dirs:
                        if dir.endswith(".app"):
                            file = os.path.join(root, dir, "Contents", "version.plist")
                            if not os.path.exists(file):
                                file = os.path.join(root, dir, "Contents", "version.plist.uncompress")
                            if os.path.exists(file):
                                with open(file, "r") as plist_file:
                                    plist_data = plist_file.read()
                                    version = re.search("<key>ShortVersionString</key>\s*<string>(.*?)</string>", plist_data)
                                    if version:
                                        apps_file.write(f"{os.path.basename(dir)}.app {version.group(1)}\n")
                
            elif TYPE == "BSD":
                subprocess.call(["sqlite3", os.path.join(MNTDIR, "var", "db", "pkg", "local.sqlite")], input="SELECT name FROM pkg_search;", stdout=apps_file, stderr=subprocess.DEVNULL)
        
        # Mostrar sistema Operativo y aplicaciones.
        ogGetOsVersion(sys.argv[1], sys.argv[2]).split(":")[1].strip()
        with open(APPS, "r") as apps_file:
            apps = apps_file.readlines()
            apps = sorted(set(apps))
            for app in apps:
                print(app.strip())
    finally:
        os.remove(APPS)
        os.remove(TMPFILE)

def ogGetOsVersion(ndisk, nfilesys):
    # Variables locales.
    MNTDIR = ogMount(ndisk, nfilesys)
    TYPE = ""
    VERSION = ""
    IS64BIT = ""

    # Si se solicita, mostrar ayuda.
    if len(sys.argv) > 1 and sys.argv[1] == "help":
        ogHelp(sys.argv[0], sys.argv[0] + " int_ndisk int_nfilesys", sys.argv[0] + " 1 2  =>  Linux:Ubuntu precise (12.04 LTS) 64 bits")
        return

    # Error si no se reciben 2 parametros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Montar la particion, si no lo estaba previamente.
    MNTDIR = ogMount(sys.argv[1], sys.argv[2])
    if not MNTDIR:
        return

    # Buscar tipo de sistema operativo.
    # Para GNU/Linux: leer descripción.
    TYPE = "Linux"
    FILE = os.path.join(MNTDIR, "etc", "os-release")
    if os.path.exists(FILE):
        with open(FILE, "r") as f:
            for line in f:
                if line.startswith("PRETTY_NAME"):
                    VERSION = line.split("=")[1].strip().strip('"')
                    break

    # Si no se puede obtener, buscar en ficheros del sistema.
    if not VERSION:
        FILE = os.path.join(MNTDIR, "etc", "lsb-release")
        if os.path.exists(FILE):
            with open(FILE, "r") as f:
                for line in f:
                    if line.startswith("DESCRIPTION"):
                        VERSION = line.split("=")[1].strip().strip('"')
                        break
        for DISTRIB in ["redhat", "SuSE", "mandrake", "gentoo"]:
            FILE = os.path.join(MNTDIR, "etc", f"{DISTRIB}-release")
            if os.path.exists(FILE):
                with open(FILE, "r") as f:
                    VERSION = f.readline().strip()
                    break
        FILE = os.path.join(MNTDIR, "etc", "arch-release")
        if os.path.exists(FILE):
            VERSION = "Arch Linux"
        FILE = os.path.join(MNTDIR, "etc", "slackware-version")
        if os.path.exists(FILE):
            with open(FILE, "r") as f:
                VERSION = f.read().strip()

    # Si no se encuentra, intentar ejecutar "lsb_release".
    if not VERSION:
        try:
            output = subprocess.check_output(["chroot", MNTDIR, "lsb_release", "-d"], stderr=subprocess.DEVNULL).decode().strip()
            VERSION = output.split(":")[1].strip()
        except subprocess.CalledProcessError:
            pass

    # Comprobar Linux de 64 bits.
    if VERSION and os.path.exists(os.path.join(MNTDIR, "lib64")):
        IS64BIT = "64 bits"

    # Para Android, leer fichero de propiedades.
    if not VERSION:
        TYPE = "Android"
        FILE = os.path.join(MNTDIR, "android*", "system", "build.prop")
        if glob.glob(FILE):
            with open(glob.glob(FILE)[0], "r") as f:
                lines = f.readlines()
                brand = ""
                release = ""
                for line in lines:
                    if line.startswith("product.brand"):
                        brand = line.split("=")[1].strip()
                    elif line.startswith("build.version.release"):
                        release = line.split("=")[1].strip()
                VERSION = f"Android {brand} {release}"
                if os.path.exists(os.path.join(MNTDIR, "lib64")):
                    IS64BIT = "64 bits"

    # Para GNU/Hurd, comprobar fichero de inicio (basado en os-prober).
    if not VERSION:
        TYPE = "Hurd"
        FILE = os.path.join(MNTDIR, "hurd", "init")
        if os.path.exists(FILE):
            VERSION = "GNU/Hurd"

    # Para Windows: leer la version del registro.
    if not VERSION:
        TYPE = "Windows"
        FILE = ogGetHivePath(MNTDIR, "SOFTWARE")
        if FILE:
            try:
                output = subprocess.check_output(["hivexsh", "load", FILE, "cd", "\\Microsoft\\Windows NT\\CurrentVersion", "lsval", "ProductName", "lsval", "DisplayVersion"], stderr=subprocess.DEVNULL).decode().strip()
                lines = output.split("\n")
                if len(lines) == 2:
                    VERSION = lines[1].strip()
                    if ogGetRegistryValue(MNTDIR, "SOFTWARE", "\\Microsoft\\Windows\\CurrentVersion\\ProgramW6432Dir"):
                        IS64BIT = "64 bits"
            except subprocess.CalledProcessError:
                pass

    # Para cargador Windows: buscar versión en fichero BCD (basado en os-prober).
    if not VERSION:
        TYPE = "WinLoader"
        FILE = ogGetPath(MNTDIR, "boot", "bcd")
        if not FILE:
            FILE = ogGetPath(MNTDIR, "EFI", "Microsoft", "boot", "bcd")
        if FILE:
            for DISTRIB in ["Windows Recovery", "Windows Boot"]:
                with open(FILE, "rb") as f:
                    if re.search(DISTRIB.encode(), f.read()):
                        VERSION = f"{DISTRIB} loader"
                        break

    # Para macOS: detectar kernel y completar con fichero plist de información del sistema.
    if not VERSION:
        TYPE = "MacOS"
        FILE = os.path.join(MNTDIR, "mach_kernel")
        if os.path.exists(FILE) and not subprocess.check_output(["file", "-b", FILE]).decode().strip().startswith("text"):
            if subprocess.check_output(["file", "-b", FILE]).decode().strip().startswith("Mach-O"):
                VERSION = "macOS"
            if subprocess.check_output(["file", "-b", FILE]).decode().strip().startswith("Mach-O 64-bit"):
                IS64BIT = "64 bits"
            FILE = os.path.join(MNTDIR, "System", "Library", "CoreServices", "SystemVersion.plist")
            if os.path.exists(FILE):
                with open(FILE, "r") as f:
                    plist_data = f.read()
                    product_name = re.search("<key>ProductName</key>\s*<string>(.*?)</string>", plist_data)
                    product_version = re.search("<key>ProductVersion</key>\s*<string>(.*?)</string>", plist_data)
                    if product_name and product_version:
                        VERSION = f"{product_name.group(1)} {product_version.group(1)}"
            FILE = os.path.join(MNTDIR, "com.apple.recovery.boot")
            if os.path.exists(FILE) and VERSION:
                VERSION = f"{VERSION} recovery"

    # Para FreeBSD: obtener datos del Kernel.
    if not VERSION:
        TYPE = "BSD"
        FILE = os.path.join(MNTDIR, "boot", "kernel", "kernel")
        if os.path.exists(FILE):
            output = subprocess.check_output(["strings", FILE]).decode().strip()
            match = re.search(r"@.*RELEASE", output)
            if match:
                VERSION = match.group().split("@")[0].strip()
            if subprocess.check_output(["file", "-b", FILE]).decode().strip().endswith("x86-64"):
                IS64BIT = "64 bits"

    # Para Solaris: leer el fichero de versión.
    if not VERSION:
        TYPE = "Solaris"
        FILE = os.path.join(MNTDIR, "etc", "release")
        if os.path.exists(FILE):
            with open(FILE, "r") as f:
                VERSION = f.readline().strip()

    # Para cargador GRUB, comprobar fichero de configuración.
    if not VERSION:
        TYPE = "GrubLoader"
        for FILE in [os.path.join(MNTDIR, "grub", "menu.lst"), os.path.join(MNTDIR, "boot", "grub", "menu.lst")]:
            if os.path.exists(FILE):
                VERSION = "GRUB Loader"
                break
        for FILE in glob.glob(os.path.join(MNTDIR, "{,boot/}{grub{,2},EFI/*}/grub.cfg")):
            if os.path.exists(FILE):
                VERSION = "GRUB2 Loader"
                break

    # Mostrar resultado y salir sin errores.
    if VERSION:
        print(f"{TYPE}:{VERSION} {IS64BIT}")

    return 0