# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""
Plugin dependency solver.
"""

import ast
import importlib
import logging
import os
import traceback

import pkg_resources

from spyder.api.exceptions import SpyderAPIError
from spyder.api.plugins import (
    SpyderDockablePlugin, SpyderPluginWidget, Plugins)
from spyder.api.utils import get_class_values
from spyder.config.base import (
    DEV, STDERR, running_in_ci, running_under_pytest)


logger = logging.getLogger(__name__)


def find_internal_plugins():
    """
    Find internal plugins based on setup.py entry points.

    In DEV mode we parse the `setup.py` file directly.
    """
    internal_plugins = {}

    # If DEV, look for entry points in setup.py file for internal plugins
    # and then look on the system for the rest
    HERE = os.path.abspath(os.path.dirname(__file__))
    base_path = os.path.dirname(os.path.dirname(HERE))
    setup_path = os.path.join(base_path, "setup.py")

    if (DEV or running_under_pytest()) and not running_in_ci():
        if not os.path.isfile(setup_path):
            raise Exception(
                'No "setup.py" file found and running in DEV mode!')

        with open(setup_path, "r") as fh:
            lines = fh.read().split("\n")

        start = None
        end = None
        for idx, line in enumerate(lines):
            if line.startswith("spyder_plugins_entry_points"):
                start = idx + 1
                continue

            if start is not None:
                if line.startswith("]"):
                    end = idx + 1
                    break

        entry_points_list = "[" + "\n".join(lines[start:end])
        spyder_plugin_entry_points = ast.literal_eval(entry_points_list)
        for entry_point in spyder_plugin_entry_points:
            try:
                name, module = entry_point.split(" = ")
                name = name.strip()
                module = module.strip()
                module, class_name = module.split(":")
            except Exception:
                logger.error(
                    '"setup.py" entry point "{entry_point}" is malformed!'
                    "".format(entry_point=entry_point)
                )

            try:
                mod = importlib.import_module(module)
                internal_plugins[name] = getattr(mod, class_name, None)
            except (ModuleNotFoundError, ImportError) as e:
                raise e
    else:
        entry_points = list(pkg_resources.iter_entry_points("spyder.plugins"))
        internal_names = get_class_values(Plugins)

        for entry_point in entry_points:
            name = entry_point.name
            if name not in internal_names:
                continue

            class_name = entry_point.attrs[0]
            mod = importlib.import_module(entry_point.module_name)
            plugin_class = getattr(mod, class_name, None)
            internal_plugins[name] = plugin_class

        # FIXME: This shouldn't be necessary but it's just to be sure
        # plugins are sorted in alphabetical order. We need to remove it
        # in a later version.
        internal_plugins = {
            key: value for key, value in sorted(internal_plugins.items())
        }

    return internal_plugins


def find_external_plugins():
    """
    Find available external plugins based on setuptools entry points.
    """
    internal_names = get_class_values(Plugins)
    plugins = list(pkg_resources.iter_entry_points("spyder.plugins"))
    external_plugins = {}

    for entry_point in plugins:
        name = entry_point.name
        if name not in internal_names:
            try:
                class_name = entry_point.attrs[0]
                mod = importlib.import_module(entry_point.module_name)
                plugin_class = getattr(mod, class_name, None)

                # To display in dependencies dialog.
                plugin_class._spyder_module_name = entry_point.module_name
                plugin_class._spyder_package_name = (
                    entry_point.dist.project_name)
                plugin_class._spyder_version = entry_point.dist.version

                external_plugins[name] = plugin_class
                if name != plugin_class.NAME:
                    raise SpyderAPIError(
                        "Entry point name '{0}' and plugin.NAME '{1}' "
                        "do not match!".format(name, plugin_class.NAME)
                    )
            except (ModuleNotFoundError, ImportError) as error:
                print("%s: %s" % (name, str(error)), file=STDERR)
                traceback.print_exc(file=STDERR)

    return external_plugins
