"""A collection of functions which are triggered automatically by finder when
opencv-python package is included.
"""

from __future__ import annotations

import sys
from pathlib import Path
from textwrap import dedent
from typing import TYPE_CHECKING

from cx_Freeze._compat import IS_MACOS, IS_MINGW, IS_WINDOWS, PYTHON_VERSION
from cx_Freeze.module import Module, ModuleHook

if TYPE_CHECKING:
    from cx_Freeze.finder import ModuleFinder


__all__ = ["Hook"]


WARNING_NUMPY_VERSION = """WARNING:

    {cv2_name} {cv2_version} isn't compatible with numpy {numpy_version}.
    To fix this issue, the easiest solution will be to
    downgrade to 'numpy<2' or try to upgrade {cv2_name}.
"""


class Hook(ModuleHook):
    """The Hook class for opencv-python."""

    def cv2(self, finder: ModuleFinder, module: Module) -> None:
        """Versions of cv2 (opencv-python) above 4.5.3 may require additional
        configuration files.

        Additionally, on Linux the opencv_python.libs directory is not
        copied across for versions above 4.5.3.
        """
        source_dir = module.file.parent
        target_dir = Path("lib", "cv2")

        if module.distribution is None:
            module.update_distribution("opencv-python")
        if module.distribution is None:
            module.update_distribution("opencv-python-headless")

        if module.distribution:
            name = module.distribution.normalized_name
            version = module.distribution.version
            if version[:2] < (4, 10):
                m_numpy = finder.include_package("numpy")
                if (
                    m_numpy.distribution
                    and m_numpy.distribution.version[0] >= 2
                ):
                    msg = WARNING_NUMPY_VERSION.format(
                        cv2_name=module.distribution.name,
                        cv2_version=module.distribution.original.version,
                        numpy_version=m_numpy.distribution.original.version,
                    )
                    print(msg, file=sys.stderr)

            # cv2 4.9.0-4.10.0 conda-forge uses qt6-main
            if module.distribution.installer == "conda":
                if version[:2] >= (4, 9):
                    source = Path(sys.base_prefix, "lib/qt6/plugins/platforms")
                else:
                    source = Path(sys.base_prefix, "plugins/platforms")
                if source.is_dir():
                    finder.include_files(
                        source, target_dir / "plugins" / "platforms"
                    )
                source = Path(sys.base_prefix, "fonts")
                if source.is_dir():
                    finder.include_files(source, target_dir / "fonts")
                qt_conf: Path = finder.cache_path / "qt.conf"
                lines = ["[Paths]", f"Prefix = {target_dir.as_posix()}"]
                with qt_conf.open(
                    mode="w", encoding="utf_8", newline=""
                ) as file:
                    file.write("\n".join(lines))
                finder.include_files(qt_conf, qt_conf.name)
                if IS_MACOS:
                    finder.include_files(qt_conf, "Contents/Resources/qt.conf")
        else:
            name = "opencv_python"

        # msys2: cv2 4.6.0-4.9.0 is a extension module
        if IS_MINGW:
            finder.include_package("numpy")
            # msys2 files is on 'share' subdirectory
            source = Path(sys.base_prefix, "share/qt6/plugins/platforms")
            if source.is_dir():
                finder.include_files(
                    source, target_dir / "plugins" / "platforms"
                )
            source = Path(sys.base_prefix, "fonts")
            if not source.is_dir():
                source = Path(sys.base_prefix, "share/fonts")
            if source.is_dir():
                finder.include_files(source, target_dir / "fonts")
            qt_conf: Path = finder.cache_path / "qt.conf"
            lines = ["[Paths]", f"Prefix = {target_dir.as_posix()}"]
            with qt_conf.open(mode="w", encoding="utf_8", newline="") as file:
                file.write("\n".join(lines))
            finder.include_files(qt_conf, qt_conf.name)

        # conda-forge and msys2: cv2 4.6.0-4.9.0 is a extension module
        if module.path is None:
            return

        # Use optimized mode
        module.in_file_system = 2
        finder.exclude_module("cv2.config")
        finder.exclude_module("cv2.config-3")
        finder.exclude_module("cv2.load_config_py2")
        module.exclude_names.add("load_config_py2")
        finder.include_package("cv2")

        # Include config files and libraries
        # When opencv_contrib_python or opencv_contrib_python_headless is used
        # an optimization is possible, because cv2.abi3.so is changed and its
        # rpath points to it .libs directory
        source_config = finder.cache_path / "cv2-config.py"
        contrib = name.replace("opencv_python", "opencv_contrib_python")
        source_libs = source_dir.parent / f"{contrib}.libs"
        if not source_libs.exists():
            source_libs = source_dir.parent / f"{name}.libs"
        if source_libs.exists():
            # Linux wheels
            target_libs = f"lib/{source_libs.name}"
            for source in source_libs.iterdir():
                finder.lib_files[source] = f"{target_libs}/{source.name}"
            source_config.write_text(
                dedent(
                    f"""\
                    import os as _os
                    import sys as _sys
                    BINARIES_PATHS = [
                        _os.path.join(_sys.prefix, "{target_libs}")
                    ] + BINARIES_PATHS
                    """
                )
            )
        else:
            source_config.touch()

        # Include config-3 (Linux wheels) or create it for conda-forge
        finder.include_files(source_config, target_dir / "config.py")
        source_config = source_dir / "config-3.py"
        if not source_config.exists():
            # create config-3 for cv2 4.10.x conda-forge
            extension_dir = f"python-{PYTHON_VERSION}"
            finder.include_files(
                source_dir / extension_dir, target_dir / extension_dir
            )
            source_config = finder.cache_path / "cv2-config-3.py"
            source_config.touch()
            source_config.write_text(
                dedent(
                    f"""\
                    import os
                    PYTHON_EXTENSIONS_PATHS = [
                        os.path.join(LOADER_DIR, '{extension_dir}')
                    ] + PYTHON_EXTENSIONS_PATHS
                    """
                )
            )
        finder.include_files(source_config, target_dir / "config-3.py")

        # Include data files
        data_dir = source_dir / "data"
        if data_dir.exists():
            for path in data_dir.glob("*.xml"):
                finder.include_files(path, target_dir / "data" / path.name)

        # Copy all binary files
        if IS_WINDOWS:
            for path in source_dir.glob("*.dll"):
                finder.include_files(path, target_dir / path.name)
            return

        if IS_MACOS:
            libs_dir = source_dir / ".dylibs"
            if libs_dir.exists():
                finder.include_files(libs_dir, target_dir / ".dylibs")
            return

        # Qt files distributed in wheels for Linux
        qt_files = source_dir / "qt"
        if qt_files.exists():
            finder.include_files(qt_files, target_dir / "qt")
