# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import re
from dataclasses import dataclass
from typing import Any, Dict, Iterable, Optional, Tuple, Union, cast

from pants.base.exceptions import DuplicateNameError, MappingError, UnaddressableObjectError
from pants.build_graph.address import Address, BuildFileAddress
from pants.engine.internals.objects import Serializable
from pants.engine.internals.parser import BuildFilePreludeSymbols, Parser
from pants.option.global_options import BuildFileImportsBehavior
from pants.util.memo import memoized_property
from pants.util.meta import frozen_after_init

ThinAddressableObject = Union[Serializable, Any]


@dataclass(frozen=True)
class AddressMap:
    """Maps addressable Serializable objects from a byte source.

    To construct an AddressMap, use `parse`.

    :param path: The path to the byte source this address map's objects were passed from.
    :param objects_by_name: A dict mapping from object name to the parsed 'thin' addressable object.
    """

    path: str
    objects_by_name: Dict[str, ThinAddressableObject]

    @classmethod
    def parse(
        cls,
        filepath: str,
        filecontent: bytes,
        parser: Parser,
        extra_symbols: BuildFilePreludeSymbols,
    ) -> "AddressMap":
        """Parses a source for addressable Serializable objects.

        No matter the parser used, the parsed and mapped addressable objects are all 'thin'; ie: any
        objects they point to in other namespaces or even in the same namespace but from a separate
        source are left as unresolved pointers.

        :param filepath: The path to the byte source containing serialized objects.
        :param filecontent: The content of byte source containing serialized objects to be parsed.
        :param parser: The parser cls to use.
        """
        try:
            objects = parser.parse(filepath, filecontent, extra_symbols)
        except Exception as e:
            raise MappingError(f"Failed to parse {filepath}:\n{e!r}")
        objects_by_name: Dict[str, ThinAddressableObject] = {}
        for obj in objects:
            if not Serializable.is_serializable(obj):
                raise UnaddressableObjectError("Parsed a non-serializable object: {!r}".format(obj))
            attributes = obj._asdict()

            name = attributes.get("name")
            if not name:
                raise UnaddressableObjectError("Parsed a non-addressable object: {!r}".format(obj))

            if name in objects_by_name:
                raise DuplicateNameError(
                    "An object already exists at {!r} with name {!r}: {!r}.  Cannot "
                    "map {!r}".format(filepath, name, objects_by_name[name], obj)
                )