use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::sync::Arc;

use anyhow::Context as _;
use cargo_util_schemas::manifest::RustVersion;
use cargo_util_schemas::manifest::{TomlManifest, TomlProfiles};
use semver::Version;
use serde::ser;
use serde::Serialize;
use url::Url;

use crate::core::compiler::rustdoc::RustdocScrapeExamples;
use crate::core::compiler::{CompileKind, CrateType};
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, PackageId, PackageIdSpec, SourceId, Summary};
use crate::core::{Edition, Feature, Features, WorkspaceConfig};
use crate::util::errors::*;
use crate::util::interning::InternedString;
use crate::util::{short_hash, Filesystem, GlobalContext};

pub const MANIFEST_PREAMBLE: &str = "\
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# \"normalize\" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
";

pub enum EitherManifest {
    Real(Manifest),
    Virtual(VirtualManifest),
}

impl EitherManifest {
    pub fn warnings_mut(&mut self) -> &mut Warnings {
        match self {
            EitherManifest::Real(r) => r.warnings_mut(),
            EitherManifest::Virtual(v) => v.warnings_mut(),
        }
    }
    pub(crate) fn workspace_config(&self) -> &WorkspaceConfig {
        match *self {
            EitherManifest::Real(ref r) => r.workspace_config(),
            EitherManifest::Virtual(ref v) => v.workspace_config(),
        }
    }
}

/// Contains all the information about a package, as loaded from a `Cargo.toml`.
///
/// This is deserialized using the [`TomlManifest`] type.
#[derive(Clone, Debug)]
pub struct Manifest {
    // alternate forms of manifests:
    contents: Rc<String>,
    document: Rc<toml_edit::ImDocument<String>>,
    original_toml: Rc<TomlManifest>,
    normalized_toml: Rc<TomlManifest>,
    summary: Summary,

    // this form of manifest:
    targets: Vec<Target>,
    default_kind: Option<CompileKind>,
    forced_kind: Option<CompileKind>,
    links: Option<String>,
    warnings: Warnings,
    exclude: Vec<String>,
    include: Vec<String>,
    metadata: ManifestMetadata,
    custom_metadata: Option<toml::Value>,
    publish: Option<Vec<String>>,
    replace: Vec<(PackageIdSpec, Dependency)>,
    patch: HashMap<Url, Vec<Dependency>>,
    workspace: WorkspaceConfig,
    unstable_features: Features,
    edition: Edition,
    rust_version: Option<RustVersion>,
    im_a_teapot: Option<bool>,
    default_run: Option<String>,
    metabuild: Option<Vec<String>>,
    resolve_behavior: Option<ResolveBehavior>,
    lint_rustflags: Vec<String>,
    embedded: bool,
}

/// When parsing `Cargo.toml`, some warnings should silenced
/// if the manifest comes from a dependency. `ManifestWarning`
/// allows this delayed emission of warnings.
#[derive(Clone, Debug)]
pub struct DelayedWarning {
    pub message: String,
    pub is_critical: bool,
}

#[derive(Clone, Debug)]
pub struct Warnings(Vec<DelayedWarning>);

#[derive(Clone, Debug)]
pub struct VirtualManifest {
    // alternate forms of manifests:
    contents: Rc<String>,
    document: Rc<toml_edit::ImDocument<String>>,
    original_toml: Rc<TomlManifest>,
    normalized_toml: Rc<TomlManifest>,

    // this form of manifest:
    replace: Vec<(PackageIdSpec, Dependency)>,
    patch: HashMap<Url, Vec<Dependency>>,
    workspace: WorkspaceConfig,
    warnings: Warnings,
    features: Features,
    resolve_behavior: Option<ResolveBehavior>,
}

/// General metadata about a package which is just blindly uploaded to the
/// registry.
///
/// Note that many of these fields can contain invalid values such as the
/// homepage, repository, documentation, or license. These fields are not
/// validated by cargo itself, but rather it is up to the registry when uploaded
/// to validate these fields. Cargo will itself accept any valid TOML
/// specification for these values.
#[derive(PartialEq, Clone, Debug)]
pub struct ManifestMetadata {
    pub authors: Vec<String>,
    pub keywords: Vec<String>,
    pub categories: Vec<String>,
    pub license: Option<String>,
    pub license_file: Option<String>,
    pub description: Option<String>,   // Not in Markdown
    pub readme: Option<String>,        // File, not contents
    pub homepage: Option<String>,      // URL
    pub repository: Option<String>,    // URL
    pub documentation: Option<String>, // URL
    pub badges: BTreeMap<String, BTreeMap<String, String>>,
    pub links: Option<String>,
    pub rust_version: Option<RustVersion>,
}

impl ManifestMetadata {
    /// Whether the given env var should be tracked by Cargo's dep-info.
    pub fn should_track(env_key: &str) -> bool {
        let keys = MetadataEnvs::keys();
        keys.iter().any(|k| *k == env_key)
    }

    pub fn env_var<'a>(&'a self, env_key: &str) -> Option<Cow<'a, str>> {
        MetadataEnvs::var(self, env_key)
    }

    pub fn env_vars(&self) -> impl Iterator<Item = (&'static str, Cow<'_, str>)> {
        MetadataEnvs::keys()
            .iter()
            .map(|k| (*k, MetadataEnvs::var(self, k).unwrap()))
    }
}

macro_rules! get_metadata_env {
    ($meta:ident, $field:ident) => {
        $meta.$field.as_deref().unwrap_or_default().into()
    };
    ($meta:ident, $field:ident, $to_var:expr) => {
        $to_var($meta).into()
    };
}

struct MetadataEnvs;

macro_rules! metadata_envs {
    (
        $(
            ($field:ident, $key:literal$(, $to_var:expr)?),
        )*
    ) => {
        impl MetadataEnvs {
            fn keys() -> &'static [&'static str] {
                &[$($key),*]
            }

            fn var<'a>(meta: &'a ManifestMetadata, key: &str) -> Option<Cow<'a, str>> {
                match key {
                    $($key => Some(get_metadata_env!(meta, $field$(, $to_var)?)),)*
                    _ => None,
                }
            }
        }
    }
}

// Metadata enviromental variables that are emitted to rustc. Usable by `env!()`
// If these change we need to trigger a rebuild.
// NOTE: The env var name will be prefixed with `CARGO_PKG_`
metadata_envs! {
    (description, "CARGO_PKG_DESCRIPTION"),
    (homepage, "CARGO_PKG_HOMEPAGE"),
    (repository, "CARGO_PKG_REPOSITORY"),
    (license, "CARGO_PKG_LICENSE"),
    (license_file, "CARGO_PKG_LICENSE_FILE"),
    (authors, "CARGO_PKG_AUTHORS", |m: &ManifestMetadata| m.authors.join(":")),
    (rust_version, "CARGO_PKG_RUST_VERSION", |m: &ManifestMetadata| m.rust_version.as_ref().map(ToString::to_string).unwrap_or_default()),
    (readme, "CARGO_PKG_README"),
}

#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum TargetKind {
    Lib(Vec<CrateType>),
    Bin,
    Test,
    Bench,
    ExampleLib(Vec<CrateType>),
    ExampleBin,
    CustomBuild,
}

impl ser::Serialize for TargetKind {
    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
    where
        S: ser::Serializer,
    {
        use self::TargetKind::*;
        match self {
            Lib(kinds) => s.collect_seq(kinds.iter().map(|t| t.to_string())),
            Bin => ["bin"].serialize(s),
            ExampleBin | ExampleLib(_) => ["example"].serialize(s),
            Test => ["test"].serialize(s),
            CustomBuild => ["custom-build"].serialize(s),
            Bench => ["bench"].serialize(s),
        }
    }
}

impl fmt::Debug for TargetKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use self::TargetKind::*;
        match *self {
            Lib(ref kinds) => kinds.fmt(f),
            Bin => "bin".fmt(f),
            ExampleBin | ExampleLib(_) => "example".fmt(f),
            Test => "test".fmt(f),
            CustomBuild => "custom-build".fmt(f),
            Bench => "bench".fmt(f),
        }
    }
}

impl TargetKind {
    pub fn description(&self) -> &'static str {
        match self {
            TargetKind::Lib(..) => "lib",
            TargetKind::Bin => "bin",
            TargetKind::Test => "integration-test",
            TargetKind::ExampleBin | TargetKind::ExampleLib(..) => "example",
            TargetKind::Bench => "bench",
            TargetKind::CustomBuild => "build-script",
        }
    }

    /// Returns whether production of this artifact requires the object files
    /// from dependencies to be available.
    ///
    /// This only returns `false` when all we're producing is an rlib, otherwise
    /// it will return `true`.
    pub fn requires_upstream_objects(&self) -> bool {
        match self {
            TargetKind::Lib(kinds) | TargetKind::ExampleLib(kinds) => {
                kinds.iter().any(|k| k.requires_upstream_objects())
            }
            _ => true,
        }
    }

    /// Returns the arguments suitable for `--crate-type` to pass to rustc.
    pub fn rustc_crate_types(&self) -> Vec<CrateType> {
        match self {
            TargetKind::Lib(kinds) | TargetKind::ExampleLib(kinds) => kinds.clone(),
            TargetKind::CustomBuild
            | TargetKind::Bench
            | TargetKind::Test
            | TargetKind::ExampleBin
            | TargetKind::Bin => vec![CrateType::Bin],
        }
    }
}

/// Information about a binary, a library, an example, etc. that is part of the
/// package.
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Target {
    inner: Arc<TargetInner>,
}

#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct TargetInner {
    kind: TargetKind,
    name: String,
    // Whether the name was inferred by Cargo, or explicitly given.
    name_inferred: bool,
    // Note that `bin_name` is used for the cargo-feature `different_binary_name`
    bin_name: Option<String>,
    // Note that the `src_path` here is excluded from the `Hash` implementation
    // as it's absolute currently and is otherwise a little too brittle for
    // causing rebuilds. Instead the hash for the path that we send to the
    // compiler is handled elsewhere.
    src_path: TargetSourcePath,
    required_features: Option<Vec<String>>,
    tested: bool,
    benched: bool,
    doc: bool,
    doctest: bool,
    harness: bool, // whether to use the test harness (--test)
    for_host: bool,
    proc_macro: bool,
    edition: Edition,
    doc_scrape_examples: RustdocScrapeExamples,
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum TargetSourcePath {
    Path(PathBuf),
    Metabuild,
}

impl TargetSourcePath {
    pub fn path(&self) -> Option<&Path> {
        match self {
            TargetSourcePath::Path(path) => Some(path.as_ref()),
            TargetSourcePath::Metabuild => None,
        }
    }

    pub fn is_path(&self) -> bool {
        matches!(self, TargetSourcePath::Path(_))
    }
}

impl Hash for TargetSourcePath {
    fn hash<H: Hasher>(&self, _: &mut H) {
        // ...
    }
}

impl fmt::Debug for TargetSourcePath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TargetSourcePath::Path(path) => path.fmt(f),
            TargetSourcePath::Metabuild => "metabuild".fmt(f),
        }
    }
}

impl From<PathBuf> for TargetSourcePath {
    fn from(path: PathBuf) -> Self {
        assert!(path.is_absolute(), "`{}` is not absolute", path.display());
        TargetSourcePath::Path(path)
    }
}

#[derive(Serialize)]
struct SerializedTarget<'a> {
    /// Is this a `--bin bin`, `--lib`, `--example ex`?
    /// Serialized as a list of strings for historical reasons.
    kind: &'a TargetKind,
    /// Corresponds to `--crate-type` compiler attribute.
    /// See <https://doc.rust-lang.org/reference/linkage.html>
    crate_types: Vec<CrateType>,
    name: &'a str,
    src_path: Option<&'a PathBuf>,
    edition: &'a str,
    #[serde(rename = "required-features", skip_serializing_if = "Option::is_none")]
    required_features: Option<Vec<&'a str>>,
    /// Whether docs should be built for the target via `cargo doc`
    /// See <https://doc.rust-lang.org/cargo/commands/cargo-doc.html#target-selection>
    doc: bool,
    doctest: bool,
    /// Whether tests should be run for the target (`test` field in `Cargo.toml`)
    test: bool,
}

impl ser::Serialize for Target {
    fn serialize<S: ser::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        let src_path = match self.src_path() {
            TargetSourcePath::Path(p) => Some(p),
            // Unfortunately getting the correct path would require access to
            // target_dir, which is not available here.
            TargetSourcePath::Metabuild => None,
        };
        SerializedTarget {
            kind: self.kind(),
            crate_types: self.rustc_crate_types(),
            name: self.name(),
            src_path,
            edition: &self.edition().to_string(),
            required_features: self
                .required_features()
                .map(|rf| rf.iter().map(|s| s.as_str()).collect()),
            doc: self.documented(),
            doctest: self.doctested() && self.doctestable(),
            test: self.tested(),
        }
        .serialize(s)
    }
}

impl fmt::Debug for Target {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.inner.fmt(f)
    }
}

compact_debug! {
    impl fmt::Debug for TargetInner {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            let (default, default_name) = {
                match &self.kind {
                    TargetKind::Lib(kinds) => {
                        (
                            Target::lib_target(
                                &self.name,
                                kinds.clone(),
                                self.src_path.path().unwrap().to_path_buf(),
                                self.edition,
                            ).inner,
                            format!("lib_target({:?}, {:?}, {:?}, {:?})",
                                    self.name, kinds, self.src_path, self.edition),
                        )
                    }
                    TargetKind::CustomBuild => {
                        match self.src_path {
                            TargetSourcePath::Path(ref path) => {
                                (
                                    Target::custom_build_target(
                                        &self.name,
                                        path.to_path_buf(),
                                        self.edition,
                                    ).inner,
                                    format!("custom_build_target({:?}, {:?}, {:?})",
                                            self.name, path, self.edition),
                                )
                            }
                            TargetSourcePath::Metabuild => {
                                (
                                    Target::metabuild_target(&self.name).inner,
                                    format!("metabuild_target({:?})", self.name),
                                )
                            }
                        }
                    }
                    _ => (
                        Target::new(self.src_path.clone(), self.edition).inner,
                        format!("with_path({:?}, {:?})", self.src_path, self.edition),
                    ),
                }
            };
            [debug_the_fields(
                kind
                name
                name_inferred
                bin_name
                src_path
                required_features
                tested
                benched
                doc
                doctest
                harness
                for_host
                proc_macro
                edition
                doc_scrape_examples
            )]
        }
    }
}

impl Manifest {
    pub fn new(
        contents: Rc<String>,
        document: Rc<toml_edit::ImDocument<String>>,
        original_toml: Rc<TomlManifest>,
        normalized_toml: Rc<TomlManifest>,
        summary: Summary,

        default_kind: Option<CompileKind>,
        forced_kind: Option<CompileKind>,
        targets: Vec<Target>,
        exclude: Vec<String>,
        include: Vec<String>,
        links: Option<String>,
        metadata: ManifestMetadata,
        custom_metadata: Option<toml::Value>,
        publish: Option<Vec<String>>,
        replace: Vec<(PackageIdSpec, Dependency)>,
        patch: HashMap<Url, Vec<Dependency>>,
        workspace: WorkspaceConfig,
        unstable_features: Features,
        edition: Edition,
        rust_version: Option<RustVersion>,
        im_a_teapot: Option<bool>,
        default_run: Option<String>,
        metabuild: Option<Vec<String>>,
        resolve_behavior: Option<ResolveBehavior>,
        lint_rustflags: Vec<String>,
        embedded: bool,
    ) -> Manifest {
        Manifest {
            contents,
            document,
            original_toml,
            normalized_toml,
            summary,

            default_kind,
            forced_kind,
            targets,
            warnings: Warnings::new(),
            exclude,
            include,
            links,
            metadata,
            custom_metadata,
            publish,
            replace,
            patch,
            workspace,
            unstable_features,
            edition,
            rust_version,
            im_a_teapot,
            default_run,
            metabuild,
            resolve_behavior,
            lint_rustflags,
            embedded,
        }
    }

    /// The raw contents of the original TOML
    pub fn contents(&self) -> &str {
        self.contents.as_str()
    }
    /// See [`Manifest::normalized_toml`] for what "normalized" means
    pub fn to_normalized_contents(&self) -> CargoResult<String> {
        let toml = toml::to_string_pretty(self.normalized_toml())?;
        Ok(format!("{}\n{}", MANIFEST_PREAMBLE, toml))
    }
    /// Collection of spans for the original TOML
    pub fn document(&self) -> &toml_edit::ImDocument<String> {
        &self.document
    }
    /// The [`TomlManifest`] as parsed from [`Manifest::document`]
    pub fn original_toml(&self) -> &TomlManifest {
        &self.original_toml
    }
    /// The [`TomlManifest`] with all fields expanded
    ///
    /// This is the intersection of what fields need resolving for cargo-publish that also are
    /// useful for the operation of cargo, including
    /// - workspace inheritance
    /// - target discovery
    pub fn normalized_toml(&self) -> &TomlManifest {
        &self.normalized_toml
    }
    pub fn summary(&self) -> &Summary {
        &self.summary
    }
    pub fn summary_mut(&mut self) -> &mut Summary {
        &mut self.summary
    }

    pub fn dependencies(&self) -> &[Dependency] {
        self.summary.dependencies()
    }
    pub fn default_kind(&self) -> Option<CompileKind> {
        self.default_kind
    }
    pub fn forced_kind(&self) -> Option<CompileKind> {
        self.forced_kind
    }
    pub fn exclude(&self) -> &[String] {
        &self.exclude
    }
    pub fn include(&self) -> &[String] {
        &self.include
    }
    pub fn metadata(&self) -> &ManifestMetadata {
        &self.metadata
    }
    pub fn name(&self) -> InternedString {
        self.package_id().name()
    }
    pub fn package_id(&self) -> PackageId {
        self.summary.package_id()
    }
    pub fn targets(&self) -> &[Target] {
        &self.targets
    }
    // It is used by cargo-c, please do not remove it
    pub fn targets_mut(&mut self) -> &mut [Target] {
        &mut self.targets
    }
    pub fn version(&self) -> &Version {
        self.package_id().version()
    }
    pub fn warnings_mut(&mut self) -> &mut Warnings {
        &mut self.warnings
    }
    pub fn warnings(&self) -> &Warnings {
        &self.warnings
    }
    pub fn profiles(&self) -> Option<&TomlProfiles> {
        self.normalized_toml.profile.as_ref()
    }
    pub fn publish(&self) -> &Option<Vec<String>> {
        &self.publish
    }
    pub fn replace(&self) -> &[(PackageIdSpec, Dependency)] {
        &self.replace
    }
    pub fn patch(&self) -> &HashMap<Url, Vec<Dependency>> {
        &self.patch
    }
    pub fn links(&self) -> Option<&str> {
        self.links.as_deref()
    }
    pub fn is_embedded(&self) -> bool {
        self.embedded
    }

    pub fn workspace_config(&self) -> &WorkspaceConfig {
        &self.workspace
    }

    /// Unstable, nightly features that are enabled in this manifest.
    pub fn unstable_features(&self) -> &Features {
        &self.unstable_features
    }

    /// The style of resolver behavior to use, declared with the `resolver` field.
    ///
    /// Returns `None` if it is not specified.
    pub fn resolve_behavior(&self) -> Option<ResolveBehavior> {
        self.resolve_behavior
    }

    /// `RUSTFLAGS` from the `[lints]` table
    pub fn lint_rustflags(&self) -> &[String] {
        self.lint_rustflags.as_slice()
    }

    pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Manifest {
        Manifest {
            summary: self.summary.map_source(to_replace, replace_with),
            ..self
        }
    }

    pub fn feature_gate(&self) -> CargoResult<()> {
        if self.im_a_teapot.is_some() {
            self.unstable_features
                .require(Feature::test_dummy_unstable())
                .with_context(|| {
                    "the `im-a-teapot` manifest key is unstable and may \
                     not work properly in England"
                })?;
        }

        if self.default_kind.is_some() || self.forced_kind.is_some() {
            self.unstable_features
                .require(Feature::per_package_target())
                .with_context(|| {
                    "the `package.default-target` and `package.forced-target` \
                     manifest keys are unstable and may not work properly"
                })?;
        }

        Ok(())
    }

    // Just a helper function to test out `-Z` flags on Cargo
    pub fn print_teapot(&self, gctx: &GlobalContext) {
        if let Some(teapot) = self.im_a_teapot {
            if gctx.cli_unstable().print_im_a_teapot {
                crate::drop_println!(gctx, "im-a-teapot = {}", teapot);
            }
        }
    }

    pub fn edition(&self) -> Edition {
        self.edition
    }

    pub fn rust_version(&self) -> Option<&RustVersion> {
        self.rust_version.as_ref()
    }

    pub fn custom_metadata(&self) -> Option<&toml::Value> {
        self.custom_metadata.as_ref()
    }

    pub fn default_run(&self) -> Option<&str> {
        self.default_run.as_deref()
    }

    pub fn metabuild(&self) -> Option<&Vec<String>> {
        self.metabuild.as_ref()
    }

    pub fn metabuild_path(&self, target_dir: Filesystem) -> PathBuf {
        let hash = short_hash(&self.package_id());
        target_dir
            .into_path_unlocked()
            .join(".metabuild")
            .join(format!("metabuild-{}-{}.rs", self.name(), hash))
    }
}

impl VirtualManifest {
    pub fn new(
        contents: Rc<String>,
        document: Rc<toml_edit::ImDocument<String>>,
        original_toml: Rc<TomlManifest>,
        normalized_toml: Rc<TomlManifest>,
        replace: Vec<(PackageIdSpec, Dependency)>,
        patch: HashMap<Url, Vec<Dependency>>,
        workspace: WorkspaceConfig,
        features: Features,
        resolve_behavior: Option<ResolveBehavior>,
    ) -> VirtualManifest {
        VirtualManifest {
            contents,
            document,
            original_toml,
            normalized_toml,
            replace,
            patch,
            workspace,
            warnings: Warnings::new(),
            features,
            resolve_behavior,
        }
    }

    /// The raw contents of the original TOML
    pub fn contents(&self) -> &str {
        self.contents.as_str()
    }
    /// Collection of spans for the original TOML
    pub fn document(&self) -> &toml_edit::ImDocument<String> {
        &self.document
    }
    /// The [`TomlManifest`] as parsed from [`VirtualManifest::document`]
    pub fn original_toml(&self) -> &TomlManifest {
        &self.original_toml
    }
    /// The [`TomlManifest`] with all fields expanded
    pub fn normalized_toml(&self) -> &TomlManifest {
        &self.normalized_toml
    }

    pub fn replace(&self) -> &[(PackageIdSpec, Dependency)] {
        &self.replace
    }

    pub fn patch(&self) -> &HashMap<Url, Vec<Dependency>> {
        &self.patch
    }

    pub fn workspace_config(&self) -> &WorkspaceConfig {
        &self.workspace
    }

    pub fn profiles(&self) -> Option<&TomlProfiles> {
        self.normalized_toml.profile.as_ref()
    }

    pub fn warnings_mut(&mut self) -> &mut Warnings {
        &mut self.warnings
    }

    pub fn warnings(&self) -> &Warnings {
        &self.warnings
    }

    pub fn unstable_features(&self) -> &Features {
        &self.features
    }

    /// The style of resolver behavior to use, declared with the `resolver` field.
    ///
    /// Returns `None` if it is not specified.
    pub fn resolve_behavior(&self) -> Option<ResolveBehavior> {
        self.resolve_behavior
    }
}

impl Target {
    fn new(src_path: TargetSourcePath, edition: Edition) -> Target {
        Target {
            inner: Arc::new(TargetInner {
                kind: TargetKind::Bin,
                name: String::new(),
                name_inferred: false,
                bin_name: None,
                src_path,
                required_features: None,
                doc: false,
                doctest: false,
                harness: true,
                for_host: false,
                proc_macro: false,
                doc_scrape_examples: RustdocScrapeExamples::Unset,
                edition,
                tested: true,
                benched: true,
            }),
        }
    }

    fn with_path(src_path: PathBuf, edition: Edition) -> Target {
        Target::new(TargetSourcePath::from(src_path), edition)
    }

    pub fn lib_target(
        name: &str,
        crate_targets: Vec<CrateType>,
        src_path: PathBuf,
        edition: Edition,
    ) -> Target {
        let mut target = Target::with_path(src_path, edition);
        target
            .set_kind(TargetKind::Lib(crate_targets))
            .set_name(name)
            .set_doctest(true)
            .set_doc(true);
        target
    }

    pub fn bin_target(
        name: &str,
        bin_name: Option<String>,
        src_path: PathBuf,
        required_features: Option<Vec<String>>,
        edition: Edition,
    ) -> Target {
        let mut target = Target::with_path(src_path, edition);
        target
            .set_kind(TargetKind::Bin)
            .set_name(name)
            .set_binary_name(bin_name)
            .set_required_features(required_features)
            .set_doc(true);
        target
    }

    /// Builds a `Target` corresponding to the `build = "build.rs"` entry.
    pub fn custom_build_target(name: &str, src_path: PathBuf, edition: Edition) -> Target {
        let mut target = Target::with_path(src_path, edition);
        target
            .set_kind(TargetKind::CustomBuild)
            .set_name(name)
            .set_for_host(true)
            .set_benched(false)
            .set_tested(false)
            .set_doc_scrape_examples(RustdocScrapeExamples::Disabled);
        target
    }

    pub fn metabuild_target(name: &str) -> Target {
        let mut target = Target::new(TargetSourcePath::Metabuild, Edition::Edition2018);
        target
            .set_kind(TargetKind::CustomBuild)
            .set_name(name)
            .set_for_host(true)
            .set_benched(false)
            .set_tested(false)
            .set_doc_scrape_examples(RustdocScrapeExamples::Disabled);
        target
    }

    pub fn example_target(
        name: &str,
        crate_targets: Vec<CrateType>,
        src_path: PathBuf,
        required_features: Option<Vec<String>>,
        edition: Edition,
    ) -> Target {
        let kind = if crate_targets.is_empty() || crate_targets.iter().all(|t| *t == CrateType::Bin)
        {
            TargetKind::ExampleBin
        } else {
            TargetKind::ExampleLib(crate_targets)
        };
        let mut target = Target::with_path(src_path, edition);
        target
            .set_kind(kind)
            .set_name(name)
            .set_required_features(required_features)
            .set_tested(false)
            .set_benched(false);
        target
    }

    pub fn test_target(
        name: &str,
        src_path: PathBuf,
        required_features: Option<Vec<String>>,
        edition: Edition,
    ) -> Target {
        let mut target = Target::with_path(src_path, edition);
        target
            .set_kind(TargetKind::Test)
            .set_name(name)
            .set_required_features(required_features)
            .set_benched(false);
        target
    }

    pub fn bench_target(
        name: &str,
        src_path: PathBuf,
        required_features: Option<Vec<String>>,
        edition: Edition,
    ) -> Target {
        let mut target = Target::with_path(src_path, edition);
        target
            .set_kind(TargetKind::Bench)
            .set_name(name)
            .set_required_features(required_features)
            .set_tested(false);
        target
    }

    pub fn name(&self) -> &str {
        &self.inner.name
    }
    pub fn name_inferred(&self) -> bool {
        self.inner.name_inferred
    }
    pub fn crate_name(&self) -> String {
        self.name().replace("-", "_")
    }
    pub fn src_path(&self) -> &TargetSourcePath {
        &self.inner.src_path
    }
    pub fn set_src_path(&mut self, src_path: TargetSourcePath) {
        Arc::make_mut(&mut self.inner).src_path = src_path;
    }
    pub fn required_features(&self) -> Option<&Vec<String>> {
        self.inner.required_features.as_ref()
    }
    pub fn kind(&self) -> &TargetKind {
        &self.inner.kind
    }
    pub fn tested(&self) -> bool {
        self.inner.tested
    }
    pub fn harness(&self) -> bool {
        self.inner.harness
    }
    pub fn documented(&self) -> bool {
        self.inner.doc
    }
    // A proc-macro or build-script.
    pub fn for_host(&self) -> bool {
        self.inner.for_host
    }
    pub fn proc_macro(&self) -> bool {
        self.inner.proc_macro
    }
    pub fn edition(&self) -> Edition {
        self.inner.edition
    }
    pub fn doc_scrape_examples(&self) -> RustdocScrapeExamples {
        self.inner.doc_scrape_examples
    }
    pub fn benched(&self) -> bool {
        self.inner.benched
    }
    pub fn doctested(&self) -> bool {
        self.inner.doctest
    }

    pub fn doctestable(&self) -> bool {
        match self.kind() {
            TargetKind::Lib(ref kinds) => kinds.iter().any(|k| {
                *k == CrateType::Rlib || *k == CrateType::Lib || *k == CrateType::ProcMacro
            }),
            _ => false,
        }
    }

    pub fn is_lib(&self) -> bool {
        matches!(self.kind(), TargetKind::Lib(_))
    }

    pub fn is_dylib(&self) -> bool {
        match self.kind() {
            TargetKind::Lib(libs) => libs.iter().any(|l| *l == CrateType::Dylib),
            _ => false,
        }
    }

    pub fn is_cdylib(&self) -> bool {
        match self.kind() {
            TargetKind::Lib(libs) => libs.iter().any(|l| *l == CrateType::Cdylib),
            _ => false,
        }
    }

    pub fn is_staticlib(&self) -> bool {
        match self.kind() {
            TargetKind::Lib(libs) => libs.iter().any(|l| *l == CrateType::Staticlib),
            _ => false,
        }
    }

    /// Returns whether this target produces an artifact which can be linked
    /// into a Rust crate.
    ///
    /// This only returns true for certain kinds of libraries.
    pub fn is_linkable(&self) -> bool {
        match self.kind() {
            TargetKind::Lib(kinds) => kinds.iter().any(|k| k.is_linkable()),
            _ => false,
        }
    }

    pub fn is_bin(&self) -> bool {
        *self.kind() == TargetKind::Bin
    }

    pub fn is_example(&self) -> bool {
        matches!(
            self.kind(),
            TargetKind::ExampleBin | TargetKind::ExampleLib(..)
        )
    }

    /// Returns `true` if it is a binary or executable example.
    /// NOTE: Tests are `false`!
    pub fn is_executable(&self) -> bool {
        self.is_bin() || self.is_exe_example()
    }

    /// Returns `true` if it is an executable example.
    pub fn is_exe_example(&self) -> bool {
        // Needed for --all-examples in contexts where only runnable examples make sense
        matches!(self.kind(), TargetKind::ExampleBin)
    }

    pub fn is_test(&self) -> bool {
        *self.kind() == TargetKind::Test
    }
    pub fn is_bench(&self) -> bool {
        *self.kind() == TargetKind::Bench
    }
    pub fn is_custom_build(&self) -> bool {
        *self.kind() == TargetKind::CustomBuild
    }

    /// Returns the arguments suitable for `--crate-type` to pass to rustc.
    pub fn rustc_crate_types(&self) -> Vec<CrateType> {
        self.kind().rustc_crate_types()
    }

    pub fn set_tested(&mut self, tested: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).tested = tested;
        self
    }
    pub fn set_benched(&mut self, benched: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).benched = benched;
        self
    }
    pub fn set_doctest(&mut self, doctest: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).doctest = doctest;
        self
    }
    pub fn set_for_host(&mut self, for_host: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).for_host = for_host;
        self
    }
    pub fn set_proc_macro(&mut self, proc_macro: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).proc_macro = proc_macro;
        self
    }
    pub fn set_edition(&mut self, edition: Edition) -> &mut Target {
        Arc::make_mut(&mut self.inner).edition = edition;
        self
    }
    pub fn set_doc_scrape_examples(
        &mut self,
        doc_scrape_examples: RustdocScrapeExamples,
    ) -> &mut Target {
        Arc::make_mut(&mut self.inner).doc_scrape_examples = doc_scrape_examples;
        self
    }
    pub fn set_harness(&mut self, harness: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).harness = harness;
        self
    }
    pub fn set_doc(&mut self, doc: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).doc = doc;
        self
    }
    pub fn set_kind(&mut self, kind: TargetKind) -> &mut Target {
        Arc::make_mut(&mut self.inner).kind = kind;
        self
    }
    pub fn set_name(&mut self, name: &str) -> &mut Target {
        Arc::make_mut(&mut self.inner).name = name.to_string();
        self
    }
    pub fn set_name_inferred(&mut self, inferred: bool) -> &mut Target {
        Arc::make_mut(&mut self.inner).name_inferred = inferred;
        self
    }
    pub fn set_binary_name(&mut self, bin_name: Option<String>) -> &mut Target {
        Arc::make_mut(&mut self.inner).bin_name = bin_name;
        self
    }
    pub fn set_required_features(&mut self, required_features: Option<Vec<String>>) -> &mut Target {
        Arc::make_mut(&mut self.inner).required_features = required_features;
        self
    }
    pub fn binary_filename(&self) -> Option<String> {
        self.inner.bin_name.clone()
    }
    pub fn description_named(&self) -> String {
        match self.kind() {
            TargetKind::Lib(..) => "lib".to_string(),
            TargetKind::Bin => format!("bin \"{}\"", self.name()),
            TargetKind::Test => format!("test \"{}\"", self.name()),
            TargetKind::Bench => format!("bench \"{}\"", self.name()),
            TargetKind::ExampleLib(..) | TargetKind::ExampleBin => {
                format!("example \"{}\"", self.name())
            }
            TargetKind::CustomBuild => "build script".to_string(),
        }
    }
}

impl fmt::Display for Target {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.kind() {
            TargetKind::Lib(..) => write!(f, "Target(lib)"),
            TargetKind::Bin => write!(f, "Target(bin: {})", self.name()),
            TargetKind::Test => write!(f, "Target(test: {})", self.name()),
            TargetKind::Bench => write!(f, "Target(bench: {})", self.name()),
            TargetKind::ExampleBin | TargetKind::ExampleLib(..) => {
                write!(f, "Target(example: {})", self.name())
            }
            TargetKind::CustomBuild => write!(f, "Target(script)"),
        }
    }
}

impl Warnings {
    fn new() -> Warnings {
        Warnings(Vec::new())
    }

    pub fn add_warning(&mut self, s: String) {
        self.0.push(DelayedWarning {
            message: s,
            is_critical: false,
        })
    }

    pub fn add_critical_warning(&mut self, s: String) {
        self.0.push(DelayedWarning {
            message: s,
            is_critical: true,
        })
    }

    pub fn warnings(&self) -> &[DelayedWarning] {
        &self.0
    }
}
