# -*- mode: python ; coding: utf-8 -*-
import os
from pathlib import Path
from PyInstaller.utils.hooks import collect_all


datas = {{ datas }}
binaries = {{ binaries }}

hiddenimports = {{ hiddenimports }}

{% for value in collect_all %}tmp_ret = collect_all('{{ value }}')
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
{% endfor %}

# Add dynamic libs in the venv bin/Script Path. This is needed because we might copy some additional libs
# e.q.: OpenSSL 1.1.1l in that directory with a separate:
# `conan install openssl@1.1.1l -g deploy && cp openssl/bin/*.so cura_inst/bin`
binaries.extend([(str(bin), ".") for bin in Path(r"{{ venv_script_path }}").glob("*.so*")])
binaries.extend([(str(bin), ".") for bin in Path(r"{{ venv_script_path }}").glob("*.dll")])
binaries.extend([(str(bin), ".") for bin in Path(r"{{ venv_script_path }}").glob("*.dylib")])

block_cipher = None

a = Analysis(
    [{{ entrypoint }}],
    pathex=[],
    binaries=binaries,
    datas=datas,
    hiddenimports=hiddenimports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name=r'{{ name }}',
    debug=False,
    bootloader_ignore_signals=False,
    strip={{ strip }},
    upx={{ upx }},
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch={{ target_arch }},
    codesign_identity=os.getenv('CODESIGN_IDENTITY', None),
    entitlements_file={{ entitlements_file }},
    icon={{ icon }}
)

coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name=r'{{ name }}'
)

{% if macos == true %}
# PyInstaller seems to copy everything in the resource folder for the MacOS, this causes issues with codesigning and notarizing
# The folder structure should adhere to the one specified in Table 2-5
# https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1
# The class below is basically ducktyping the BUNDLE class of PyInstaller and using our own `assemble` method for more fine-grain and specific
# control. Some code of the method below is copied from:
# https://github.com/pyinstaller/pyinstaller/blob/22d1d2a5378228744cc95f14904dae1664df32c4/PyInstaller/building/osx.py#L115
#-----------------------------------------------------------------------------
# Copyright (c) 2005-2022, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------

import plistlib
import shutil
import PyInstaller.utils.osx as osxutils
from pathlib import Path
from PyInstaller.building.osx import BUNDLE
from PyInstaller.building.utils import (_check_path_overlap, _rmtree, add_suffix_to_extension, checkCache)
from PyInstaller.building.datastruct import logger
from PyInstaller.building.icon import normalize_icon_type


class UMBUNDLE(BUNDLE):
    def assemble(self):
        from PyInstaller.config import CONF

        if _check_path_overlap(self.name) and os.path.isdir(self.name):
            _rmtree(self.name)
        logger.info("Building BUNDLE %s", self.tocbasename)

        # Create a minimal Mac bundle structure.
        macos_path = Path(self.name, "Contents", "MacOS")
        resources_path = Path(self.name, "Contents", "Resources")
        frameworks_path = Path(self.name, "Contents", "Frameworks")
        os.makedirs(macos_path)
        os.makedirs(resources_path)
        os.makedirs(frameworks_path)

        # Makes sure the icon exists and attempts to convert to the proper format if applicable
        self.icon = normalize_icon_type(self.icon, ("icns",), "icns", CONF["workpath"])

        # Ensure icon path is absolute
        self.icon = os.path.abspath(self.icon)

        # Copy icns icon to Resources directory.
        shutil.copy(self.icon, os.path.join(self.name, 'Contents', 'Resources'))

        # Key/values for a minimal Info.plist file
        info_plist_dict = {
            "CFBundleDisplayName": self.appname,
            "CFBundleName": self.appname,

            # Required by 'codesign' utility.
            # The value for CFBundleIdentifier is used as the default unique name of your program for Code Signing
            # purposes. It even identifies the APP for access to restricted OS X areas like Keychain.
            #
            # The identifier used for signing must be globally unique. The usual form for this identifier is a
            # hierarchical name in reverse DNS notation, starting with the toplevel domain, followed by the company
            # name, followed by the department within the company, and ending with the product name. Usually in the
            # form: com.mycompany.department.appname
            # CLI option --osx-bundle-identifier sets this value.
            "CFBundleIdentifier": self.bundle_identifier,
            "CFBundleExecutable": os.path.basename(self.exename),
            "CFBundleIconFile": os.path.basename(self.icon),
            "CFBundleInfoDictionaryVersion": "6.0",
            "CFBundlePackageType": "APPL",
            "CFBundleVersionString": self.version,
            "CFBundleShortVersionString": self.version,
        }

        # Set some default values. But they still can be overwritten by the user.
        if self.console:
            # Setting EXE console=True implies LSBackgroundOnly=True.
            info_plist_dict['LSBackgroundOnly'] = True
        else:
            # Let's use high resolution by default.
            info_plist_dict['NSHighResolutionCapable'] = True

        # Merge info_plist settings from spec file
        if isinstance(self.info_plist, dict) and self.info_plist:
            info_plist_dict.update(self.info_plist)

        plist_filename = os.path.join(self.name, "Contents", "Info.plist")
        with open(plist_filename, "wb") as plist_fh:
            plistlib.dump(info_plist_dict, plist_fh)

        links = []
        _QT_BASE_PATH = {'PySide2', 'PySide6', 'PyQt6', 'PyQt6', 'PySide6'}
        for inm, fnm, typ in self.toc:
            # Adjust name for extensions, if applicable
            inm, fnm, typ = add_suffix_to_extension(inm, fnm, typ)
            inm = Path(inm)
            fnm = Path(fnm)
            # Copy files from cache. This ensures that are used files with relative paths to dynamic library
            # dependencies (@executable_path)
            if typ in ('EXTENSION', 'BINARY') or (typ == 'DATA' and inm.suffix == '.so'):
                if any(['.' in p for p in inm.parent.parts]):
                    inm = Path(inm.name)
                fnm = Path(checkCache(
                    str(fnm),
                    strip = self.strip,
                    upx = self.upx,
                    upx_exclude = self.upx_exclude,
                    dist_nm = str(inm),
                    target_arch = self.target_arch,
                    codesign_identity = self.codesign_identity,
                    entitlements_file = self.entitlements_file,
                    strict_arch_validation = (typ == 'EXTENSION'),
                ))
                frame_dst = frameworks_path.joinpath(inm)
                if not frame_dst.exists():
                    if frame_dst.is_dir():
                        os.makedirs(frame_dst, exist_ok = True)
                    else:
                        os.makedirs(frame_dst.parent, exist_ok = True)
                shutil.copy(fnm, frame_dst, follow_symlinks = True)
                macos_dst = macos_path.joinpath(inm)
                if not macos_dst.exists():
                    if macos_dst.is_dir():
                        os.makedirs(macos_dst, exist_ok = True)
                    else:
                        os.makedirs(macos_dst.parent, exist_ok = True)

                    # Create relative symlink to the framework
                    symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Frameworks").joinpath(
                        frame_dst.relative_to(frameworks_path))
                    try:
                        macos_dst.symlink_to(symlink_to)
                    except FileExistsError:
                        pass
            else:
                if typ == 'DATA':
                    if any(['.' in p for p in inm.parent.parts]) or inm.suffix == '.so':
                        # Skip info dist egg and some not needed folders in tcl and tk, since they all contain dots in their files
                        logger.warning(f"Skipping DATA file {inm}")
                        continue
                    res_dst = resources_path.joinpath(inm)
                    if not res_dst.exists():
                        if res_dst.is_dir():
                            os.makedirs(res_dst, exist_ok = True)
                        else:
                            os.makedirs(res_dst.parent, exist_ok = True)
                    shutil.copy(fnm, res_dst, follow_symlinks = True)
                    macos_dst = macos_path.joinpath(inm)
                    if not macos_dst.exists():
                        if macos_dst.is_dir():
                            os.makedirs(macos_dst, exist_ok = True)
                        else:
                            os.makedirs(macos_dst.parent, exist_ok = True)

                        # Create relative symlink to the resource
                        symlink_to = Path(*[".." for p in macos_dst.relative_to(macos_path).parts], "Resources").joinpath(
                            res_dst.relative_to(resources_path))
                        try:
                            macos_dst.symlink_to(symlink_to)
                        except FileExistsError:
                            pass
                else:
                    macos_dst = macos_path.joinpath(inm)
                    if not macos_dst.exists():
                        if macos_dst.is_dir():
                            os.makedirs(macos_dst, exist_ok = True)
                        else:
                            os.makedirs(macos_dst.parent, exist_ok = True)
                        shutil.copy(fnm, macos_dst, follow_symlinks = True)

        # Sign the bundle
        logger.info('Signing the BUNDLE...')
        try:
            osxutils.sign_binary(self.name, self.codesign_identity, self.entitlements_file, deep = True)
        except Exception as e:
            logger.warning(f"Error while signing the bundle: {e}")
            logger.warning("You will need to sign the bundle manually!")

        logger.info(f"Building BUNDLE {self.tocbasename} completed successfully.")

app = UMBUNDLE(
    coll,
    name='{{ display_name }}.app',
    icon={{ icon }},
    bundle_identifier={{ osx_bundle_identifier }} + "_" + '{{ display_name }}'.replace(" ", "_") + "_" {{ short_version }},
    version={{ version }},
    info_plist={
        'CFBundleDisplayName': '{{ display_name }}',
        'NSPrincipalClass': 'NSApplication',
        'CFBundleDevelopmentRegion': 'English',
        'CFBundleExecutable': '{{ name }}',
        'CFBundleInfoDictionaryVersion': '6.0',
        'CFBundlePackageType': 'APPL',
        'CFBundleVersionString': {{ version }},
        'CFBundleShortVersionString': {{ short_version }},
        'CFBundleDocumentTypes': [{
                'CFBundleTypeRole': 'Viewer',
                'CFBundleTypeExtensions': ['*'],
                'CFBundleTypeName': 'Model Files',
            }]
        },
){% endif %}
