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

from collections import namedtuple
from dataclasses import dataclass
from enum import Enum
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple

import pytest

from pants.engine.addresses import Address
from pants.engine.fs import GlobExpansionConjunction, GlobMatchErrorBehavior, PathGlobs, Paths
from pants.engine.target import (
    AsyncFieldMixin,
    BoolField,
    DictStringToStringField,
    DictStringToStringSequenceField,
    ExplicitlyProvidedDependencies,
    Field,
    FieldSet,
    FloatField,
    GeneratedTargets,
    GenerateSourcesRequest,
    IntField,
    InvalidFieldChoiceException,
    InvalidFieldException,
    InvalidFieldTypeException,
    InvalidGeneratedTargetException,
    InvalidTargetException,
    MultipleSourcesField,
    NestedDictStringToStringField,
    OptionalSingleSourceField,
    OverridesField,
    RequiredFieldMissingException,
    ScalarField,
    SequenceField,
    SingleSourceField,
    StringField,
    StringSequenceField,
    Target,
    ValidNumbers,
    targets_with_sources_types,
)
from pants.engine.unions import UnionMembership
from pants.option.global_options import FilesNotFoundBehavior
from pants.testutil.pytest_util import no_exception
from pants.util.frozendict import FrozenDict
from pants.util.meta import FrozenInstanceError
from pants.util.ordered_set import FrozenOrderedSet, OrderedSet

# -----------------------------------------------------------------------------------------------
# Test core Field and Target abstractions
# -----------------------------------------------------------------------------------------------


class FortranExtensions(Field):
    alias = "fortran_extensions"
    value: Tuple[str, ...]
    default = ()

    @classmethod
    def compute_value(cls, raw_value: Optional[Iterable[str]], address: Address) -> Tuple[str, ...]:
        value_or_default = super().compute_value(raw_value, address)
        # Add some arbitrary validation to test that hydration/validation works properly.
        bad_extensions = [
            extension for extension in value_or_default if not extension.startswith("Fortran")
        ]
        if bad_extensions:
            raise InvalidFieldException(
                f"The {repr(cls.alias)} field in target {address} expects all elements to be "
                f"prefixed by `Fortran`. Received {bad_extensions}.",
            )
        return tuple(value_or_default)


class FortranVersion(StringField):
    alias = "version"


class UnrelatedField(BoolField):
    alias = "unrelated"
    default = False


class FortranTarget(Target):
    alias = "fortran"
    core_fields = (FortranExtensions, FortranVersion)

    def validate(self) -> None:
        if self[FortranVersion].value == "bad":
            raise InvalidTargetException("Bad!")


def test_field_and_target_eq() -> None:
    addr = Address("", target_name="tgt")
    field = FortranVersion("dev0", addr)
    assert field.value == "dev0"

    other = FortranVersion("dev0", addr)
    assert field == other
    assert hash(field) == hash(other)

    other = FortranVersion("dev1", addr)
    assert field != other
    assert hash(field) != hash(other)

    # NB: because normal `Field`s throw away the address, these are equivalent.
    other = FortranVersion("dev0", Address("", target_name="other"))
    assert field == other
    assert hash(field) == hash(other)