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

from typing import Dict, Iterable, Optional, Tuple, Union, cast

from pants.backend.native.subsystems.native_build_step import ToolchainVariant
from pants.backend.native.targets.external_native_library import ConanRequirement
from pants.backend.native.targets.native_artifact import NativeArtifact
from pants.engine.addresses import Address
from pants.engine.target import (
    COMMON_TARGET_FIELDS,
    BoolField,
    Dependencies,
    InvalidFieldTypeException,
    PrimitiveField,
    ScalarField,
    SequenceField,
    Sources,
    StringField,
    StringSequenceField,
    Target,
)
from pants.util.collections import ensure_str_list
from pants.util.frozendict import FrozenDict

# -----------------------------------------------------------------------------------------------
# `ctypes_compatible_c_library` and `ctypes_compatible_cpp_library` targets
# -----------------------------------------------------------------------------------------------


class CSources(Sources):
    default = ("*.h", "*.c")


class CppSources(Sources):
    # TODO: Add support for all the different C++ file extensions, including `.cc` and `.cxx`. See
    #  https://stackoverflow.com/a/1546107.
    default = ("*.h", "*.hpp", "*.cpp")


class CtypesNativeLibrary(ScalarField):
    alias = "ctypes_native_library"
    expected_type = NativeArtifact
    expected_type_description = "a `native_artifact` object"
    value: Optional[NativeArtifact]

    @classmethod
    def compute_value(
        cls, raw_value: Optional[NativeArtifact], *, address: Address
    ) -> Optional[NativeArtifact]:
        return super().compute_value(raw_value, address=address)


class NativeFatalWarnings(BoolField):
    alias = "fatal_warnings"
    default = False


# NB: This is very similar to the JvmStrictDeps field in `backend/jvm`. Consider using the same
# field for both purposes.
class NativeStrictDeps(BoolField):
    """Whether to include only dependencies directly declared in the BUILD file.

    If this is False, all transitive dependencies are used when compiling and linking native code.
    """

    alias = "strict_deps"
    default = False


class ToolchainVariantField(StringField):
    """Whether to use gcc (gnu) or clang (llvm) to compile.

    Note that currently, despite the choice of toolchain, all linking is done with binutils ld on
    Linux, and the XCode CLI Tools on MacOS.
    """

    alias = "toolchain_variant"
    valid_choices = ToolchainVariant


class NativeCompilerOptionSets(StringSequenceField):
    alias = "compiler_option_sets"


NATIVE_LIBRARY_COMMON_FIELDS = (
    *COMMON_TARGET_FIELDS,
    Dependencies,
    CtypesNativeLibrary,
    NativeStrictDeps,
    NativeFatalWarnings,
    ToolchainVariantField,
    NativeCompilerOptionSets,
)


class CLibrary(Target):
    """A C library that is compatible with Python's ctypes."""

    alias = "ctypes_compatible_c_library"
    core_fields = (*NATIVE_LIBRARY_COMMON_FIELDS, CSources)
    v1_only = True


class CppLibrary(Target):
    """A C++ library that is compatible with Python's ctypes."""

    alias = "ctypes_compatible_cpp_library"
    core_fields = (*NATIVE_LIBRARY_COMMON_FIELDS, CppSources)
    v1_only = True


# -----------------------------------------------------------------------------------------------
# `external_native_library` target
# -----------------------------------------------------------------------------------------------


class ConanPackages(SequenceField):
    """The `ConanRequirement`s to resolve into a `packaged_native_library()` target."""

    alias = "packages"
    expected_element_type = ConanRequirement
    expected_type_description = "an iterable of `conan_requirement` objects (e.g. a list)"
    value: Tuple[ConanRequirement, ...]
    required = True

    @classmethod
    def compute_value(
        cls, raw_value: Optional[Iterable[ConanRequirement]], *, address: Address
    ) -> Tuple[ConanRequirement, ...]:
        return cast(Tuple[ConanRequirement, ...], super().compute_value(raw_value, address=address))


class ConanNativeLibrary(Target):
    """A set of Conan package strings to be passed to the Conan package manager."""

    alias = "external_native_library"
    core_fields = (*COMMON_TARGET_FIELDS, ConanPackages)