#
#  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.
#
#  Authors:
#        Tristan Van Berkom <tristan.vanberkom@codethink.co.uk>
#        Jim MacArthur <jim.macarthur@codethink.co.uk>
#        Benjamin Schubert <bschubert15@bloomberg.net>

"""
Foundation types
================

"""

from typing import Any, Dict, List, Union, Optional
import os

from .node import MappingNode, SequenceNode
from ._types import MetaFastEnum


class FastEnum(metaclass=MetaFastEnum):
    """
    A reimplementation of a subset of the `Enum` functionality, which is far quicker than `Enum`.

    :class:`enum.Enum` attributes accesses can be really slow, and slow down the execution noticeably.
    This reimplementation doesn't suffer the same problems, but also does not reimplement everything.
    """

    name = None
    """The name of the current Enum entry, same as :func:`enum.Enum.name`
    """

    value = None
    """The value of the current Enum entry, same as :func:`enum.Enum.value`
    """

    # A dict of all values mapping to the entries in the enum
    _value_to_entry = {}  # type: Dict[str, Any]

    @classmethod
    def values(cls):
        """Get all the possible values for the enum.

        Returns:
            list: the list of all possible values for the enum
        """
        return cls._value_to_entry.keys()

    def __new__(cls, value):
        try:
            return cls._value_to_entry[value]
        except KeyError:
            if type(value) is cls:  # pylint: disable=unidiomatic-typecheck
                return value
            raise ValueError("Unknown enum value: {}".format(value))

    def __eq__(self, other):
        if self.__class__ is not other.__class__:
            return NotImplemented
        # Enums instances are unique, so creating an instance with the same value as another will just
        # send back the other one, hence we can use an identity comparison, which is much faster than '=='
        return self is other

    def __ne__(self, other):
        if self.__class__ is not other.__class__:
            return NotImplemented
        return self is not other

    def __hash__(self):
        return hash(id(self))

    def __str__(self):
        return "{}.{}".format(self.__class__.__name__, self.name)

    def __reduce__(self):
        return self.__class__, (self.value,)


class CoreWarnings:
    """CoreWarnings()

    Some common warnings which are raised by core functionalities within BuildStream are found in this class.
    """

    OVERLAPS = "overlaps"
    """
    This warning will be produced when buildstream detects an overlap on an element
        which is not whitelisted. See :ref:`Overlap Whitelist <public_overlap_whitelist>`
    """

    UNSTAGED_FILES = "unstaged-files"
    """
    This warning will be produced when a file cannot be staged. This can happen when
    a file overlaps with a directory in the sandbox that is not empty.
    """

    REF_NOT_IN_TRACK = "ref-not-in-track"
    """
    This warning will be produced when a source is configured with a reference
    which is found to be invalid based on the configured track
    """

    UNALIASED_URL = "unaliased-url"
    """
    A URL used for fetching a sources was specified without specifying any
    :ref:`alias <project_source_aliases>`
    """

    UNAVAILABLE_SOURCE_INFO = "unavailable-source-info"
    """
    A source was queried for its provenance information but did not implement
    :func:`Source.collect_source_info() <buildstream.source.Source.collect_source_info>`.
    """


class OverlapAction(FastEnum):
    """OverlapAction()

    Defines what action to take when files staged into the sandbox overlap.

    .. note::

       This only dictates what happens when functions such as
       :func:`Element.stage_artifact() <buildstream.element.Element.stage_artifact>` and
       :func:`Element.stage_dependency_artifacts() <buildstream.element.Element.stage_dependency_artifacts>`
       are called multiple times in an Element's :func:`Element.stage() <buildstream.element.Element.stage>`
       implementation, and the files staged from one function call result in overlapping files staged
       from previous invocations.

       If multiple staged elements overlap eachother within a single call to
       :func:`Element.stage_dependency_artifacts() <buildstream.element.Element.stage_dependency_artifacts>`,
       then the :ref:`overlap whitelist <public_overlap_whitelist>` will be ovserved, and warnings will
       be issued for overlapping files, which will be fatal warnings if
       :attr:`CoreWarnings.OVERLAPS <buildstream.types.CoreWarnings.OVERLAPS>` is specified
       as a :ref:`fatal warning <configurable_warnings>`.
    """

    ERROR = "error"
    """
    It is an error to overlap previously staged files
    """

    WARNING = "warning"
    """
    A warning will be issued for previously staged files, which will fatal if
    :attr:`CoreWarnings.OVERLAPS <buildstream.types.CoreWarnings.OVERLAPS>` is specified
    as a :ref:`fatal warning <configurable_warnings>` in the project.
    """

    IGNORE = "ignore"
    """
    Overlapping files are acceptable, and do not cause any warning or error.
    """


# _Scope():
#
# Defines the scope of dependencies to include for a given element
# when iterating over the dependency graph in APIs like
# Element._dependencies().
#
class _Scope(FastEnum):

    # All elements which the given element depends on, following
    # all elements required for building. Including the element itself.
    #
    ALL = 1

    # All elements required for building the element, including their
    # respective run dependencies. Not including the given element itself.
    #
    BUILD = 2

    # All elements required for running the element. Including the element
    # itself.
    #
    RUN = 3

    # Just the element itself, no dependencies.
    #
    NONE = 4


# _KeyStrength():
#
# Strength of cache key
#
class _KeyStrength(FastEnum):

    # Includes strong cache keys of all build dependencies and their
    # runtime dependencies.
    STRONG = 1

    # Includes names of direct build dependencies but does not include
    # cache keys of dependencies.
    WEAK = 2


# _DisplayKey():
#
# The components of a cache key which need to be displayed
#
# This is a part of Message() so it needs to be a simple serializable object.
#
# Args:
#    full: A full hex digest cache key for an Element
#    brief: An abbreviated hex digest cache key for an Element
#    strict: Whether the key matches the key which would be used in strict mode
#
class _DisplayKey:
    def __init__(self, full: str, brief: str, strict: bool):
        self.full = full  # type: str
        self.brief = brief  # type: str
        self.strict = strict  # type: bool


# _SchedulerErrorAction()
#
# Actions the scheduler can take on error
#
class _SchedulerErrorAction(FastEnum):

    # Continue building the rest of the tree
    CONTINUE = "continue"

    # finish ongoing work and quit
    QUIT = "quit"

    # Abort immediately
    TERMINATE = "terminate"


# _CacheBuildTrees()
#
# When to cache build trees
#
class _CacheBuildTrees(FastEnum):

    # Always store build trees
    ALWAYS = "always"

    # Store build trees when they might be useful for BuildStream
    # (eg: on error, to allow for a shell to debug that)
    AUTO = "auto"

    # Never cache build trees
    NEVER = "never"


# _SourceUriPolicy()
#
# A policy for which URIs to access when fetching and tracking
#
class _SourceUriPolicy(FastEnum):

    # Use all URIs from default aliases and mirrors
    ALL = "all"

    # Use only the base source aliases defined in project configuration
    #
    ALIASES = "aliases"

    # Use only URIs from source mirrors (whether they are found
    # in project configuration or user configuration)
    MIRRORS = "mirrors"

    # Use only URIs from user configuration, intentionally causing
    # a failure if we try to access a source for which the user
    # configuration has not provided a mirror
    USER = "user"


# _PipelineSelection()
#
# Defines the kind of pipeline selection to make when the pipeline
# is provided a list of targets, for whichever purpose.
#
# These values correspond to the CLI `--deps` arguments for convenience.
#
class _PipelineSelection(FastEnum):

    # Select only the target elements in the associated targets
    NONE = "none"

    # As NONE, but redirect elements that are capable of it
    REDIRECT = "redirect"

    # All dependencies of all targets, including the targets
    ALL = "all"

    # All direct build dependencies and their recursive runtime dependencies,
    # excluding the targets
    BUILD = "build"

    # All direct runtime dependencies and their recursive runtime dependencies,
    # including the targets
    RUN = "run"

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


# _ProjectInformation()
#
# A descriptive object about a project.
#
# Args:
#    project (Project): The project instance
#    provenance_node (Node): The provenance information, if any
#    duplicates (list): List of project descriptions which declared this project as a duplicate
#    internal (list): List of project descriptions which declared this project as internal
#
class _ProjectInformation:
    def __init__(self, project, provenance_node, duplicates, internal):
        self.project = project
        self.provenance = provenance_node.get_provenance() if provenance_node else None
        self.duplicates = duplicates
        self.internal = internal


# _HostMount()
#
# A simple object describing the behavior of a host mount.
#
class _HostMount:
    def __init__(self, path: str, host_path: Optional[str] = None, optional: bool = False) -> None:

        # Support environment variable expansion in host mounts
        path = os.path.expandvars(path)
        if host_path is None:
            host_path = path
        else:
            host_path = os.path.expandvars(host_path)

        self.path: str = path  # Path inside the sandbox
        self.host_path: str = host_path  # Path on the host
        self.optional: bool = optional  # Optional mounts do not incur warnings or errors


# _SourceMirror()
#
# A simple object describing a source mirror
#
# Args:
#    name: The mirror name
#    aliases: A dictionary of URI lists, keyed by alias names
#
class _SourceMirror:
    def __init__(self, name: str, aliases: Dict[str, List[str]]):
        self.name: str = name
        self.aliases: Dict[str, List[str]] = aliases

    # new_from_node():
    #
    # Creates a _SourceMirror() from a YAML loaded node.
    #
    # Args:
    #    node: The configuration node describing the spec.
    #
    # Returns:
    #    The described _SourceMirror instance.
    #
    # Raises:
    #    LoadError: If the node is malformed.
    #
    @classmethod
    def new_from_node(cls, node: MappingNode) -> "_SourceMirror":
        node.validate_keys(["name", "aliases"])

        name: str = node.get_str("name")
        aliases: Dict[str, List[str]] = {}

        alias_node: MappingNode = node.get_mapping("aliases")

        for alias, uris in alias_node.items():
            assert type(uris) is SequenceNode  # pylint: disable=unidiomatic-typecheck
            aliases[alias] = uris.as_str_list()

        return cls(name, aliases)


########################################
#           Type aliases               #
########################################

# Internal reference for a given Source
SourceRef = Union[None, int, str, List[Any], Dict[str, Any]]
"""
A simple python object used to describe and exact set of sources

This can be ``None`` in order to represent an absense of a source reference,
otherwise it can be ``int``, ``str``, or a complex ``list`` or ``dict`` consisting
of ``int``, ``str``, ``list`` and ``dict`` types.

The order of elements in ``list`` objects is meaningful and should be produced
deterministically by :class:`.Source` implementations, as this order will effect
:ref:`cache keys <cachekeys>`.

See the :ref:`source documentation <core_source_ref>` for more detils on how
:class:`.Source` implementations are expected to handle the source ref.
"""
