#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#

from ..types import FastEnum
from ..node import ScalarNode, MappingNode
from .._exceptions import LoadError
from ..exceptions import LoadErrorReason


# PluginType()
#
# A type of plugin
#
class PluginType(FastEnum):

    # A Source plugin
    SOURCE = "source"

    # An Element plugin
    ELEMENT = "element"

    # A SourceMirror plugin
    SOURCE_MIRROR = "source-mirror"

    def __str__(self):
        return str(self.value)


# PluginOriginType:
#
# An enumeration depicting the type of plugin origin
#
class PluginOriginType(FastEnum):

    # A local plugin
    LOCAL = "local"

    # A pip plugin
    PIP = "pip"

    # A plugin loaded via a junction
    JUNCTION = "junction"


# PluginConfiguration:
#
# An object representing the configuration of a single
# plugin in the origin.
#
class PluginConfiguration:
    def __init__(self, kind, allow_deprecated):
        self.kind = kind
        self.allow_deprecated = allow_deprecated


# PluginOrigin
#
# Base class holding common properties of all origins.
#
class PluginOrigin:

    # Common fields valid for all plugin origins
    _COMMON_CONFIG_KEYS = ["origin", "sources", "elements", "source-mirrors", "allow-deprecated"]

    def __init__(self, origin_type):

        # Public
        self.origin_type = origin_type  # The PluginOriginType
        self.elements = {}  # A dictionary of PluginConfiguration
        self.sources = {}  # A dictionary of PluginConfiguration objects
        self.source_mirrors = {}  # A dictionary of PluginConfiguration objects
        self.provenance_node = None
        self.project = None

        # Private
        self._kinds = {}
        self._allow_deprecated = False

    # initialize()
    #
    # Initializes the origin, resulting in loading the origin
    # node.
    #
    # This is the bottom half of the initialization, it is done
    # separately because load_plugin_origin() needs to stay in
    # __init__.py in order to avoid cyclic dependencies between
    # PluginOrigin and it's subclasses.
    #
    # Args:
    #    project (Project): The project this PluginOrigin was loaded for
    #    origin_node (MappingNode): The node defining this origin
    #
    def initialize(self, project, origin_node):

        self.provenance_node = origin_node
        self.project = project
        self.load_config(origin_node)

        # Parse commonly defined aspects of PluginOrigins
        self._allow_deprecated = origin_node.get_bool("allow-deprecated", False)

        element_sequence = origin_node.get_sequence("elements", [])
        self._load_plugin_configurations(element_sequence, self.elements)

        source_sequence = origin_node.get_sequence("sources", [])
        self._load_plugin_configurations(source_sequence, self.sources)

        source_mirror_sequence = origin_node.get_sequence("source-mirrors", [])
        self._load_plugin_configurations(source_mirror_sequence, self.source_mirrors)

    ##############################################
    #              Abstract methods              #
    ##############################################

    # get_plugin_paths():
    #
    # Abstract method for loading the details about a specific plugin,
    # the PluginFactory uses this to get the assets needed to actually
    # load the plugins.
    #
    # Args:
    #    kind (str): The plugin
    #    plugin_type (PluginType): The kind of plugin to load
    #
    # Returns:
    #    (str): The full path to the directory containing the plugin
    #    (str): The full path to the accompanying .yaml file containing
    #           the plugin's preferred defaults.
    #    (str): The explanatory display string describing how this plugin was loaded
    #
    def get_plugin_paths(self, kind, plugin_type):
        pass

    # load_config()
    #
    # Abstract method for loading data from the origin node, this
    # method should not load the source and element lists.
    #
    # Args:
    #    origin_node (MappingNode): The node defining this origin
    #
    def load_config(self, origin_node):
        pass

    ##############################################
    #               Private methods              #
    ##############################################

    # _load_plugin_configurations()
    #
    # Helper function to load the list of source or element
    # PluginConfigurations
    #
    # Args:
    #    sequence_node (SequenceNode): The list of configurations
    #    dictionary (dict): The location to store the results
    #
    def _load_plugin_configurations(self, sequence_node, dictionary):

        for node in sequence_node:

            # Parse as a simple string
            if type(node) is ScalarNode:  # pylint: disable=unidiomatic-typecheck
                kind = node.as_str()
                conf = PluginConfiguration(kind, self._allow_deprecated)

            # Parse as a dictionary
            elif type(node) is MappingNode:  # pylint: disable=unidiomatic-typecheck
                node.validate_keys(["kind", "allow-deprecated"])
                kind = node.get_str("kind")
                allow_deprecated = node.get_bool("allow-deprecated", self._allow_deprecated)
                conf = PluginConfiguration(kind, allow_deprecated)
            else:
                p = node.get_provenance()
                raise LoadError(
                    "{}: Plugin is not specified as a string or a dictionary".format(p), LoadErrorReason.INVALID_DATA
                )

            dictionary[kind] = conf
