#-----------------------------------------------------------------------------
# Copyright (c) 2005-2020, 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 hook for PyGObject's "gi.repository.GdkPixbuf" package.
"""

import glob
import os
import subprocess

from PyInstaller.config import CONF
from PyInstaller.compat import (
    exec_command_stdout, is_darwin, is_win, is_linux, open_file, which)
from PyInstaller.utils.hooks import (
    collect_glib_translations, get_gi_typelibs, get_gi_libdir, logger)

loaders_path = os.path.join('gdk-pixbuf-2.0', '2.10.0', 'loaders')

destpath = "lib/gdk-pixbuf-2.0/2.10.0/loaders"
cachedest = "lib/gdk-pixbuf-2.0/2.10.0"

# If the "gdk-pixbuf-query-loaders" command is not in the current ${PATH}, or
# is not in the GI lib path, GDK and thus GdkPixbuf is unavailable. Return with
# a non-fatal warning.
gdk_pixbuf_query_loaders = None

try:
    libdir = get_gi_libdir('GdkPixbuf', '2.0')
except ValueError:
    logger.warning(
        '"hook-gi.repository.GdkPixbuf" ignored, '
        'since GdkPixbuf library not found'
    )
    libdir = None

if libdir:

    # Distributions either package gdk-pixbuf-query-loaders in the GI libs
    # directory (not on the path), or on the path with or without a -x64 suffix
    # depending on the architecture
    cmds = [
        os.path.join(libdir, 'gdk-pixbuf-2.0/gdk-pixbuf-query-loaders'),
        'gdk-pixbuf-query-loaders-64',
        'gdk-pixbuf-query-loaders',
    ]

    for cmd in cmds:
        gdk_pixbuf_query_loaders = which(cmd)
        if gdk_pixbuf_query_loaders is not None:
            break

    if gdk_pixbuf_query_loaders is None:
        logger.warning(
            '"hook-gi.repository.GdkPixbuf" ignored, since '
            '"gdk-pixbuf-query-loaders" is not in $PATH or gi lib dir.'
        )

    # Else, GDK is available. Let's do this.
    else:
        binaries, datas, hiddenimports = get_gi_typelibs('GdkPixbuf', '2.0')
        datas += collect_glib_translations('gdk-pixbuf')

        # To add support for a new platform, add a new "elif" branch below with
        # the proper is_<platform>() test and glob for finding loaders on that
        # platform.
        if is_win:
            ext = "*.dll"
        elif is_darwin or is_linux:
            ext = "*.so"

        # If loader detection is supported on this platform, bundle all
        # detected loaders and an updated loader cache.
        if ext:
            loader_libs = []

            # Bundle all found loaders with this user application.
            pattern = os.path.join(libdir, loaders_path, ext)
            for f in glob.glob(pattern):
                binaries.append((f, destpath))
                loader_libs.append(f)

            # Sometimes the loaders are stored in a different directory from
            # the library (msys2)
            if not loader_libs:
                pattern = os.path.join(libdir, '..', 'lib', loaders_path, ext)
                for f in glob.glob(pattern):
                    binaries.append((f, destpath))
                    loader_libs.append(f)

            # Filename of the loader cache to be written below.
            cachefile = os.path.join(CONF['workpath'], 'loaders.cache')

            # Run the "gdk-pixbuf-query-loaders" command and capture its
            # standard output providing an updated loader cache; then write
            # this output to the loader cache bundled with this frozen
            # application.
            #
            # On OSX we use @executable_path to specify a path relative to the
            # generated bundle. However, on non-Windows we need to rewrite the
            # loader cache because it isn't relocatable by default. See
            # https://bugzilla.gnome.org/show_bug.cgi?id=737523
            #
            # To make it easier to rewrite, we just always write
            # @executable_path, since its significantly easier to find/replace
            # at runtime. :)
            #
            # If we need to rewrite it...
            if not is_win:
                # To permit string munging, decode the encoded bytes output by
                # this command (i.e., enable the "universal_newlines" option).
                # Note that:
                #
                # * Under Python 2.7, "cachedata" will be a decoded "unicode"
                # object. * Under Python 3.x, "cachedata" will be a decoded
                # "str" object.
                #
                # On Fedora, the default loaders cache is /usr/lib64, but the
                # libdir is actually /lib64. To get around this, we pass the
                # path to the loader command, and it will create a cache with
                # the right path.
                cachedata = exec_command_stdout(gdk_pixbuf_query_loaders,
                                                *loader_libs)

                cd = []
                prefix = '"' + os.path.join(libdir, 'gdk-pixbuf-2.0', '2.10.0')
                plen = len(prefix)

                # For each line in the updated loader cache...
                for line in cachedata.splitlines():
                    if line.startswith('#'):
                        continue
                    if line.startswith(prefix):
                        line = '"@executable_path/' + cachedest + line[plen:]
                    cd.append(line)

                # Rejoin these lines in a manner preserving this object's
                # "unicode" type under Python 2.
                cachedata = u'\n'.join(cd)

                # Write the updated loader cache to this file.
                with open_file(cachefile, 'w') as fp:
                    fp.write(cachedata)
            # Else, GdkPixbuf will do the right thing on Windows, so no changes
            # to the loader cache are required. For efficiency and reliability,
            # this command's encoded byte output is written as is without being
            # decoded.
            else:
                with open_file(cachefile, 'wb') as fp:
                    fp.write(subprocess.check_output(gdk_pixbuf_query_loaders))

            # Bundle this loader cache with this frozen application.
            datas.append((cachefile, cachedest))
        # Else, loader detection is unsupported on this platform.
        else:
            logger.warning(
                'GdkPixbuf loader bundling unsupported on your platform.'
            )
