#
#  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.

    For mypy Enum static type checking support, all FastEnum should be stubbed as inheriting from enum.Enum.
    Use `stubgen src/buildstream/types.py --include-docstrings` to generate the stubs, add `from enum import Enum` and replace all `(FastEnum)` with `(Enum)`
    """

    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 isinstance(value, 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 str(self.value)

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

    def __repr__(self):
        return "<{}.{}: {}>".format(self.__class__.__name__, self.name, 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"


# _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 isinstance(uris, 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.
"""
