"""Gallium Nine helper module"""
import os
import shutil

from lutris.util import system
from lutris.runners.commands.wine import wineexec
from lutris.util.wine.cabinstall import CabInstaller


class NineUnavailable(RuntimeError):
    """Exception raised when Gallium Nine is not available"""


class NineManager:
    """Utility class to install and manage Gallium Nine to a Wine prefix"""

    nine_files = ("d3d9-nine.dll", "ninewinecfg.exe")
    mesa_files = ("d3dadapter9.so.1",)

    def __init__(self, prefix, arch):
        self.prefix = prefix
        self.wine_arch = arch

    @staticmethod
    def nine_is_supported():
        """Check if MESA is built with Gallium Nine state tracker support

        basic check for presence of d3dadapter9 library in 'd3d' subdirectory
        of system library directory
        """
        for mesa_file in NineManager.mesa_files:
            if not any(
                [
                    os.path.exists(os.path.join(lib, "d3d", mesa_file))
                    for lib in system.LINUX_SYSTEM.iter_lib_folders()
                ]
            ):
                return False

        return True

    @staticmethod
    def nine_is_installed():
        """Check if Gallium Nine standalone is installed on this system

        check 'wine/fakedlls' subdirectory of system library directory for Nine binaries
        """
        for nine_file in NineManager.nine_files:
            if not any(
                [
                    os.path.exists(os.path.join(lib, "wine/fakedlls", nine_file))
                    for lib in system.LINUX_SYSTEM.iter_lib_folders()
                ]
            ):
                return False

        return True

    @staticmethod
    def is_available():
        """Check if Gallium Nine can be enabled on this system"""
        return NineManager.nine_is_supported() and NineManager.nine_is_installed()

    def get_system_path(self, arch):
        """Return path of Windows system directory with binaries of chosen architecture"""
        windows_path = os.path.join(self.prefix, "drive_c/windows")

        if self.wine_arch == "win32" and arch == "x32":
            return os.path.join(windows_path, "system32")
        if self.wine_arch == "win64" and arch == "x32":
            return os.path.join(windows_path, "syswow64")
        if self.wine_arch == "win64" and arch == "x64":
            return os.path.join(windows_path, "system32")

        return None

    def is_prefix_prepared(self):
        if not all(
            system.path_exists(os.path.join(self.get_system_path("x32"), nine_file))
            for nine_file in self.nine_files
        ):
            return False

        if self.wine_arch == "win64":
            if not all(
                system.path_exists(os.path.join(self.get_system_path("x64"), nine_file))
                for nine_file in self.nine_files
            ):
                return False

        return True

    def prepare_prefix(self):
        for nine_file in NineManager.nine_files:
            for lib in system.LINUX_SYSTEM.iter_lib_folders():
                nine_file_path = os.path.join(lib, "wine/fakedlls", nine_file)

                if (
                    os.path.exists(nine_file_path)
                    and CabInstaller.get_arch_from_dll(nine_file_path) == "win32"
                ):
                    shutil.copy(nine_file_path, self.get_system_path("x32"))

                if self.wine_arch == "win64":
                    if (
                        os.path.exists(nine_file_path)
                        and CabInstaller.get_arch_from_dll(nine_file_path) == "win64"
                    ):
                        shutil.copy(nine_file_path, self.get_system_path("x64"))

            if not os.path.exists(os.path.join(self.get_system_path("x32"), nine_file)):
                raise NineUnavailable("could not install " + nine_file + " (x32)")

            if self.wine_arch == "win64":
                if not os.path.exists(
                    os.path.join(self.get_system_path("x64"), nine_file)
                ):
                    raise NineUnavailable("could not install " + nine_file + " (x64)")

    def enable(self):
        if not self.nine_is_supported():
            raise NineUnavailable("Nine is not supported on this system")
        if not self.nine_is_installed():
            raise NineUnavailable("Nine Standalone is not installed")
        if not self.is_prefix_prepared():
            self.prepare_prefix()

        wineexec(
            "ninewinecfg", args="-e", prefix=self.prefix, blocking=True,
        )

    def disable(self):
        if self.is_prefix_prepared():
            wineexec(
                "ninewinecfg", args="-d", prefix=self.prefix, blocking=True,
            )
