#  -------------------------------
#  Difficult Rocket
#  Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com
#  All rights reserved
#  -------------------------------

import platform
import warnings
from pathlib import Path
from typing import List, Tuple, Optional, Union, Any
from enum import Enum

from lib_not_dr.types import Options, Version, VersionRequirement


def ensure_cmd_readable(cmd: str) -> str:
    """
    保证 参数中 不含空格
    :param cmd: 要格式化的命令行参数
    :return: 格式化后的命令行参数
    """
    if " " in str(cmd):
        return f'"{cmd}"'
    return cmd


def format_cmd(
    arg_name: Optional[str] = None,
    arg_value: Optional[Union[str, List[str]]] = None,
    write: Optional[Any] = True,
) -> List[str]:
    """
    用来格式化输出命令行参数
    :param arg_name: 类似 --show-memory 之类的主项
    :param arg_value: 类似 xxx 类的内容
    :param write: 是否写入
    :return: 直接拼接好的命令行参数 不带 =
    """
    if not write:
        return []
    if arg_name is None:
        return []
    if arg_value is None:
        return [arg_name]
    if isinstance(arg_value, list):
        arg_value = ",".join(
            [ensure_cmd_readable(value) for value in arg_value]
        )
        return [f"{arg_name}{arg_value}"]
    arg_value = ensure_cmd_readable(arg_value)
    return [f"{arg_name}{arg_value}"]


class NuitkaSubConfig(Options):
    """
    Nuitka 配置的子项
    Nuitka configuration sub-items
    """

    name = "Nuitka Sub Configuration"

    def gen_cmd(self) -> List[str]:
        """
        生成命令行参数
        :return:
        """
        raise NotImplementedError


class NuitkaPluginConfig(NuitkaSubConfig):
    """
    控制 nuitka 的 plugin 相关参数的部分
    Control part of nuitka's plugin related parameters
    """

    name = "Nuitka Plugin Configuration"

    # --enable-plugin=PLUGIN_NAME
    enable_plugin: List[str] = []
    # --disable-plugin=PLUGIN_NAME
    disable_plugin: List[str] = []
    # --plugin-no-detection
    plugin_no_detection: bool = False
    # --user-plugin=PATH
    user_plugin: List[Path] = []
    # --show-source-changes
    show_source_changes: bool = False

    # --include-plugin-directory=MODULE/PACKAGE
    include_plugin_dir: List[str] = []
    # --include-plugin-files=PATTERN
    include_plugin_files: List[str] = []

    def gen_cmd(self) -> List[str]:
        lst = []
        lst += format_cmd(
            "--enable-plugin=", self.enable_plugin, self.enable_plugin
        )
        lst += format_cmd(
            "--disable-plugin=", self.disable_plugin, self.disable_plugin
        )
        lst += format_cmd(
            "--plugin-no-detection" if self.plugin_no_detection else None
        )
        lst += format_cmd(
            "--user-plugin=",
            [str(plugin.absolute()) for plugin in self.user_plugin],
            self.user_plugin,
        )
        lst += format_cmd(
            "--show-source-changes" if self.show_source_changes else None
        )
        lst += format_cmd(
            "--include-plugin-directory=",
            self.include_plugin_dir,
            self.include_plugin_dir,
        )
        lst += format_cmd(
            "--include-plugin-files=",
            self.include_plugin_files,
            self.include_plugin_files,
        )
        return lst


class NuitkaIncludeConfig(NuitkaSubConfig):
    """
    控制 nuitka 的 include 和 数据 相关参数的部分
    Control part of nuitka's include related parameters
    """

    name = "Nuitka Include Configuration"

    # --include-package=PACKAGE
    include_packages: List[str] = []
    # --include-module=MODULE
    include_modules: List[str] = []

    # --prefer-source-code
    # --no-prefer-source-code    for --module
    prefer_source_code: bool = False
    # --follow-stdlib
    follow_stdlib: bool = False

    def gen_cmd(self) -> List[str]:
        lst = []
        lst += format_cmd(
            "--include-package=", self.include_packages, self.include_packages
        )
        lst += format_cmd(
            "--include-module=", self.include_modules, self.include_modules
        )
        lst += format_cmd(
            "--prefer-source-code" if self.prefer_source_code else None
        )
        lst += format_cmd(
            "--no-prefer-source-code" if not self.prefer_source_code else None
        )
        lst += format_cmd("--follow-stdlib" if self.follow_stdlib else None)
        return lst


class NuitkaDataConfig(NuitkaSubConfig):
    """
    控制 nuitka 的 数据 相关参数的部分
    Control part of nuitka's data related parameters
    """

    name = "Nuitka Data Configuration"

    # --include-package-data=PACKAGE=PACKAGE_PATH
    include_package_data: List[Tuple[Path, Path]] = []
    # --include-data-files=PATH=PATH
    include_data_files: List[Tuple[Path, Path]] = []
    # --include-data-dir=DIRECTORY=PATH
    include_data_dir: List[Tuple[Path, Path]] = []

    # --noinclude-data-files=PATH
    no_include_data_files: List[Path] = []

    # --list-package-data=LIST_PACKAGE_DATA
    list_package_data: List[str] = []
    # --list-package-dlls=LIST_PACKAGE_DLLS
    list_package_dlls: List[str] = []

    # --include-distribution-metadata=DISTRIBUTION
    include_distribution_metadata: List[str] = []


class NuitkaBinaryInfo(Options):
    """
    nuitka 构建的二进制文件的信息
    nuitka build binary file information
    """

    name = "Nuitka Binary Info"

    # --company-name=COMPANY_NAME
    company_name: Optional[str] = None
    # --product-name=PRODUCT_NAME
    product_name: Optional[str] = None

    # --file-version=FILE_VERSION
    # --macos-app-version=MACOS_APP_VERSION
    file_version: Optional[Union[str, Version]] = None
    # --product-version=PRODUCT_VERSION
    product_version: Optional[Union[str, Version]] = None

    # --file-description=FILE_DESCRIPTION
    file_description: Optional[str] = None
    # --copyright=COPYRIGHT_TEXT
    copyright: Optional[str] = None
    # --trademarks=TRADEMARK_TEXT
    trademarks: Optional[str] = None

    # Icon
    # --linux-icon=ICON_PATH
    # --macos-app-icon=ICON_PATH
    # --windows-icon-from-ico=ICON_PATH
    # --windows-icon-from-exe=ICON_EXE_PATH
    # 注意: 只有 Windows 下 才可以提供多个 ICO 文件
    # 其他平台 和 EXE 下只会使用第一个路径
    icon: Optional[List[Path]] = None

    # Console
    # --enable-console
    # --disable-console
    console: bool = True

    # Windows UAC
    # --windows-uac-admin
    windows_uac_admin: bool = False
    # --windows-uac-uiaccess
    windows_uac_ui_access: bool = False


class NuitkaOutputConfig(Options):
    """
    nuitka 构建的选项
    nuitka build output information
    """

    name = "Nuitka Output Config"

    # --output-dir=DIRECTORY
    output_dir: Optional[Path] = None
    # --output-filename=FILENAME
    output_filename: Optional[str] = None

    # --quiet
    quiet: bool = False
    # --no-progressbar
    no_progressbar: bool = False
    # --verbose
    verbose: bool = False
    # --verbose-output=PATH
    verbose_output: Optional[Path] = None

    # --show-progress
    show_progress: bool = False
    # --show-memory
    show_memory: bool = False
    # --show-scons
    show_scons: bool = False
    # --show-modules
    show_modules: bool = False
    # --show-modules-output=PATH
    show_modules_output: Optional[Path] = None

    # --xml=XML_FILENAME
    xml: Optional[Path] = None
    # --report=REPORT_FILENAME
    report: Optional[Path] = None
    # --report-diffable
    report_diffable: bool = False

    # --remove-output
    remove_output: bool = False
    # --no-pyo-file
    no_pyo_file: bool = False


class NuitkaDebugConfig(Options):
    """
    nuitka 构建的调试选项
    nuikta build debug information
    """

    name = "Nuitka Debug Config"

    # --debug
    debug: bool = False
    # --unstripped
    strip: bool = True
    # --profile
    profile: bool = False
    # --internal-graph
    internal_graph: bool = False
    # --trace-execution
    trace_execution: bool = False
    # --recompile-c-only
    recompile_c_only: bool = False
    # --generate-c-only
    generate_c_only: bool = False
    # --deployment
    deployment: bool = False
    # --no-deployment-flag=FLAG
    deployment_flag: Optional[str] = None
    # --experimental=FLAG
    experimental: Optional[str] = None


class NuitkaTarget(Enum):
    """
    用于指定 nuitka 构建的目标
    Use to specify the target of nuitka build
    exe: 不带任何参数
    module: --module
    standalone: --standalone
    one_file: --onefile
    """

    exe = ""
    module = "module"
    standalone = "standalone"
    one_file = "package"


class NuitkaScriptGenerator(Options):
    """
    用于帮助生成 nuitka 构建脚本的类
    Use to help generate nuitka build script

    :arg main 需要编译的文件
    """

    name = "Nuitka Script Generator"

    # --main=PATH
    # 可以有多个 输入时需要包在列表里
    main: List[Path]

    # --run
    run_after_build: bool = False
    # --debugger
    debugger: bool = False
    # --execute-with-pythonpath
    execute_with_python_path: bool = False

    # --assume-yes-for-downloads
    download_confirm: bool = True

    # standalone/one_file/module/exe
    target: NuitkaTarget = NuitkaTarget.exe

    # --python-debug
    python_debug: bool = False
    # --python-flag=FLAG
    python_flag: List[str] = []
    # --python-for-scons=PATH
    python_for_scons: Optional[Path] = None


class CompilerHelper(Options):
    """
    用于帮助生成 nuitka 构建脚本的类
    Use to help generate nuitka build script

    """

    name = "Nuitka Compiler Helper"

    output_path: Path = Path("./build")
    src_file: Path

    python_cmd: str = "python"
    compat_nuitka_version: VersionRequirement = VersionRequirement(
        "~1.8.0"
    )  # STATIC VERSION

    # 以下为 nuitka 的参数
    # nuitka options below
    use_lto: bool = False  # --lto=yes (no is faster)
    use_clang: bool = True  # --clang
    use_msvc: bool = True  # --msvc=latest
    use_mingw: bool = False  # --mingw64

    onefile: bool = False  # --onefile
    onefile_tempdir: Optional[str] = ""  # --onefile-tempdir-spec=
    standalone: bool = True  # --standalone
    use_ccache: bool = True  # not --disable-ccache
    enable_console: bool = True  # --enable-console / --disable-console

    show_progress: bool = True  # --show-progress
    show_memory: bool = False  # --show-memory
    remove_output: bool = True  # --remove-output
    save_xml: bool = False  # --xml
    xml_path: Path = Path("build/compile_data.xml")
    save_report: bool = False  # --report
    report_path: Path = Path("build/compile_report.xml")

    download_confirm: bool = True  # --assume-yes-for-download
    run_after_build: bool = False  # --run

    company_name: Optional[str] = ""
    product_name: Optional[str] = ""
    file_version: Optional[Version] = None
    product_version: Optional[Version] = None
    file_description: Optional[str] = ""  # --file-description

    copy_right: Optional[str] = ""  # --copyright

    icon_path: Optional[Path] = None

    follow_import: List[str] = []
    no_follow_import: List[str] = []

    include_data_dir: List[Tuple[str, str]] = []
    include_packages: List[str] = []

    enable_plugin: List[str] = []  # --enable-plugin=xxx,xxx
    disable_plugin: List[str] = []  # --disable-plugin=xxx,xxx

    def init(self, **kwargs) -> None:
        if (compat_version := kwargs.get("compat_nuitka_version")) is not None:
            if not self.compat_nuitka_version.accept(compat_version):
                warnings.warn(
                    f"Nuitka version may not compat with {compat_version}\n"
                    "requirement: {self.compat_nuitka_version}"
                )
        # 非 windows 平台不使用 msvc
        if platform.system() != "Windows":
            self.use_msvc = False
            self.use_mingw = False
        else:
            self.use_mingw = self.use_mingw and not self.use_msvc
            # Windows 平台下使用 msvc 时不使用 mingw

    def __str__(self):
        return self.as_markdown()

    def as_markdown(self, longest: Optional[int] = None) -> str:
        """
        输出编译器帮助信息
        Output compiler help information

        Args:
            longest (Optional[int], optional):
                输出信息的最大长度限制 The maximum length of output information.
                Defaults to None.

        Returns:
            str: 以 markdown 格式输出的编译器帮助信息
                Compile helper information in markdown format
        """
        front = super().as_markdown(longest)
        gen_cmd = self.gen_subprocess_cmd()
        return f"{front}\n\n```bash\n{' '.join(gen_cmd)}\n```"

    def gen_subprocess_cmd(self) -> List[str]:
        """生成 nuitka 构建脚本
        Generate nuitka build script

        Returns:
            List[str]:
                生成的 nuitka 构建脚本
                Generated nuitka build script
        """
        warnings.warn(
            "\033[33mlib_not_dr.nuitka.compile is deprecated "
            "use lib_not_dr.nuitka.reader.main or lndl-nuitka instead\033[0m",
            DeprecationWarning,
            stacklevel=2,
        )
        cmd_list = [self.python_cmd, "-m", "nuitka"]
        # macos 和 非 macos icon 参数不同
        if platform.system() == "Darwin":
            cmd_list += format_cmd(
                "--macos-app-version=",
                self.product_version,
                self.product_version,
            )
            if self.icon_path is not None:
                cmd_list += format_cmd(
                    "--macos-app-icon=", self.icon_path.absolute(), self.icon_path
                )
        elif platform.system() == "Windows" and self.icon_path is not None:
            cmd_list += format_cmd(
                "--windows-icon-from-ico=",
                self.icon_path.absolute(),
                self.icon_path,
            )
        elif platform.system() == "Linux" and self.icon_path is not None:
            cmd_list += format_cmd(
                "--linux-icon=", self.icon_path.absolute(), self.icon_path
            )

        cmd_list += format_cmd("--lto=", "yes" if self.use_lto else "no")
        cmd_list += format_cmd("--clang" if self.use_clang else None)
        cmd_list += format_cmd("--msvc=latest" if self.use_msvc else None)
        cmd_list += format_cmd("--mingw64" if self.use_mingw else None)
        cmd_list += format_cmd("--standalone" if self.standalone else None)
        cmd_list += format_cmd("--onefile" if self.onefile else None)
        cmd_list += format_cmd(
            "--onefile-tempdir-spec=",
            self.onefile_tempdir,
            self.onefile_tempdir,
        )

        cmd_list += format_cmd(
            "--disable-ccache" if not self.use_ccache else None
        )
        cmd_list += format_cmd(
            "--show-progress" if self.show_progress else None
        )
        cmd_list += format_cmd("--show-memory" if self.show_memory else None)
        cmd_list += format_cmd(
            "--remove-output" if self.remove_output else None
        )
        cmd_list += format_cmd(
            "--assume-yes-for-download" if self.download_confirm else None
        )
        cmd_list += format_cmd("--run" if self.run_after_build else None)
        cmd_list += format_cmd(
            "--enable-console" if self.enable_console else "--disable-console"
        )

        cmd_list += format_cmd(
            "--xml=", str(self.xml_path.absolute()), self.save_xml
        )
        cmd_list += format_cmd(
            "--report=", str(self.report_path.absolute()), self.save_report
        )
        cmd_list += format_cmd(
            "--output-dir=", str(self.output_path.absolute()), self.output_path
        )
        cmd_list += format_cmd(
            "--company-name=", self.company_name, self.company_name
        )
        cmd_list += format_cmd(
            "--product-name=", self.product_name, self.product_name
        )
        cmd_list += format_cmd(
            "--file-version=", str(self.file_version), self.file_version
        )
        cmd_list += format_cmd(
            "--product-version=",
            str(self.product_version),
            self.product_version,
        )
        cmd_list += format_cmd(
            "--file-description=", self.file_description, self.file_description
        )
        cmd_list += format_cmd("--copyright=", self.copy_right, self.copy_right)

        cmd_list += format_cmd(
            "--follow-import-to=", self.follow_import, self.follow_import
        )
        cmd_list += format_cmd(
            "--nofollow-import-to=",
            self.no_follow_import,
            self.no_follow_import,
        )
        cmd_list += format_cmd(
            "--enable-plugin=", self.enable_plugin, self.enable_plugin
        )
        cmd_list += format_cmd(
            "--disable-plugin=", self.disable_plugin, self.disable_plugin
        )

        if self.include_data_dir:
            cmd_list += [
                f"--include-data-dir={src}={dst}"
                for src, dst in self.include_data_dir
            ]
        if self.include_packages:
            cmd_list += [
                f"--include-package={package}"
                for package in self.include_packages
            ]

        cmd_list.append(f"--main={self.src_file}")
        return cmd_list
