# ----------------------------------------------------------------------------
# Copyright (c) 2005-2017, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License with exception
# for distributing bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
# ----------------------------------------------------------------------------
import os
import sys

from ..hooks import eval_statement, exec_statement, get_homebrew_path
from ... import log as logging
from ...compat import exec_command, is_py3, is_win
from ...utils import misc

logger = logging.getLogger(__name__)


def qt_plugins_dir(namespace):
    """
    Return list of paths searched for plugins.

    :param namespace: Import namespace, i.e., PyQt4, PyQt5, PySide, or PySide2

    :return: Plugin directory paths
    """
    if namespace not in ['PyQt4', 'PyQt5', 'PySide', 'PySide2']:
        raise Exception('Invalid namespace: {0}'.format(namespace))
    paths = eval_statement("""
        from {0}.QtCore import QCoreApplication;
        app = QCoreApplication([]);
        # For Python 2 print would give <PyQt4.QtCore.QStringList
        # object at 0x....>", so we need to convert each element separately
        str = getattr(__builtins__, 'unicode', str);  # for Python 2
        print([str(p) for p in app.libraryPaths()])
        """.format(namespace))
    if not paths:
        raise Exception('Cannot find {0} plugin directories'.format(namespace))
    else:
        valid_paths = []
        for path in paths:
            if os.path.isdir(path):
                valid_paths.append(str(path))  # must be 8-bit chars for one-file builds
        qt_plugin_paths = valid_paths
    if not qt_plugin_paths:
        raise Exception("""
            Cannot find existing {0} plugin directories
            Paths checked: {1}
            """.format(namespace, ", ".join(paths)))
    return qt_plugin_paths


def qt_plugins_binaries(plugin_type, namespace):
    """
    Return list of dynamic libraries formatted for mod.binaries.

    :param plugin_type: Plugin to look for
    :param namespace: Import namespace, i.e., PyQt4, PyQt5, PySide, or PySide2

    :return: Plugin directory path corresponding to the given plugin_type
    """
    if namespace not in ['PyQt4', 'PyQt5', 'PySide', 'PySide2']:
        raise Exception('Invalid namespace: {0}'.format(namespace))
    pdir = qt_plugins_dir(namespace=namespace)
    files = []
    for path in pdir:
        files.extend(misc.dlls_in_dir(os.path.join(path, plugin_type)))

    # Windows:
    #
    # dlls_in_dir() grabs all files ending with *.dll, *.so and *.dylib in a certain
    # directory. On Windows this would grab debug copies of Qt plugins, which then
    # causes PyInstaller to add a dependency on the Debug CRT __in addition__ to the
    # release CRT.
    #
    # Since on Windows debug copies of Qt4 plugins end with "d4.dll" and Qt5 plugins
    # end with "d.dll" we filter them out of the list.
    #
    if is_win and (namespace in ['PyQt4', 'PySide']):
        files = [f for f in files if not f.endswith("d4.dll")]
    elif is_win and namespace in ['PyQt5', 'PySide2']:
        files = [f for f in files if not f.endswith("d.dll")]

    logger.debug('Found plugin files {0} for plugin \'{1}\''.format(files, plugin_type))
    if namespace in ['PyQt4', 'PySide']:
        plugin_dir = 'qt4_plugins'
    elif namespace == 'PyQt5':
        plugin_dir = os.path.join('PyQt5', 'Qt', 'plugins')
    else:
        plugin_dir = 'qt5_plugins'
    dest_dir = os.path.join(plugin_dir, plugin_type)
    binaries = [
        (f, dest_dir)
        for f in files]
    return binaries


def qt_menu_nib_dir(namespace):
    """
    Return path to Qt resource dir qt_menu.nib on OSX only.

    :param namespace: Import namespace, i.e., PyQt4, PyQt5,  PySide, or PySide2

    :return: Directory containing qt_menu.nib for specified namespace
    """
    if namespace not in ['PyQt4', 'PyQt5', 'PySide', 'PySide2']:
        raise Exception('Invalid namespace: {0}'.format(namespace))
    menu_dir = None

    path = exec_statement("""
    from {0}.QtCore import QLibraryInfo
    path = QLibraryInfo.location(QLibraryInfo.LibrariesPath)
    str = getattr(__builtins__, 'unicode', str)  # for Python 2
    print(str(path))
    """.format(namespace))
    anaconda_path = os.path.join(sys.exec_prefix, "python.app", "Contents", "Resources")
    paths = [os.path.join(path, 'Resources'), os.path.join(path, 'QtGui.framework', 'Resources'), anaconda_path]

    for location in paths:
        # Check directory existence
        path = os.path.join(location, 'qt_menu.nib')
        if os.path.exists(path):
            menu_dir = path
            logger.debug('Found qt_menu.nib for {0} at {1}'.format(namespace, path))
            break
    if not menu_dir:
        raise Exception("""
            Cannot find qt_menu.nib for {0}
            Path checked: {1}
            """.format(namespace, ", ".join(paths)))
    return menu_dir


def get_qmake_path(version=''):
    """
    Try to find the path to qmake with version given by the argument as a string.

    :param version: qmake version
    """
    import subprocess

    # Use QT[45]DIR if specified in the environment
    if 'QT5DIR' in os.environ and version[0] == '5':
        logger.debug('Using $QT5DIR/bin as qmake path')
        return os.path.join(os.environ['QT5DIR'], 'bin', 'qmake')
    if 'QT4DIR' in os.environ and version[0] == '4':
        logger.debug('Using $QT4DIR/bin as qmake path')
        return os.path.join(os.environ['QT4DIR'], 'bin', 'qmake')

    # try the default $PATH
    dirs = ['']

    # try homebrew paths
    for formula in ('qt', 'qt5'):
        homebrewqtpath = get_homebrew_path(formula)
        if homebrewqtpath:
            dirs.append(homebrewqtpath)

    for directory in dirs:
        try:
            qmake = os.path.join(directory, 'bin', 'qmake')
            versionstring = subprocess.check_output([qmake, '-query',
                                                     'QT_VERSION']).strip()
            if is_py3:
                # version string is probably just ASCII
                versionstring = versionstring.decode('utf8')
            if versionstring.find(version) == 0:
                logger.debug('Found qmake version "%s" at "%s".'
                             % (versionstring, qmake))
                return qmake
        except (OSError, subprocess.CalledProcessError):
            pass
    logger.debug('Could not find qmake matching version "%s".' % version)
    return None


def qt5_qml_dir(namespace):
    if namespace not in ['PyQt5', 'PySide2']:
        raise Exception('Invalid namespace: {0}'.format(namespace))

    if namespace == 'PyQt5':
        import PyQt5
        qmldir = os.path.join(PyQt5.__path__[0], 'Qt', 'qml')
        if os.path.isdir(qmldir):
            return qmldir

    qmake = get_qmake_path('5')
    if qmake is None:
        qmldir = ''
        logger.error('Could not find qmake version 5.x, make sure PATH is '
                     'set correctly or try setting QT5DIR.')
    else:
        qmldir = exec_command(qmake, "-query", "QT_INSTALL_QML").strip()
    if len(qmldir) == 0:
        logger.error('Cannot find QT_INSTALL_QML directory, "qmake -query ' +
                     'QT_INSTALL_QML" returned nothing')
    elif not os.path.exists(qmldir):
        logger.error("Directory QT_INSTALL_QML: %s doesn't exist" % qmldir)

    # 'qmake -query' uses / as the path separator, even on Windows
    qmldir = os.path.normpath(qmldir)
    return qmldir


def qt5_qml_data(qmldir, directory):
    """
    Return Qml library directory formatted for data.
    """
    return os.path.join(qmldir, directory), os.path.join('qml', directory)


def qt5_qml_plugins_binaries(qmldir, directory):
    """
    Return list of dynamic libraries formatted for mod.binaries.
    """
    binaries = []

    qt5_qml_plugin_dir = os.path.join(qmldir, directory)
    files = misc.dlls_in_subdirs(qt5_qml_plugin_dir)

    for f in files:
        relpath = os.path.relpath(f, qmldir)
        instdir, file = os.path.split(relpath)
        instdir = os.path.join("qml", instdir)
        logger.debug("qt5_qml_plugins_binaries installing %s in %s"
                     % (f, instdir))
        binaries.append((f, instdir))
    return binaries


def qt5_qml_plugins_datas(qmldir, directory):
    """
    Return list of data files for mod.binaries. (qmldir, *.qmltypes)
    """
    datas = []

    qt5_qml_plugin_dir = os.path.join(qmldir, directory)

    files = []
    for root, _dirs, _files in os.walk(qt5_qml_plugin_dir):
        files.extend(misc.files_in_dir(root, ["qmldir", "*.qmltypes"]))

    for f in files:
        relpath = os.path.relpath(f, qmldir)
        instdir, file = os.path.split(relpath)
        instdir = os.path.join("qml", instdir)
        logger.debug("qt5_qml_plugins_datas installing %s in %s"
                     % (f, instdir))
        datas.append((f, instdir))
    return datas


__all__ = ('qt_plugins_dir', 'qt_plugins_binaries', 'qt_menu_nib_dir', 'get_qmake_path', 'qt5_qml_dir', 'qt5_qml_data',
           'qt5_qml_plugins_binaries', 'qt5_qml_plugins_datas')
