/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::iter;

use allocative::Allocative;
use derive_more::Display;
use dupe::Dupe;
use serde::Serialize;
use serde::Serializer;
use static_assertions::assert_eq_size;
use thiserror::Error;

use crate::ascii_char_set::AsciiCharSet;
use crate::configuration::data::ConfigurationData;
use crate::configuration::pair::Configuration;
use crate::configuration::pair::ConfigurationNoExec;
use crate::target::label::ConfiguredTargetLabel;
use crate::target::label::TargetLabel;

#[derive(
    Display, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative
)]
pub struct ProviderName(String);

#[derive(Error, Debug)]
#[error(
    "Invalid provider name `{}`. Inner providers names can only contain non-empty alpha numeric characters, and symbols `,`, '=', `-`, `/`, `+` and `_`. No other characters are allowed.",
    _0
)]
struct InvalidProviderName(String);

impl ProviderName {
    pub fn as_str(&self) -> &str {
        &self.0
    }

    pub fn new_unchecked(name: String) -> ProviderName {
        ProviderName(name)
    }

    pub fn new(name: String) -> anyhow::Result<ProviderName> {
        Self::verify(&name)?;
        Ok(ProviderName(name))
    }

    fn verify(name: &str) -> anyhow::Result<()> {
        const VALID_CHARS: &str =
            r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_\\/.=,+-";
        const SET: AsciiCharSet = AsciiCharSet::new(VALID_CHARS);

        if name.is_empty() || name.as_bytes().iter().any(|&b| !SET.contains(b)) {
            return Err(InvalidProviderName(name.to_owned()).into());
        }

        Ok(())
    }
}

#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub enum NonDefaultProvidersName {
    Named(Box<[ProviderName]>),
    // For some flavors from buck1, we can translate them to ProvidersName::Named
    // as we know that we can implement them as a subtarget. For many flavored targets,
    // we can't do that. For those cases, we parse them to this "UnrecognizedFlavor" so
    // that we can defer any errors related to us not supporting it.
    UnrecognizedFlavor(Box<str>),
    // TODO(cjhopman): We should add an InferredNamed for flavors where we infer a name
    // so that we can display them in their original form.
}

///
/// A 'ProvidersName' is an optional String label that refers to the specific
/// set of inner providers of a rule.
/// It should be non-empty alphanumeric characters, '/', '.', ',', '-','=',
/// and'_' character. All other special characters including spaces are
/// prohibited.
#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub enum ProvidersName {
    Default,
    NonDefault(Box<NonDefaultProvidersName>),
}

assert_eq_size!(ProvidersName, [usize; 1]);

impl Default for ProvidersName {
    fn default() -> Self {
        Self::Default
    }
}

impl Display for ProvidersName {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            ProvidersName::Default => {
                write!(f, "")
            }
            ProvidersName::NonDefault(box NonDefaultProvidersName::Named(names)) => {
                for name in &**names {
                    write!(f, "[{}]", name)?;
                }
                Ok(())
            }
            ProvidersName::NonDefault(box NonDefaultProvidersName::UnrecognizedFlavor(s)) => {
                write!(f, "#{}", s)
            }
        }
    }
}

impl ProvidersName {
    pub fn push(&self, name: ProviderName) -> Self {
        let items = match self {
            ProvidersName::Default => vec![name],
            ProvidersName::NonDefault(x) => match &**x {
                NonDefaultProvidersName::Named(xs) => {
                    xs.iter().cloned().chain(iter::once(name)).collect()
                }
                NonDefaultProvidersName::UnrecognizedFlavor(_) => return self.clone(),
            },
        };
        ProvidersName::NonDefault(Box::new(NonDefaultProvidersName::Named(
            items.into_boxed_slice(),
        )))
    }
}

/// A unconfigured 'ProvidersLabel'. This contains a 'TargetLabel' referring to
/// the node on the static graph that produces this provider, and a 'name' is
/// the 'ProvidersName' referring to the specific set of inner providers of a
/// rule.
#[derive(
    Clone, Debug, Display, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative
)]
#[display(fmt = "{}{}", target, name)]
pub struct ProvidersLabel {
    target: TargetLabel,
    name: ProvidersName,
}

assert_eq_size!(ProvidersLabel, [usize; 2]);

impl ProvidersLabel {
    pub fn new(target: TargetLabel, name: ProvidersName) -> Self {
        ProvidersLabel { target, name }
    }

    pub fn default_for(target: TargetLabel) -> Self {
        Self::new(target, ProvidersName::Default)
    }

    pub fn into_parts(self) -> (TargetLabel, ProvidersName) {
        (self.target, self.name)
    }

    pub fn target(&self) -> &TargetLabel {
        &self.target
    }

    pub fn name(&self) -> &ProvidersName {
        &self.name
    }

    /// Creates a 'ConfiguredProvidersLabel' from ['Self'] based on the provided
    /// configuration.
    pub fn configure(&self, cfg: ConfigurationData) -> ConfiguredProvidersLabel {
        ConfiguredProvidersLabel {
            target: self.target.configure(cfg),
            name: self.name.clone(),
        }
    }

    /// Like `configure`, but forces the execution configuration too.
    pub fn configure_with_exec(
        &self,
        cfg: ConfigurationData,
        exec_cfg: ConfigurationData,
    ) -> ConfiguredProvidersLabel {
        ConfiguredProvidersLabel {
            target: self.target.configure_with_exec(cfg, exec_cfg),
            name: self.name.clone(),
        }
    }

    #[inline]
    pub fn configure_pair(&self, cfg_pair: Configuration) -> ConfiguredProvidersLabel {
        ConfiguredProvidersLabel {
            target: self.target.configure_pair(cfg_pair),
            name: self.name.clone(),
        }
    }

    #[inline]
    pub fn configure_pair_no_exec(&self, cfg: ConfigurationNoExec) -> ConfiguredProvidersLabel {
        self.configure_pair(cfg.cfg_pair().dupe())
    }

    /// Determines whether a string, **IF IT IS LATER COERCED** would be a relative label.
    ///
    /// This function **DOES NOT** validate the entire label string.
    pub fn maybe_relative_label(raw_label: &str) -> bool {
        raw_label.starts_with(':')
    }
}

impl Serialize for ProvidersLabel {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

///
/// A configured 'ProvidersLabel'.
#[derive(
    Clone, Debug, Display, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative
)]
#[display(fmt = "{}{} ({})", "target.unconfigured()", "name", "target.cfg()")]
pub struct ConfiguredProvidersLabel {
    target: ConfiguredTargetLabel,
    name: ProvidersName,
}

impl ConfiguredProvidersLabel {
    pub fn new(target: ConfiguredTargetLabel, name: ProvidersName) -> Self {
        Self { target, name }
    }

    pub fn target(&self) -> &ConfiguredTargetLabel {
        &self.target
    }

    #[inline]
    pub fn cfg(&self) -> &ConfigurationData {
        self.target.cfg()
    }

    pub fn unconfigured(&self) -> ProvidersLabel {
        ProvidersLabel::new(self.target.unconfigured().dupe(), self.name.clone())
    }

    pub fn name(&self) -> &ProvidersName {
        &self.name
    }
}

impl Serialize for ConfiguredProvidersLabel {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

/// [`ProvidersLabel`] or [`ConfiguredProvidersLabel`].
pub trait ProvidersLabelMaybeConfigured: Display + Allocative {}

impl ProvidersLabelMaybeConfigured for ProvidersLabel {}
impl ProvidersLabelMaybeConfigured for ConfiguredProvidersLabel {}

pub mod testing {
    use gazebo::prelude::*;

    use super::*;
    use crate::package::PackageLabel;
    use crate::target::label::TargetLabel;
    use crate::target::name::TargetNameRef;

    pub trait ProvidersLabelTestExt {
        fn testing_new(
            cell: &str,
            package: &str,
            target: &str,
            name: Option<&[&str]>,
        ) -> ProvidersLabel;
    }

    impl ProvidersLabelTestExt for ProvidersLabel {
        fn testing_new(
            cell: &str,
            package: &str,
            target: &str,
            name: Option<&[&str]>,
        ) -> ProvidersLabel {
            ProvidersLabel::new(
                TargetLabel::new(
                    PackageLabel::testing_new(cell, package),
                    TargetNameRef::new(target).unwrap(),
                ),
                match name {
                    Some(n) => ProvidersName::NonDefault(Box::new(NonDefaultProvidersName::Named(
                        n.map(|s| ProviderName::new((*s).to_owned()).unwrap())
                            .into_boxed_slice(),
                    ))),
                    _ => ProvidersName::Default,
                },
            )
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::provider::label::ProviderName;
    use crate::provider::label::ProvidersLabel;

    #[test]
    fn providers_name_validation() {
        ProviderName::new("foo".to_owned()).unwrap();
        ProviderName::new("foo_-,.=+/1".to_owned()).unwrap();
        assert!(ProviderName::new("foo bar".to_owned()).is_err());
        assert!(ProviderName::new("foo@bar".to_owned()).is_err());
    }

    #[test]
    fn providers_label_maybe_relative() {
        assert!(ProvidersLabel::maybe_relative_label(":foo"));
        assert!(ProvidersLabel::maybe_relative_label(":foo[bar]"));
        assert!(ProvidersLabel::maybe_relative_label(":invalid@label"));
        assert!(!ProvidersLabel::maybe_relative_label("root//:bar"));
        assert!(!ProvidersLabel::maybe_relative_label("root//foo:foo"));
        assert!(!ProvidersLabel::maybe_relative_label(
            "root//foo:invalid@label"
        ));
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//!
//! 'Provider's are advertised data exposed by rules. These contain typed data
//! that can be requested. The data may be materialized lazily or eagerly.
//!
//! 'ProvidersLabel's are labels/keys that uniquely map to a specific set of
//! 'Provider's exposed by a rule. There are two versions of these labels, one
//! with 'Configuration's attached, and one without.
//!
//! For example, one might imagine a `thrift_rule` exposing itself as various
//! languages, for which specific languages can be referred to via depends as
//! `some_rule[java]` or `some_rule[cxx]`.

pub mod flavors;
pub mod id;
pub mod label;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::cmp::Ordering;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Display;
use std::hash::Hash;
use std::hash::Hasher;
use std::str;

use allocative::Allocative;
use buck2_data::ToProtoMessage;
use dupe::Dupe;
use serde::Serialize;
use serde::Serializer;
use starlark_map::StarlarkHashValue;
use triomphe::ThinArc;

use crate::cells::name::CellName;
use crate::cells::paths::CellRelativePath;
use crate::cells::CellResolver;
use crate::configuration::data::ConfigurationData;
use crate::configuration::pair::Configuration;
use crate::configuration::pair::ConfigurationNoExec;
use crate::package::PackageLabel;
use crate::pattern::lex_target_pattern;
use crate::pattern::pattern_type::TargetPatternExtra;
use crate::pattern::ParsedPattern;
use crate::target::name::TargetNameRef;

#[derive(Eq, PartialEq, Allocative)]
struct TargetLabelHeader {
    /// Hash of target label (not package, not name).
    /// Place hash first to make equality check faster.
    hash: StarlarkHashValue,
    pkg: PackageLabel,
    // TODO(nga): this struct has 4 bytes of padding.
}

/// 'TargetLabel' that uniquely maps to a 'target'
/// It contains a 'Package' which is the 'Package' defined by the build fine
/// that contains this 'target', and a 'name' which is a 'TargetName'
/// representing the target name given to the particular target.
#[derive(Clone, derive_more::Display, Eq, PartialEq, Allocative)]
#[display(fmt = "{}:{}", "self.pkg()", "self.name()")]
pub struct TargetLabel(
    ThinArc<
        TargetLabelHeader,
        // `u8` type argument means `ThinArc` stores `[u8]` inline.
        // We store string target name in that `[u8]`.
        u8,
    >,
);

impl Debug for TargetLabel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("TargetLabel")
            .field("pkg", &self.pkg())
            .field("name", &self.name())
            .finish()
    }
}

impl Dupe for TargetLabel {}

#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for TargetLabel {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.header.header.hash.hash(state);
    }
}

impl Ord for TargetLabel {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        (self.pkg(), self.name()).cmp(&(other.pkg(), other.name()))
    }
}

impl PartialOrd for TargetLabel {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl TargetLabel {
    pub fn new(pkg: PackageLabel, name: &TargetNameRef) -> Self {
        // TODO(nga): unnecessary to take `TargetName` by value.
        let hash = StarlarkHashValue::new(&(pkg.dupe(), &name));
        TargetLabel(ThinArc::from_header_and_slice(
            TargetLabelHeader { hash, pkg },
            name.as_str().as_bytes(),
        ))
    }

    #[inline]
    pub fn pkg(&self) -> PackageLabel {
        self.0.header.header.pkg.dupe()
    }

    #[inline]
    pub fn name(&self) -> &TargetNameRef {
        let name = unsafe { str::from_utf8_unchecked(&self.0.slice) };
        TargetNameRef::unchecked_new(name)
    }

    /// Creates a 'ConfiguredTargetLabel' from ['Self'] based on the provided
    /// configuration.
    #[inline]
    pub fn configure(&self, cfg: ConfigurationData) -> ConfiguredTargetLabel {
        self.configure_pair(Configuration::new(cfg, None))
    }

    /// Like `configure`, but forces the execution configuration too.
    #[inline]
    pub fn configure_with_exec(
        &self,
        cfg: ConfigurationData,
        exec_cfg: ConfigurationData,
    ) -> ConfiguredTargetLabel {
        self.configure_pair(Configuration::new(cfg, Some(exec_cfg)))
    }

    #[inline]
    pub fn configure_pair(&self, cfg_pair: Configuration) -> ConfiguredTargetLabel {
        ConfiguredTargetLabel {
            target: self.dupe(),
            cfg_pair,
        }
    }

    #[inline]
    pub fn configure_pair_no_exec(&self, cfg: ConfigurationNoExec) -> ConfiguredTargetLabel {
        self.configure_pair(cfg.cfg_pair().dupe())
    }

    pub fn parse(
        label: &str,
        cell_name: CellName,
        cell_resolver: &CellResolver,
    ) -> anyhow::Result<TargetLabel> {
        let (pkg, name, TargetPatternExtra) =
            ParsedPattern::<TargetPatternExtra>::parse_precise(label, cell_name, cell_resolver)?
                .as_literal(label)?;
        Ok(TargetLabel::new(pkg, name.as_ref()))
    }

    /// Simple and incorrect target label parser which can be used in tests.
    pub fn testing_parse(target_label: &str) -> TargetLabel {
        let parts = lex_target_pattern(target_label, false).expect("failed to parse");
        let cell_name = CellName::testing_new(parts.cell_alias.expect("must have cell name"));

        let pattern_data = parts
            .pattern
            .reject_ambiguity()
            .expect("target label must be unambiguous");
        let (target_name, TargetPatternExtra) =
            pattern_data.target().expect("target label must be precise");

        TargetLabel::new(
            PackageLabel::new(
                cell_name,
                CellRelativePath::new(pattern_data.package_path()),
            ),
            target_name,
        )
    }
}

impl Serialize for TargetLabel {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl ToProtoMessage for TargetLabel {
    type Message = buck2_data::TargetLabel;

    fn as_proto(&self) -> Self::Message {
        buck2_data::TargetLabel {
            package: self.pkg().to_string(),
            name: self.name().to_string(),
        }
    }
}

/// 'ConfiguredTargetLabel' are 'TargetLabel's with an 'Configuration' attached.
/// These uniquely map to nodes of the build graph with 'Configuration's
/// applied.
#[derive(Clone, Dupe, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Allocative)]
pub struct ConfiguredTargetLabel {
    target: TargetLabel,
    cfg_pair: Configuration,
}

impl Display for ConfiguredTargetLabel {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} ({})", self.target, self.cfg())?;
        if let Some(exec_cfg) = self.exec_cfg() {
            write!(f, " ({})", exec_cfg)?;
        }
        Ok(())
    }
}

impl ConfiguredTargetLabel {
    #[inline]
    pub fn pkg(&self) -> PackageLabel {
        self.target.pkg()
    }

    #[inline]
    pub fn name(&self) -> &TargetNameRef {
        self.target.name()
    }

    #[inline]
    pub fn unconfigured(&self) -> &TargetLabel {
        &self.target
    }

    #[inline]
    pub fn cfg_pair(&self) -> &Configuration {
        &self.cfg_pair
    }

    #[inline]
    pub fn cfg(&self) -> &ConfigurationData {
        self.cfg_pair.cfg()
    }

    #[inline]
    pub fn exec_cfg(&self) -> Option<&ConfigurationData> {
        self.cfg_pair.exec_cfg()
    }

    pub fn testing_parse(label: &str, cfg: ConfigurationData) -> ConfiguredTargetLabel {
        TargetLabel::testing_parse(label).configure(cfg)
    }
}

impl Serialize for ConfiguredTargetLabel {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_str(self)
    }
}

impl ToProtoMessage for ConfiguredTargetLabel {
    type Message = buck2_data::ConfiguredTargetLabel;

    fn as_proto(&self) -> Self::Message {
        buck2_data::ConfiguredTargetLabel {
            label: Some(self.unconfigured().as_proto()),
            configuration: Some(self.cfg().as_proto()),
            execution_configuration: self.exec_cfg().map(ToProtoMessage::as_proto),
        }
    }
}

/// [`TargetLabel`] or [`ConfiguredTargetLabel`]
pub trait TargetLabelMaybeConfigured: Display {}

impl TargetLabelMaybeConfigured for TargetLabel {}
impl TargetLabelMaybeConfigured for ConfiguredTargetLabel {}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//!
//! A 'target' is an instance of a rule declared in the build file. Each
//! 'target' is a node on the 'static graph'. Targets are determined by parsing;
//! no extra analysis or building is necessary to determine the 'target's
//! available.
//!
//! For example, the below is a target, defined in some 'Package' with the given
//! name "foo".
//!
//!```ignored
//! java_library(
//!    name = "foo",
//!    srcs = [ ... ],
//!    ...
//! )
//! ```
//!
//! Target names are limited to non-empty alpha numeric characters `,`, `=`,
//! `-`, `/`, and `_`. No other special characters, e.g. spaces, are allowed.
//! Currently, `+` is allow for backwards compatibility but may be removed.
//!
//! 'TargetLabel's are labels/keys that uniquely map to a 'target' in the static
//! graph. These are of the form `<cell>//<path to build file>:<target name>`.
//! e.g. `mycell//my/package/path:my_target`, where `mycell` is the cell,
//! `my/package/path` is the package, and `my_target` is the target name
//! belonging to the package.

pub mod label;
pub mod name;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::borrow::Borrow;
use std::ops::Deref;

use allocative::Allocative;
use buck2_util::arc_str::ThinArcStr;
use dupe::Dupe;

use crate::ascii_char_set::AsciiCharSet;

pub const EQ_SIGN_SUBST: &str = "_eqsb_";

/// 'TargetName' is the name given to a particular target.
/// e.g. `foo` in the label `fbsource//package/path:foo`.
#[derive(
    Clone,
    Debug,
    Dupe,
    derive_more::Display,
    Hash,
    Eq,
    PartialEq,
    Ord,
    PartialOrd,
    Allocative
)]
// TODO intern this?
pub struct TargetName(ThinArcStr);

#[derive(thiserror::Error, Debug)]
enum TargetNameError {
    #[error(
        "Invalid target name `{}`. Target names are non-empty strings and can only contain alpha numeric characters, and symbols \
        `,`, `.`, `=`, `-`, `/`, `~`, `@`, `!`, `+`, `$`, and `_`. No other characters are allowed.",
        _0
    )]
    InvalidName(String),
    #[error(
        "found inner providers label when target names are expected. remove `[...]` portion of the target name from `{}`",
        _0
    )]
    FoundProvidersLabel(String),
    #[error("Target name `{0}` has special character `{1}`, which is discouraged")]
    LabelHasSpecialCharacter(String, char),
    #[error("Target name must not be equal to `...`")]
    DotDotDot,
    #[error("Target name `{0}` should not contain pattern: `{1}`")]
    InvalidPattern(String, String),
}

impl TargetName {
    #[inline]
    pub fn new(name: &str) -> anyhow::Result<Self> {
        TargetNameRef::new(name)?;
        Ok(Self(ThinArcStr::from(name)))
    }

    #[inline]
    pub fn unchecked_new(name: &str) -> Self {
        Self(ThinArcStr::from(name))
    }

    fn bad_name_error(name: &str) -> anyhow::Error {
        if let Some((_, p)) = name.split_once('[') {
            if p.contains(']') {
                return TargetNameError::FoundProvidersLabel(name.to_owned()).into();
            }
        }
        TargetNameError::InvalidName(name.to_owned()).into()
    }

    fn verify(name: &str) -> anyhow::Result<()> {
        const VALID_CHARS: &str =
            r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_,.=-\/~@!+$";
        const SET: AsciiCharSet = AsciiCharSet::new(VALID_CHARS);

        if name.is_empty() || !name.as_bytes().iter().all(|&b| SET.contains(b)) {
            return Err(Self::bad_name_error(name));
        }

        if name.contains(EQ_SIGN_SUBST) {
            return Err(
                TargetNameError::InvalidPattern(name.to_owned(), EQ_SIGN_SUBST.to_owned()).into(),
            );
        }

        if name == "..." {
            soft_error!("label_is_dot_dot_dot", TargetNameError::DotDotDot.into())?;
        }
        if name.contains(',') {
            soft_error!(
                "label_has_comma",
                TargetNameError::LabelHasSpecialCharacter(name.to_owned(), ',').into(),
                quiet: true
            )?;
        }
        if name.contains('$') {
            soft_error!(
                "label_has_dollar_sign",
                TargetNameError::LabelHasSpecialCharacter(name.to_owned(), '$').into(),
                quiet: true
            )?;
        }

        Ok(())
    }

    // Generic `as_ref` confuses typechecker because of overloads.
    #[allow(clippy::should_implement_trait)]
    #[inline]
    pub fn as_ref(&self) -> &TargetNameRef {
        TargetNameRef::unchecked_new(&self.0)
    }
}

impl AsRef<str> for TargetName {
    #[inline]
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl Borrow<str> for TargetName {
    #[inline]
    fn borrow(&self) -> &str {
        &self.0
    }
}

impl PartialEq<String> for TargetName {
    #[inline]
    fn eq(&self, other: &String) -> bool {
        *self.0 == *other
    }
}

impl PartialEq<str> for TargetName {
    #[inline]
    fn eq(&self, other: &str) -> bool {
        *self.0 == *other
    }
}

impl Deref for TargetName {
    type Target = TargetNameRef;

    fn deref(&self) -> &TargetNameRef {
        self.as_ref()
    }
}

#[derive(Debug, derive_more::Display, Hash, Eq, PartialEq, Ord, PartialOrd)]
#[repr(transparent)]
pub struct TargetNameRef(str);

impl TargetNameRef {
    pub fn new(name: &str) -> anyhow::Result<&TargetNameRef> {
        TargetName::verify(name)?;
        Ok(TargetNameRef::unchecked_new(name))
    }

    #[inline]
    pub fn unchecked_new(name: &str) -> &TargetNameRef {
        unsafe {
            // SAFETY: `repr(transparent)`.
            &*(name as *const str as *const TargetNameRef)
        }
    }

    #[inline]
    pub fn as_str(&self) -> &str {
        &self.0
    }

    #[inline]
    pub fn to_owned(&self) -> TargetName {
        TargetName::unchecked_new(&self.0)
    }
}

impl Borrow<TargetNameRef> for TargetName {
    #[inline]
    fn borrow(&self) -> &TargetNameRef {
        self.as_ref()
    }
}

impl AsRef<str> for TargetNameRef {
    #[inline]
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

#[cfg(test)]
mod tests {
    use std::hash::Hash;
    use std::hash::Hasher;

    use buck2_util::arc_str::ThinArcStr;

    use crate::target::name::TargetName;
    use crate::target::name::TargetNameRef;

    #[test]
    fn target_name_validation() {
        assert_eq!(
            TargetName::new("foo").unwrap(),
            TargetName(ThinArcStr::from("foo"))
        );
        assert_eq!(
            // Copied allowed symbols from above.
            // `.`, `-`, `/`, `~`, `@`, `!`, `+` and `_`
            // `,`, `$`, and `=` are currently soft errors and should eventually be removed.
            TargetName::new("foo.-/~@!+_1").unwrap(),
            TargetName(ThinArcStr::from("foo.-/~@!+_1"))
        );
        assert!(TargetName::new("foo bar").is_err());
        assert!(TargetName::new("foo?bar").is_err());
        assert!(TargetName::new("foo_eqsb_bar").is_err());

        if let Err(e) = TargetName::new("target[label]") {
            let msg = format!("{:#}", e);
            assert!(msg.contains("found inner providers label when target names are expected. remove `[...]` portion of the target name from `target[label]`"), "{}", msg);
        } else {
            panic!("should have gotten an error")
        }
    }

    #[test]
    fn test_value_and_ref_hashes_equal() {
        fn hash<T: Hash + ?Sized>(t: &T) -> u64 {
            let mut hasher = std::collections::hash_map::DefaultHasher::new();
            t.hash(&mut hasher);
            hasher.finish()
        }

        assert_eq!(
            hash(TargetNameRef::unchecked_new("foo")),
            hash(&TargetName::unchecked_new("foo"))
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Mutex;
use std::sync::MutexGuard;
use std::sync::Once;

use buck2_core::error::initialize;
use buck2_core::error::reset_soft_error_counters;
use buck2_core::error::StructuredErrorOptions;
use buck2_core::is_open_source;
use buck2_core::soft_error;

static RESULT: Mutex<Vec<String>> = Mutex::new(Vec::new());

fn mock_handler(
    category: &str,
    err: &anyhow::Error,
    loc: (&str, u32, u32),
    options: StructuredErrorOptions,
) {
    RESULT.lock().unwrap().push(format!(
        "{:?}, : {} : {} : {}",
        loc, err, category, options.quiet
    ));
}

fn test_init() -> MutexGuard<'static, ()> {
    // Tests in Rust can be executed concurrently, and these tests work with global state,
    // so use mutex to ensure we only run one test at a time.
    static TEST_MUTEX: Mutex<()> = Mutex::new(());
    let guard = TEST_MUTEX.lock().unwrap();

    static ONCE: Once = Once::new();
    ONCE.call_once(|| {
        initialize(Box::new(mock_handler)).unwrap();
    });

    RESULT.lock().unwrap().clear();

    guard
}

#[test]
fn test_soft_error() {
    if is_open_source() {
        return; // Errors are always hard in open source
    }
    let _guard = test_init();

    let before_error_line = line!();
    let _ignore_hard_error = soft_error!(
        "test_logged_soft_error",
        anyhow::anyhow!("Should be logged")
    );
    assert_eq!(
        Some(&format!(
            "({:?}, {}, 30), : Should be logged : test_logged_soft_error : false",
            file!(),
            before_error_line + 1,
        )),
        RESULT.lock().unwrap().get(0)
    );
}

#[test]
fn test_reset_counters() {
    if is_open_source() {
        return; // Errors are always hard in open source
    }
    let _guard = test_init();

    assert_eq!(0, RESULT.lock().unwrap().len(), "Sanity check");

    for _ in 0..100 {
        let _ignore = soft_error!("test_reset_counters", anyhow::anyhow!("Message"));
    }

    assert_eq!(
        10,
        RESULT.lock().unwrap().len(),
        "Should be logged 10 times"
    );

    reset_soft_error_counters();

    for _ in 0..100 {
        let _ignore = soft_error!("test_reset_counters", anyhow::anyhow!("Message"));
    }

    assert_eq!(
        20,
        RESULT.lock().unwrap().len(),
        "Should be logged 10 more times"
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt::Display;
use std::hash::Hash;

use starlark_map::small_map::SmallMap;
use starlark_map::Hashed;
use thiserror::Error;

use crate::graph::Graph;
use crate::graph::GraphVertex;
use crate::types::VertexData;
use crate::types::VertexId;
use crate::types::VertexKeys;

#[derive(Error, Debug)]
pub enum PushError<K: Display> {
    #[error("duplicate key: {key}")]
    DuplicateKey { key: K },

    #[error("overflow")]
    Overflow,
}

pub struct GraphBuilder<K: Hash + Eq, D> {
    keys: SmallMap<K, VertexId>,
    data: Vec<D>,
    vertices: Vec<GraphVertex>,
    edges: Vec<VertexId>,
}

impl<K, D> GraphBuilder<K, D>
where
    K: Hash + Eq + Display,
{
    pub fn new() -> Self {
        Self {
            keys: Default::default(),
            data: Default::default(),
            vertices: Default::default(),
            edges: Default::default(),
        }
    }

    pub fn push(
        &mut self,
        key: K,
        deps: impl Iterator<Item = K>,
        data: D,
    ) -> Result<(), PushError<K>> {
        let idx: u32 = self
            .vertices
            .len()
            .try_into()
            .map_err(|_| PushError::Overflow)?;

        // We need to constrain ourselves to i32::MAX in order tosupport optionals.
        if idx > i32::MAX as u32 {
            return Err(PushError::Overflow);
        }

        let idx = VertexId::new(idx);

        let hashed = Hashed::new(key);
        if self.keys.contains_key_hashed(hashed.as_ref()) {
            return Err(PushError::DuplicateKey {
                key: hashed.into_key(),
            });
        }
        self.keys.insert_hashed(hashed, idx);

        self.data.push(data);

        let edges_idx = self
            .edges
            .len()
            .try_into()
            .map_err(|_| PushError::Overflow)?;

        let mut edges_count = 0;

        for dep in deps {
            let dep_idx = match self.keys.get(&dep) {
                Some(idx) => idx,
                None => continue,
            };
            self.edges.push(*dep_idx);
            edges_count += 1;
        }

        self.vertices.push(GraphVertex {
            edges_idx,
            edges_count,
        });

        Ok(())
    }

    pub fn finish(self) -> (Graph, VertexKeys<K>, VertexData<D>) {
        (
            Graph {
                edges: self.edges,
                vertices: VertexData::new(self.vertices),
            },
            VertexKeys::new(self.keys),
            VertexData::new(self.data),
        )
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_finish() {
        let mut builder = GraphBuilder::new();
        builder
            .push("foo", std::iter::empty(), "foo value")
            .unwrap();
        builder
            .push("bar", std::iter::empty(), "bar value")
            .unwrap();
        let (_graph, keys, data) = builder.finish();
        let foo_idx = keys.get(&"foo").unwrap();
        let bar_idx = keys.get(&"bar").unwrap();

        assert_eq!(keys[foo_idx], "foo");
        assert_eq!(keys[bar_idx], "bar");
        assert_eq!(data[foo_idx], "foo value");
        assert_eq!(data[bar_idx], "bar value");
    }

    #[test]
    fn test_missing_ok() {
        let mut builder = GraphBuilder::new();
        builder
            .push("foo", std::iter::once("bar"), "foo value")
            .unwrap();
    }

    #[test]
    fn test_duplicate_err() {
        let mut builder = GraphBuilder::new();
        builder
            .push("foo", std::iter::empty(), "foo value")
            .unwrap();
        assert!(
            builder
                .push("foo", std::iter::empty(), "foo value")
                .is_err()
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use thiserror::Error;

use crate::types::OptionalVertexId;
use crate::types::VertexData;
use crate::types::VertexId;

#[derive(Copy, Clone)]
pub struct GraphVertex {
    pub edges_idx: u32,
    pub edges_count: u32,
}

#[derive(Clone)]
pub struct Graph {
    pub(crate) vertices: VertexData<GraphVertex>,
    pub(crate) edges: Vec<VertexId>,
}

impl Graph {
    #[inline]
    pub fn iter_vertices(&self) -> impl Iterator<Item = VertexId> + DoubleEndedIterator {
        self.vertices.keys()
    }

    #[inline]
    pub fn iter_edges(&self, idx: VertexId) -> impl Iterator<Item = VertexId> + '_ {
        let vertex = self.vertices[idx];
        let range_from = vertex.edges_idx as usize;
        let range_to = range_from + vertex.edges_count as usize;
        self.edges[range_from..range_to].iter().copied()
    }

    pub fn iter_all_edges(&self) -> impl Iterator<Item = (VertexId, VertexId)> + '_ {
        self.iter_vertices()
            .flat_map(|i| self.iter_edges(i).map(move |j| (i, j)))
    }

    /// Allocate a VertexData with space for each of the vertices in this graph.
    pub fn allocate_vertex_data<T>(&self, default: T) -> VertexData<T>
    where
        T: Clone,
    {
        VertexData::new(vec![default; self.vertices.len()])
    }

    /// Reverse this Graph. The VertexIds are unchanged (they still represent the same vertices so
    /// they can be used to index into VertexData).
    pub fn reversed(&self) -> Self {
        let mut reverse_vertices = self.allocate_vertex_data(GraphVertex {
            edges_idx: 0,
            edges_count: 0,
        });

        // We could compute that count ahead of time while reading, probably not worth it.
        for edge in &self.edges {
            reverse_vertices[*edge].edges_count += 1;
        }

        let mut idx = 0;
        for vertex in reverse_vertices.values_mut() {
            vertex.edges_idx = idx;
            idx += vertex.edges_count;
        }
        assert_eq!(idx as usize, self.edges.len());

        let mut vertex_edge_offset = self.allocate_vertex_data(0usize);
        let mut reverse_edges = vec![VertexId::default(); self.edges.len()];

        for from in self.iter_vertices() {
            for to in self.iter_edges(from) {
                let offset = &mut vertex_edge_offset[to];
                reverse_edges[reverse_vertices[to].edges_idx as usize + *offset] = from;
                *offset += 1;
            }
        }

        Self {
            vertices: reverse_vertices,
            edges: reverse_edges,
        }
    }

    /// Obtain a topological ordering of this graph. The graph must be a DAG (but that's the only
    /// thing that GraphBuilder can construct).
    pub fn topo_sort(&self) -> Result<Vec<VertexId>, TopoSortError> {
        enum Work {
            Push,
            Pop,
        }

        let mut topo_order = vec![VertexId::new(0); self.vertices.len()];

        // A pair of (marked, visited), where marked means this node is on the stack and visited
        // means it was popped off.
        let mut state = self.allocate_vertex_data((false, false));
        let mut current_topo_order_index = self.vertices.len().saturating_sub(1);

        let mut queue = Vec::new();

        for i in self.iter_vertices() {
            queue.push((i, Work::Push));

            while let Some((j, work)) = queue.pop() {
                let (marked, visited) = &mut state[j];

                match work {
                    Work::Push => {
                        if *visited {
                            continue;
                        }

                        if *marked {
                            return Err(TopoSortError::Cycle);
                        }

                        *marked = true;

                        queue.push((j, Work::Pop));
                        queue.extend(self.iter_edges(j).map(|k| (k, Work::Push)));
                    }
                    Work::Pop => {
                        *visited = true;
                        topo_order[current_topo_order_index] = j;
                        current_topo_order_index = current_topo_order_index.saturating_sub(1);
                    }
                }
            }
        }

        Ok(topo_order)
    }

    /// Given a DAG and a *reverse topological* ordering thereof, return the predecessor for each
    /// node after aggregating by runtime.
    pub fn find_longest_paths(
        &self,
        reverse_topo_order: impl IntoIterator<Item = VertexId>,
        weights: &VertexData<u64>,
    ) -> (VertexData<PathCost>, VertexData<OptionalVertexId>) {
        let mut predecessor = self.allocate_vertex_data(OptionalVertexId::none());

        // The runtime for this vertex
        let mut costs = self.allocate_vertex_data(PathCost::default());

        for idx in reverse_topo_order {
            let mut max: Option<(PathCost, VertexId)> = None;

            // Visit my dependencies.
            for from in self.iter_edges(idx) {
                let from_cost = &costs[from];

                let replace = match max {
                    Some((max_cost, _)) => max_cost < *from_cost,
                    _ => true,
                };

                if replace {
                    max = Some((*from_cost, from));
                }
            }

            let me = PathCost {
                runtime: weights[idx],
                len: 1,
            };

            match max {
                Some((cost, vertex)) => {
                    costs[idx] = cost + me;
                    predecessor[idx] = vertex.into();
                }
                None => {
                    costs[idx] = me;
                    predecessor[idx] = OptionalVertexId::none();
                }
            }
        }

        (costs, predecessor)
    }

    pub fn vertices_count(&self) -> usize {
        self.vertices.len()
    }

    pub fn edges_count(&self) -> usize {
        self.edges.len()
    }

    /// Produce a new graph after adding edges. This may produce a multigraph and the graph may
    /// stop being a DAG.
    pub fn add_edges(
        &self,
        add: &impl AddEdges,
        size_hint: impl Into<Option<usize>>,
    ) -> Result<Graph, AddEdgesError> {
        let mut vertices = self.allocate_vertex_data(GraphVertex {
            edges_idx: 0,
            edges_count: 0,
        });

        let mut edges = Vec::with_capacity(self.edges.len() + size_hint.into().unwrap_or_default());
        let mut all_edges_idx = 0;

        for from in self.iter_vertices() {
            let edges_idx = all_edges_idx;
            let mut edges_count = 0;

            for e in self.iter_edges(from) {
                edges.push(e);
                edges_count += 1;
                all_edges_idx += 1;
            }

            for e in add.edges_for_vertex(from) {
                edges.push(e);
                edges_count += 1;
                all_edges_idx += 1;
            }

            vertices[from] = GraphVertex {
                edges_idx,
                edges_count,
            };
        }

        Ok(Graph { vertices, edges })
    }
}

#[derive(Error, Debug)]
pub enum TopoSortError {
    #[error("cycle")]
    Cycle,
}

pub trait AddEdges {
    type EdgeIterator: Iterator<Item = VertexId>;

    fn edges_for_vertex(&self, idx: VertexId) -> Self::EdgeIterator;
}

impl AddEdges for VertexData<OptionalVertexId> {
    type EdgeIterator = std::option::IntoIter<VertexId>;

    #[inline]
    fn edges_for_vertex(&self, idx: VertexId) -> Self::EdgeIterator {
        self[idx].into_option().into_iter()
    }
}

#[derive(Error, Debug)]
pub enum AddEdgesError {
    #[error("overflow")]
    Overflow,
}

#[derive(
    Default,
    Clone,
    Copy,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    derive_more::Add,
    Debug
)]
pub struct PathCost {
    pub runtime: u64,
    pub len: u32,
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::builder::GraphBuilder;
    use crate::test_utils::make_dag;
    use crate::test_utils::seeded_rng;
    use crate::types::VertexKeys;

    const K0: &str = "key0";
    const K1: &str = "key1";
    const K2: &str = "key2";
    const K3: &str = "key3";

    fn test_graph() -> (Graph, VertexKeys<&'static str>, VertexData<&'static str>) {
        let mut builder = GraphBuilder::new();
        builder.push(K3, std::iter::empty(), K3).unwrap();
        builder.push(K2, vec![K3].into_iter(), K2).unwrap();
        builder.push(K1, std::iter::empty(), K1).unwrap();
        builder.push(K0, vec![K1, K2].into_iter(), K0).unwrap();

        builder.finish()
    }

    #[test]
    fn test_iter() {
        let (graph, _keys, data) = test_graph();
        let edges = graph
            .iter_all_edges()
            .map(|(l, r)| (data[l], data[r]))
            .collect::<Vec<_>>();

        assert_eq!(vec![(K2, K3), (K0, K1), (K0, K2)], edges);
    }

    #[test]
    fn test_reverse() {
        let (graph, _keys, data) = test_graph();
        let rev_graph = graph.reversed();
        let rev_edges = rev_graph
            .iter_all_edges()
            .map(|(l, r)| (data[l], data[r]))
            .collect::<Vec<_>>();

        assert_eq!(vec![(K3, K2), (K2, K0), (K1, K0)], rev_edges);
    }

    #[test]
    fn test_topo_sort() {
        let (graph, _keys, data) = test_graph();
        let topo = graph
            .topo_sort()
            .unwrap()
            .into_iter()
            .map(|k| data[k])
            .collect::<Vec<_>>();
        assert_eq!(topo, vec![K0, K1, K2, K3]);
    }

    #[test]
    fn test_topo_sort_empty() {
        let (graph, _, _) = GraphBuilder::<&'static str, ()>::new().finish();
        assert_eq!(graph.topo_sort().unwrap(), vec![]);
    }

    #[test]
    fn test_topo_sort_large() {
        let mut rng = seeded_rng();
        let graph = make_dag(10000, &mut rng).shuffled(&mut rng).0.graph;
        let topo_order = graph.topo_sort().unwrap();
        let ranks = {
            let mut ranks = VertexData::new(vec![0; graph.vertices.len()]);
            for (rank, idx) in topo_order.iter().enumerate() {
                ranks[*idx] = rank;
            }
            ranks
        };

        for (i, j) in graph.iter_all_edges() {
            assert!(ranks[i] < ranks[j]);
        }
    }

    #[test]
    fn test_topo_sort_cycle() {
        let mut builder = GraphBuilder::new();
        builder.push(K1, std::iter::empty(), ()).unwrap();
        builder.push(K0, std::iter::empty(), ()).unwrap();
        let (graph, keys, _data) = builder.finish();

        let v0 = keys.get(&K0).unwrap();
        let v1 = keys.get(&K1).unwrap();

        let mut new_edges = graph.allocate_vertex_data(OptionalVertexId::none());
        new_edges[v0] = v1.into();
        new_edges[v1] = v0.into();
        let graph = graph.add_edges(&new_edges, None).unwrap();

        assert!(graph.topo_sort().is_err());
    }

    #[test]
    fn test_longest_paths() {
        let (graph, keys, _data) = test_graph();

        // Get the vertex ids
        let v0 = keys.get(&K0).unwrap();
        let v1 = keys.get(&K1).unwrap();
        let v2 = keys.get(&K2).unwrap();
        let v3 = keys.get(&K3).unwrap();

        let mut weights = graph.allocate_vertex_data(0);
        weights[v0] = 5;
        weights[v1] = 15;
        weights[v2] = 10;
        weights[v3] = 20;

        let topo = graph.topo_sort().unwrap();
        let (costs, predecessor) = graph.find_longest_paths(topo.iter().rev().copied(), &weights);

        assert_eq!(
            costs[v3],
            PathCost {
                len: 1,
                runtime: 20
            }
        );

        assert_eq!(
            costs[v2],
            PathCost {
                len: 2,
                runtime: 30
            }
        );

        assert_eq!(
            costs[v1],
            PathCost {
                len: 1,
                runtime: 15
            }
        );

        assert_eq!(
            costs[v0],
            PathCost {
                len: 3,
                runtime: 35
            }
        );

        assert_eq!(predecessor[v0].into_option(), Some(v2));
        assert_eq!(predecessor[v1].into_option(), None);
        assert_eq!(predecessor[v2].into_option(), Some(v3));
        assert_eq!(predecessor[v3].into_option(), None);
    }

    #[test]
    fn test_add_edges() {
        let (graph, keys, data) = test_graph();

        let v0 = keys.get(&K0).unwrap();
        let v2 = keys.get(&K2).unwrap();
        let v3 = keys.get(&K3).unwrap();

        let mut new_edges = graph.allocate_vertex_data(OptionalVertexId::none());
        new_edges[v0] = v3.into(); // new edge
        new_edges[v2] = v3.into(); // already exists
        let graph = graph.add_edges(&new_edges, None).unwrap();

        let edges = graph
            .iter_all_edges()
            .map(|(l, r)| (data[l], data[r]))
            .collect::<Vec<_>>();
        assert_eq!(
            vec![(K2, K3), (K2, K3), (K0, K1), (K0, K2), (K0, K3)],
            edges
        );
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

mod builder;
mod graph;
mod potential;
mod types;

#[cfg(test)]
mod test_utils;

pub use builder::GraphBuilder;
pub use builder::PushError;
pub use graph::Graph;
pub use graph::GraphVertex;
pub use potential::compute_critical_path_potentials;
pub use types::CriticalPathIndex;
pub use types::CriticalPathVertexData;
pub use types::OptionalVertexId;
pub use types::VertexData;
pub use types::VertexId;
pub use types::VertexKeys;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::BinaryHeap;

use anyhow::Context as _;
use crossbeam::thread;

use crate::graph::Graph;
use crate::graph::PathCost;
use crate::types::CriticalPathIndex;
use crate::types::CriticalPathVertexData;
use crate::types::OptionalCriticalPathIndex;
use crate::types::VertexData;
use crate::types::VertexId;

pub fn compute_critical_path_potentials(
    deps: &Graph,
    weights: &VertexData<u64>,
) -> anyhow::Result<(
    CriticalPathVertexData<VertexId>,
    PathCost,
    CriticalPathVertexData<PathCost>,
)> {
    let mut rdeps = None;
    let mut topo_order = None;

    thread::scope(|s| {
        s.spawn(|_| {
            rdeps = Some(deps.reversed());
        });
        s.spawn(|_| {
            topo_order = Some(deps.topo_sort());
        });
    })
    .ok()
    .context("Threads panicked")?;

    let rdeps = rdeps.unwrap();
    let topo_order = topo_order.unwrap()?;

    let mut cost_to_sink = None;
    let mut cost_from_source = None;
    let mut predecessors = None;

    thread::scope(|s| {
        s.spawn(|_| {
            let (paths, _) = rdeps.find_longest_paths(topo_order.iter().copied(), weights);
            cost_to_sink = Some(paths);
        });
        s.spawn(|_| {
            let (paths, pred) = deps.find_longest_paths(topo_order.iter().rev().copied(), weights);
            cost_from_source = Some(paths);
            predecessors = Some(pred);
        });
    })
    .ok()
    .context("Threads panicked")?;

    let cost_to_sink = cost_to_sink.unwrap();
    let cost_from_source = cost_from_source.unwrap();
    let predecessors = predecessors.unwrap();

    // Look up the critical path. Find the node with the highest cost from a source, then iterate
    // over predecessors to reconstruct the critical path.
    let critical_path_end = cost_from_source.iter().max_by_key(|(_idx, cost)| *cost);

    let (critical_path_sink, critical_path_cost) = match critical_path_end {
        Some(c) => c,
        None => {
            // The graph is empty.
            return Ok((
                CriticalPathVertexData::new(Vec::new()),
                PathCost::default(),
                CriticalPathVertexData::new(Vec::new()),
            ));
        }
    };

    let critical_path_cost = *critical_path_cost;

    // Now, traverse predecessors to actually get the list of ndoes on the critical path.
    let critical_path = {
        let critical_path_len = critical_path_cost.len as usize;
        let mut critical_path = vec![VertexId::new(0); critical_path_len];
        let mut idx: VertexId = critical_path_sink;
        for i in 0..critical_path_len {
            critical_path[critical_path_len - 1 - i] = idx;
            if i != critical_path_len - 1 {
                idx = predecessors[idx].into_option().unwrap();
            }
        }
        CriticalPathVertexData::new(critical_path)
    };

    drop(predecessors); // We no longer need this.

    // For each node, we now identify:

    // - The last node on the critical path with a path to this node.
    let mut last_cp_predecessor = deps.allocate_vertex_data(OptionalCriticalPathIndex::none());

    // - The first node on the critical path with a path from this node.
    let mut first_cp_successor = deps.allocate_vertex_data(OptionalCriticalPathIndex::none());

    // To do this, we just need to traverse starting from the critical path nodes.

    struct GraphVisitor<'a> {
        graph: &'a Graph,
        mark: &'a mut VertexData<OptionalCriticalPathIndex>,
        cp_idx: CriticalPathIndex,
    }

    impl GraphVisitor<'_> {
        fn visit(&mut self, vertex_idx: VertexId) {
            if self.mark[vertex_idx].is_some() {
                return;
            }

            self.mark[vertex_idx] = self.cp_idx.into();

            for edge in self.graph.iter_edges(vertex_idx) {
                self.visit(edge);
            }
        }
    }

    thread::scope(|s| {
        s.spawn(|_| {
            for (cp_idx, vertex_idx) in critical_path.iter().rev() {
                GraphVisitor {
                    graph: &rdeps,
                    mark: &mut last_cp_predecessor,
                    cp_idx,
                }
                .visit(*vertex_idx);
            }
        });

        s.spawn(|_| {
            for (cp_idx, vertex_idx) in critical_path.iter() {
                GraphVisitor {
                    graph: deps,
                    mark: &mut first_cp_successor,
                    cp_idx,
                }
                .visit(*vertex_idx);
            }
        });
    })
    .ok()
    .context("Threads panicked")?;

    // Compute the cost of the longest path through each vertex. We do this here instead of inline
    // later to avoid jumping around 3 arrays later (whereas here we can do so linearly).

    let mut vertices_cost = deps.allocate_vertex_data(PathCost::default());

    for idx in deps.iter_vertices() {
        let mut cost = cost_from_source[idx] + cost_to_sink[idx];
        // Don't double-count the vertex at `idx`.
        cost.runtime -= weights[idx];
        cost.len -= 1;
        vertices_cost[idx] = cost
    }

    // Now, we know the longest path through each vertex, so what we need to do is find out when
    // that path does not overlap with the critical path. To do this, we're going lay out our
    // computation as a series of items that represent when nodes have their longest path overlap
    // with the critical path, relative to each critical path node.

    enum WorkItem {
        NodeValid {
            // Which node became valid (there is no longer a path from the current critical path to
            // this node).
            idx: VertexId,
            // When does this node become invalid (there is now a path from the current critical
            // path node to this node).
            invalid_at: Option<CriticalPathIndex>, // Might make sense to move this to a separate array to avoid copying it when we sort.
        },

        // Ordering matters here. We process all the nodes that become valid before we compute.
        Compute,
    }

    let mut work: Vec<(CriticalPathIndex, WorkItem)> =
        Vec::with_capacity(deps.vertices.len() + critical_path.len());

    for cp_idx in critical_path.keys() {
        work.push((cp_idx, WorkItem::Compute));
    }

    for idx in deps.iter_vertices() {
        let pred = last_cp_predecessor[idx];
        let succ = first_cp_successor[idx];

        let valid_at = match pred.into_option() {
            // Valid once we're past the last predecessor.
            Some(pred) => pred.successor(),
            // Valid since the beginning.
            None => CriticalPathIndex::zero(),
        };

        let invalid_at = succ.into_option();

        work.push((valid_at, WorkItem::NodeValid { idx, invalid_at }));
    }

    // Sort all of this by when the nodes become valid. Process all node validities before
    // computing the actual updted critical path cost.

    work.sort_by_key(|(idx, item)| {
        (
            *idx,
            match item {
                WorkItem::NodeValid { .. } => 0,
                WorkItem::Compute { .. } => 1,
            },
        )
    });

    // Initialize the new cost for the critical path for each node assuming it now takes zero time.

    let mut updated_critical_path_cost =
        CriticalPathVertexData::new(vec![PathCost::default(); critical_path.len()]);

    for (idx, vertex) in critical_path.iter() {
        // Drop this vertex's runtime.
        let mut cost = critical_path_cost;
        cost.runtime -= weights[*vertex];
        updated_critical_path_cost[idx] = cost;
    }

    // Now, compare to other longest paths. The heap is a max heap and keeps track of the longest
    // path through any of the vertices in the graph that does not overlap with the critical path
    // up to the current index.

    let mut heap = BinaryHeap::new();

    for (critical_path_index, item) in work {
        match item {
            WorkItem::NodeValid { idx, invalid_at } => {
                let cost = vertices_cost[idx];
                heap.push((cost, invalid_at));
            }
            WorkItem::Compute => {
                while let Some(item) = heap.peek() {
                    let replacement_cost = match item {
                        (cost, Some(invalid_at)) if *invalid_at > critical_path_index => cost,
                        (cost, None) => cost,
                        _ => {
                            // This node is invalid now (meaning its longest path now overlaps with
                            // the critical path), so pop it.
                            heap.pop();
                            continue;
                        }
                    };

                    let curr_cost = updated_critical_path_cost[critical_path_index];
                    if curr_cost < *replacement_cost {
                        updated_critical_path_cost[critical_path_index] = *replacement_cost
                    }

                    break;
                }
            }
        }
    }

    Ok((
        critical_path,
        critical_path_cost,
        updated_critical_path_cost,
    ))
}

#[cfg(test)]
mod test {
    use std::time::Instant;

    use rand::SeedableRng;
    use rand_chacha::ChaCha8Rng;

    use super::*;
    use crate::test_utils::make_dag;
    use crate::test_utils::seeded_rng;
    use crate::test_utils::TestDag;

    fn naive_critical_path_cost(dag: &TestDag, replacement: Option<(VertexId, u64)>) -> PathCost {
        // By construction, TestDag guarantees `vertices` is a topological order, so we iterate in
        // reverse order.
        let mut costs = dag.graph.allocate_vertex_data(PathCost::default());

        for idx in dag.graph.iter_vertices().rev() {
            let mut max: Option<PathCost> = None;

            for edge in dag.graph.iter_edges(idx) {
                let edge_cost = costs[edge];

                let replace = match max {
                    Some(max_cost) => max_cost < edge_cost,
                    _ => true,
                };

                if replace {
                    max = Some(edge_cost)
                }
            }

            let v_runtime = match replacement {
                Some((replacement, runtime)) if replacement == idx => runtime,
                _ => dag.weights[idx],
            };

            let cost = PathCost {
                len: 1,
                runtime: v_runtime,
            } + max.unwrap_or_default();

            costs[idx] = cost;
        }

        costs.values().max().copied().unwrap()
    }

    fn do_test(dag: &TestDag) {
        eprintln!("{} vertices", dag.graph.vertices.len());
        eprintln!("{} edges", dag.graph.edges.len());

        let fast = Instant::now();
        let (critical_path, critical_path_cost, replacement_costs) =
            compute_critical_path_potentials(&dag.graph, &dag.weights).unwrap();

        for (cp_idx, _) in critical_path.iter() {
            assert!(critical_path_cost >= replacement_costs[cp_idx]);
        }

        let fast = fast.elapsed();

        let naive = naive_critical_path_cost(dag, None);
        assert_eq!(naive, critical_path_cost);

        eprintln!();
        eprintln!("critical path = {:?}", naive);

        let slow = Instant::now();
        for (idx, replacement) in critical_path.values().zip(replacement_costs.values()) {
            let naive = naive_critical_path_cost(dag, Some((*idx, 0)));
            assert_eq!(naive, *replacement, "replacing node {idx:?} fails");
        }
        let slow = slow.elapsed();

        eprintln!("fast: {} us", fast.as_micros());
        eprintln!("slow: {} us", slow.as_micros());
    }

    pub fn test_dag(nodes: usize) -> TestDag {
        make_dag(nodes, &mut seeded_rng())
    }

    #[test]
    fn test_trivial() {
        do_test(&test_dag(2));
    }

    #[test]
    fn test_mini() {
        do_test(&test_dag(4));
    }

    #[test]
    fn test_medium() {
        do_test(&test_dag(100))
    }

    #[test]
    fn test_large() {
        do_test(&test_dag(1000))
    }

    #[test]
    fn test_xlarge() {
        do_test(&test_dag(10_000))
    }

    #[test]
    fn test_xxlarge() {
        do_test(&test_dag(100_000))
    }

    #[test]
    fn test_xxxlarge() {
        do_test(&test_dag(1_000_000))
    }

    /// Run on a larger number of random graphs.
    #[test]
    fn test_random_large() {
        for i in 0..10 {
            eprintln!();
            eprintln!("test {i}");
            let mut this_rng = ChaCha8Rng::seed_from_u64(i);
            let dag = make_dag(1000, &mut this_rng);
            do_test(&dag);
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use rand_distr::Normal;

use crate::graph::Graph;
use crate::graph::GraphVertex;
use crate::types::VertexData;
use crate::types::VertexId;

#[derive(Clone)]
pub struct TestDag {
    pub graph: Graph,
    pub keys: VertexData<String>,
    pub weights: VertexData<u64>,
}

pub fn seeded_rng() -> ChaCha8Rng {
    ChaCha8Rng::seed_from_u64(123)
}

pub fn make_dag(nodes: usize, rng: &mut impl Rng) -> TestDag {
    let degree_distribution = Normal::<f64>::new(2.0, 5.0).unwrap();

    let mut keys = Vec::new();
    let mut weights = Vec::new();
    let mut vertices = Vec::new();
    let mut edges = Vec::new();

    for i in 0..nodes {
        let candidate_count = nodes - 1 - i;
        let edges_count = degree_distribution.sample(rng).round() as usize;
        let edges_count = edges_count.min(candidate_count);

        keys.push(format!("k{}", i));
        weights.push(rng.gen_range(0..10_000));
        vertices.push(GraphVertex {
            edges_idx: edges.len().try_into().unwrap(),
            edges_count: edges_count.try_into().unwrap(),
        });

        // Those are relative to i + 1.
        for j in rand::seq::index::sample(rng, candidate_count, edges_count) {
            let j = i + 1 + j;
            edges.push(VertexId::new(j.try_into().unwrap())); // i depends on j
        }
    }

    TestDag {
        keys: VertexData::new(keys),
        weights: VertexData::new(weights),
        graph: Graph {
            vertices: VertexData::new(vertices),
            edges,
        },
    }
}

impl TestDag {
    /// Returns this graph, but shuffled, and a mapping of old to new.
    pub fn shuffled(&self, rng: &mut impl Rng) -> (Self, VertexData<VertexId>) {
        let len = self.graph.vertices.len();

        let mapping = {
            let mut mapping = Vec::with_capacity(len);
            mapping.extend(self.graph.iter_vertices());
            mapping.shuffle(rng);
            VertexData::new(mapping)
        };

        let mut vertices = VertexData::new(vec![
            GraphVertex {
                edges_idx: 0,
                edges_count: 0,
            };
            len
        ]);
        let mut edges = vec![VertexId::new(0); self.graph.edges.len()];
        let mut keys = VertexData::new(vec![String::default(); len]);
        let mut weights = VertexData::new(vec![u64::default(); len]);

        for idx in self.graph.iter_vertices() {
            let new = mapping[idx];

            let edges_idx = edges.len() as _;
            let mut edges_count = 0;

            for e in self.graph.iter_edges(idx) {
                edges.push(mapping[e]);
                edges_count += 1;
            }

            vertices[new] = GraphVertex {
                edges_idx,
                edges_count,
            };

            keys[new] = self.keys[idx].clone();
            weights[new] = self.weights[idx];
        }

        (
            Self {
                graph: Graph { vertices, edges },
                keys,
                weights,
            },
            mapping,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_shuffle() {
        let mut rng = seeded_rng();
        let dag = make_dag(100, &mut rng);
        let (shuffled, map) = dag.shuffled(&mut rng);

        let mut edges_before = dag
            .graph
            .iter_all_edges()
            .map(|(a, b)| (map[a].into_inner(), map[b].into_inner()))
            .collect::<Vec<_>>();
        edges_before.sort();

        let mut edges_after = shuffled
            .graph
            .iter_all_edges()
            .map(|(a, b)| (a.into_inner(), b.into_inner()))
            .collect::<Vec<_>>();
        edges_after.sort();

        assert_eq!(edges_before, edges_after);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::fmt;
use std::hash::Hash;
use std::marker::PhantomData;
use std::ops::Index;
use std::ops::IndexMut;

use derive_more::Display;
use starlark_map::small_map::SmallMap;

#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
pub struct GraphVertexKind;

#[derive(Copy, Clone, Default, Ord, PartialOrd, PartialEq, Eq, Debug)]
pub struct CriticalPathIndexKind;

pub trait VertexKind: Copy + Clone + Default {}

impl VertexKind for GraphVertexKind {}
impl VertexKind for CriticalPathIndexKind {}

/// The ID of a Vertex. This can be used to index into AbstractVertexData. Those IDs are given a
/// kind so we don't confuse indices in a critical path with vertex indices in a graph.
#[derive(Copy, Clone, Default, Ord, PartialOrd, PartialEq, Eq, Display, Hash)]
#[display(fmt = "{}", "self.0")]
pub struct AbstractVertexId<Kind: VertexKind>(u32, PhantomData<Kind>);

impl<Kind> AbstractVertexId<Kind>
where
    Kind: VertexKind,
{
    pub(crate) fn new(v: u32) -> Self {
        Self(v, PhantomData)
    }

    #[cfg(test)]
    pub(crate) fn into_inner(self) -> u32 {
        self.0
    }
}

impl<T, Kind> Index<AbstractVertexId<Kind>> for AbstractVertexData<T, Kind>
where
    Kind: VertexKind,
{
    type Output = T;

    #[inline]
    fn index(&self, index: AbstractVertexId<Kind>) -> &Self::Output {
        &self.0[index.0 as usize]
    }
}

impl<T, Kind> IndexMut<AbstractVertexId<Kind>> for AbstractVertexData<T, Kind>
where
    Kind: VertexKind,
{
    #[inline]
    fn index_mut(&mut self, index: AbstractVertexId<Kind>) -> &mut Self::Output {
        &mut self.0[index.0 as usize]
    }
}

impl<K, Kind> Index<AbstractVertexId<Kind>> for AbstractKeys<K, Kind>
where
    Kind: VertexKind,
{
    type Output = K;

    fn index(&self, index: AbstractVertexId<Kind>) -> &Self::Output {
        // NOTE: Unwrap is par for the course in [] access.
        self.0.get_index(index.0 as usize).unwrap().0
    }
}

/// An Optional AbstractVertexId.
#[derive(Copy, Clone)]
pub struct AbstractOptionalVertexId<Kind: VertexKind>(
    /// -1 means None.
    i32,
    PhantomData<Kind>,
);

impl<Kind> From<AbstractVertexId<Kind>> for AbstractOptionalVertexId<Kind>
where
    Kind: VertexKind,
{
    #[inline]
    fn from(node: AbstractVertexId<Kind>) -> Self {
        Self(node.0 as _, PhantomData)
    }
}

impl<Kind> AbstractOptionalVertexId<Kind>
where
    Kind: VertexKind,
{
    #[inline]
    pub fn none() -> Self {
        Self(-1, PhantomData)
    }

    #[inline]
    pub fn is_some(self) -> bool {
        self.0 >= 0
    }

    #[inline]
    pub fn into_option(self) -> Option<AbstractVertexId<Kind>> {
        if self.0 >= 0 {
            return Some(AbstractVertexId(self.0 as _, PhantomData));
        }

        None
    }
}

/// A Vec that stores data indexed by Vertex.
#[derive(Clone)]
pub struct AbstractVertexData<T, Kind: VertexKind>(Vec<T>, PhantomData<Kind>);

impl<T, Kind> AbstractVertexData<T, Kind>
where
    Kind: VertexKind,
{
    pub(crate) fn new(v: Vec<T>) -> Self {
        Self(v, PhantomData)
    }

    pub fn keys(&self) -> impl Iterator<Item = AbstractVertexId<Kind>> + DoubleEndedIterator {
        // By construction the length of this is always less than the maximum vertex id.
        let len: u32 = self.0.len().try_into().unwrap();
        (0..len).map(AbstractVertexId::new)
    }

    pub fn iter(&self) -> impl Iterator<Item = (AbstractVertexId<Kind>, &T)> + DoubleEndedIterator {
        self.keys().map(|k| (k, &self.0[k.0 as usize]))
    }

    pub fn values(&self) -> impl Iterator<Item = &T> {
        self.0.iter()
    }

    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> {
        self.0.iter_mut()
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn into_inner(self) -> Vec<T> {
        self.0
    }

    pub fn map_ref<TT>(&self, f: impl FnMut(&T) -> TT) -> AbstractVertexData<TT, Kind> {
        AbstractVertexData(self.values().map(f).collect(), PhantomData)
    }

    pub fn try_map_ref<TT, E>(
        &self,
        f: impl FnMut(&T) -> Result<TT, E>,
    ) -> Result<AbstractVertexData<TT, Kind>, E> {
        Ok(AbstractVertexData(
            self.values().map(f).collect::<Result<_, E>>()?,
            PhantomData,
        ))
    }
}

impl<Kind> fmt::Debug for AbstractVertexId<Kind>
where
    Kind: VertexKind,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0, f)
    }
}

impl<Kind> fmt::Debug for AbstractOptionalVertexId<Kind>
where
    Kind: VertexKind,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.into_option(), f)
    }
}

/// The keys for a graph built using GraphBuilder. This can be accessed using both VertexId
/// (because the IDs are assigned in order), or K.
#[derive(Clone)]
pub struct AbstractKeys<K, Kind: VertexKind>(SmallMap<K, AbstractVertexId<Kind>>);

impl<K, Kind> AbstractKeys<K, Kind>
where
    Kind: VertexKind,
    K: Hash + Eq,
{
    pub(crate) fn new(v: SmallMap<K, AbstractVertexId<Kind>>) -> Self {
        Self(v)
    }

    pub fn iter(&self) -> impl Iterator<Item = (AbstractVertexId<Kind>, &K)> + DoubleEndedIterator {
        self.0.iter().map(|(key, idx)| (*idx, key))
    }

    pub fn get(&self, k: &K) -> Option<AbstractVertexId<Kind>> {
        self.0.get(k).copied()
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }
}

pub type VertexId = AbstractVertexId<GraphVertexKind>;
pub type OptionalVertexId = AbstractOptionalVertexId<GraphVertexKind>;
pub type VertexData<T> = AbstractVertexData<T, GraphVertexKind>;
pub type VertexKeys<T> = AbstractKeys<T, GraphVertexKind>;

pub type CriticalPathIndex = AbstractVertexId<CriticalPathIndexKind>;
pub type OptionalCriticalPathIndex = AbstractOptionalVertexId<CriticalPathIndexKind>;
pub type CriticalPathVertexData<T> = AbstractVertexData<T, CriticalPathIndexKind>;

impl CriticalPathIndex {
    pub(crate) fn zero() -> Self {
        Self::new(0)
    }

    pub(crate) fn successor(self) -> Self {
        Self::new(self.0 + 1)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io;

fn main() -> io::Result<()> {
    let proto_files = &["data.proto"];

    buck2_protoc_dev::configure()
        .setup_protoc()
        .type_attribute(
            "buck.data.BuckEvent.data",
            "#[allow(clippy::large_enum_variant)]",
        )
        .type_attribute(
            "buck.data.SpanEndEvent.data",
            "#[allow(clippy::large_enum_variant)]",
        )
        .type_attribute(
            "buck.data.InstantEvent.data",
            "#[allow(clippy::large_enum_variant)]",
        )
        .type_attribute(
            "buck.data.BuckEvent.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.SpanStartEvent.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.SpanEndEvent.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CommandStart.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CommandEnd.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CommandCriticalStart.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CommandCriticalEnd.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.InstantEvent.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.RecordEvent.data",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.LocalStage.stage",
            "#[derive(::derive_more::From)]",
        )
        .type_attribute("buck.data.ReStage.stage", "#[derive(::derive_more::From)]")
        .type_attribute(
            "buck.data.ExecutorStageStart.stage",
            "#[derive(::derive_more::From)]",
        )
        .type_attribute(
            "buck.data.CommandExecution.status",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.ActionExecutionEnd.error",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CommandExecutionDetails.command",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .field_attribute(
            "buck.data.CommandExecutionDetails.input_materialization_duration",
            "#[serde(rename = \"input_materialization_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .type_attribute(
            "buck.data.DynamicLambdaStart.owner",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.DeferredPreparationStageStart.stage",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.AnalysisStart.target",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.AnalysisEnd.target",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute("buck.data.TargetLabel", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.Configuration", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.ConfiguredTargetLabel", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.AnonTarget", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.BxlFunctionLabel", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.BxlFunctionKey", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.ActionKey.owner", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.ActionKey", "#[derive(Eq, Hash)]")
        .type_attribute("buck.data.FileWatcherEvent", "#[derive(Eq, Hash)]")
        .type_attribute(
            "buck.data.ActionKind",
            "#[derive(::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.MaterializationMethod",
            "#[derive(::gazebo::variants::VariantName)]",
        )
        .type_attribute("buck.data.CommandExecutionStats", "#[derive(Copy, dupe::Dupe)]")
        .type_attribute(".", "#[derive(::serde::Serialize, ::serde::Deserialize)]")
        .type_attribute(".", "#[derive(::allocative::Allocative)]")
        .field_attribute(
            "timestamp",
            "#[serde(with = \"crate::serialize_timestamp\")]",
        )
        .field_attribute(
            "duration",
            "#[serde(rename = \"duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "command_duration",
            "#[serde(rename = \"command_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "client_walltime",
            "#[serde(rename = \"client_walltime_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "critical_path_duration",
            "#[serde(rename = \"critical_path_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "ActionExecutionEnd.wall_time",
            "#[serde(rename = \"wall_time_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "ActionKey.id",
            "#[serde(with = \"crate::serialize_bytes\")]",
        )
        // When serializing using Serde we don't want those to just be i32s, since those are
        // meaningless without the Protobuf schema.
        .field_attribute(
            "ActionExecutionStart.kind",
            "#[serde(with = \"crate::serialize_action_kind\")]",
        )
        .field_attribute(
            "ActionExecutionEnd.kind",
            "#[serde(with = \"crate::serialize_action_kind\")]",
        )
        .field_attribute(
            "RemoteCommand.queue_time",
            "#[serde(rename = \"queue_time_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "concurrent_command_blocking_duration",
            "#[serde(rename = \"concurrent_command_blocking_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "bxl_ensure_artifacts_duration",
            "#[serde(rename = \"bxl_ensure_artifacts_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "CriticalPathEntry2.user_duration",
            "#[serde(rename = \"user_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "CriticalPathEntry2.total_duration",
            "#[serde(rename = \"total_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .field_attribute(
            "CriticalPathEntry2.potential_improvement_duration",
            "#[serde(rename = \"potential_improvement_duration_us\", with = \"crate::serialize_duration_as_micros\")]",
        )
        .type_attribute(
            "buck.data.CriticalPathEntry2.entry",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CriticalPathEntry2.Analysis.target",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CriticalPathEntry2.ActionExecution.owner",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.CriticalPathEntry2.Materialization.owner",
            "#[derive(::derive_more::From, ::gazebo::variants::VariantName)]",
        )
        .type_attribute(
            "buck.data.StarlarkUserMetadataValue",
            "#[serde(transparent)]",
        )
        .type_attribute(
            "buck.data.StarlarkUserMetadataValue.value",
            "#[serde(untagged)]",
        )
        .boxed("RecordEvent.data.invocation_record")
        .boxed("SpanEndEvent.data.action_execution")
        .boxed("SpanEndEvent.data.cache_upload")
        .boxed("InstantEvent.data.snapshot")
        .compile(proto_files, &["."])
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

/// This should have been a message in proto, but that is backwards incompatible.
pub enum BaseDeferredKeyProto {
    TargetLabel(crate::ConfiguredTargetLabel),
    BxlKey(crate::BxlFunctionKey),
    AnonTarget(crate::AnonTarget),
}

impl From<BaseDeferredKeyProto> for crate::critical_path_entry2::action_execution::Owner {
    fn from(value: BaseDeferredKeyProto) -> Self {
        match value {
            BaseDeferredKeyProto::TargetLabel(t) => {
                crate::critical_path_entry2::action_execution::Owner::TargetLabel(t)
            }
            BaseDeferredKeyProto::BxlKey(b) => {
                crate::critical_path_entry2::action_execution::Owner::BxlKey(b)
            }
            BaseDeferredKeyProto::AnonTarget(a) => {
                crate::critical_path_entry2::action_execution::Owner::AnonTarget(a)
            }
        }
    }
}

impl From<BaseDeferredKeyProto> for crate::critical_path_entry2::materialization::Owner {
    fn from(value: BaseDeferredKeyProto) -> Self {
        match value {
            BaseDeferredKeyProto::TargetLabel(t) => {
                crate::critical_path_entry2::materialization::Owner::TargetLabel(t)
            }
            BaseDeferredKeyProto::BxlKey(b) => {
                crate::critical_path_entry2::materialization::Owner::BxlKey(b)
            }
            BaseDeferredKeyProto::AnonTarget(a) => {
                crate::critical_path_entry2::materialization::Owner::AnonTarget(a)
            }
        }
    }
}

impl From<BaseDeferredKeyProto> for crate::dynamic_lambda_start::Owner {
    fn from(value: BaseDeferredKeyProto) -> Self {
        match value {
            BaseDeferredKeyProto::TargetLabel(t) => {
                crate::dynamic_lambda_start::Owner::TargetLabel(t)
            }
            BaseDeferredKeyProto::BxlKey(b) => crate::dynamic_lambda_start::Owner::BxlKey(b),
            BaseDeferredKeyProto::AnonTarget(a) => {
                crate::dynamic_lambda_start::Owner::AnonTarget(a)
            }
        }
    }
}

impl From<BaseDeferredKeyProto> for crate::action_key::Owner {
    fn from(value: BaseDeferredKeyProto) -> Self {
        match value {
            BaseDeferredKeyProto::TargetLabel(t) => crate::action_key::Owner::TargetLabel(t),
            BaseDeferredKeyProto::BxlKey(b) => crate::action_key::Owner::BxlKey(b),
            BaseDeferredKeyProto::AnonTarget(a) => crate::action_key::Owner::AnonTarget(a),
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::borrow::Cow;
use std::fmt;

pub mod action_key_owner;

mod serialize_timestamp {
    use serde::Deserialize;
    use serde::Deserializer;
    use serde::Serialize;
    use serde::Serializer;

    pub fn serialize<S>(
        value: &Option<::prost_types::Timestamp>,
        serializer: S,
    ) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let d = value.as_ref().map(|v| (v.seconds, v.nanos));
        d.serialize(serializer)
    }

    pub fn deserialize<'de, D>(
        deserializer: D,
    ) -> Result<Option<::prost_types::Timestamp>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let d = Option::<(i64, i32)>::deserialize(deserializer)?;
        let d = d.map(|(seconds, nanos)| ::prost_types::Timestamp { seconds, nanos });
        Ok(d)
    }
}

mod serialize_bytes {
    use serde::Deserialize;
    use serde::Deserializer;
    use serde::Serialize;
    use serde::Serializer;

    pub fn serialize<S>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let d = hex::encode(value);
        d.serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let d = String::deserialize(deserializer)?;
        let d = hex::decode(d).map_err(serde::de::Error::custom)?;
        Ok(d)
    }
}

mod serialize_action_kind {
    use serde::Deserialize;
    use serde::Deserializer;
    use serde::Serialize;
    use serde::Serializer;

    #[allow(clippy::trivially_copy_pass_by_ref)]
    pub fn serialize<S>(value: &i32, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let k = crate::ActionKind::from_i32(*value).ok_or_else(|| {
            serde::ser::Error::custom(format!("Invalid ActionKind enum value: {}", value))
        })?;
        k.serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<i32, D::Error>
    where
        D: Deserializer<'de>,
    {
        let d = crate::ActionKind::deserialize(deserializer)?;
        Ok(d as i32)
    }
}

tonic::include_proto!("buck.data");

/// Trait for things that can be converted into protobuf messages, for ease of emitting events. There are many core Buck
/// types that are represented in the Daemon API that use this trait to ease conversion.
pub trait ToProtoMessage {
    type Message: prost::Message;

    fn as_proto(&self) -> Self::Message;
}

/// Write out a human-readable description of the error tags
/// that is printed out in the context stack when program fails.
impl fmt::Display for ErrorCategory {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let msg = match &self {
            ErrorCategory::Infra => "This error is an internal Buck2 error",
            ErrorCategory::User => "This error was caused by the end user",
        };

        write!(f, "{}", msg)
    }
}

impl fmt::Display for ErrorCause {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let msg = match &self {
            ErrorCause::InvalidPackage => "The package is invalid",
            ErrorCause::DaemonIsBusy => "Buck daemon is busy processing another command",
        };

        write!(f, "{}", msg)
    }
}

impl fmt::Display for DaemonShutdown {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}, caller:", self.reason)?;

        for caller in self.callers.iter() {
            let max_len = 70;

            let short_caller = if caller.len() > max_len {
                Cow::Owned(
                    caller
                        .chars()
                        .take(max_len)
                        .chain(std::iter::repeat('.').take(3))
                        .collect(),
                )
            } else {
                Cow::Borrowed(caller)
            };

            writeln!(f)?;
            write!(f, "  * {}", short_caller)?;
        }

        Ok(())
    }
}

pub mod serialize_duration_as_micros {
    use serde::Deserialize;
    use serde::Deserializer;
    use serde::Serialize;
    use serde::Serializer;

    pub fn serialize<S>(
        value: &Option<::prost_types::Duration>,
        serializer: S,
    ) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        value.as_ref().map(to_micros).serialize(serializer)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<::prost_types::Duration>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let value = Option::<i64>::deserialize(deserializer)?;
        Ok(value.map(from_micros))
    }

    const MICROS_IN_SECOND: i64 = 1000000;
    const NANOS_IN_MICRO: i32 = 1000;

    /// We saturate here since technically not all ::prost_types::Duration would fit (but all those
    /// we care about would).
    fn to_micros(duration: &::prost_types::Duration) -> i64 {
        let mut micros: i64 = 0;
        micros = micros.saturating_add(duration.seconds.saturating_mul(MICROS_IN_SECOND));
        micros = micros.saturating_add(duration.nanos.saturating_div(NANOS_IN_MICRO).into());
        micros
    }

    fn from_micros(duration: i64) -> ::prost_types::Duration {
        let seconds = duration / MICROS_IN_SECOND;
        let nanos = ((duration % MICROS_IN_SECOND) as i32) * NANOS_IN_MICRO;
        prost_types::Duration {
            seconds,
            nanos: nanos as _,
        }
    }

    #[cfg(test)]
    mod test {
        use super::*;

        #[test]
        fn test_value() {
            assert_eq!(
                to_micros(&::prost_types::Duration {
                    seconds: 1,
                    nanos: 1000
                }),
                1000001
            );
        }

        #[test]
        fn test_roundtrip() {
            for v in [-1, 10, i64::MAX, i64::MIN, 0] {
                assert_eq!(to_micros(&from_micros(v)), v);
            }
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! The downward api for external processes. This crate defines a trait of downward api that Buck
//! will need to handle as the process runner.

use std::collections::HashMap;

use tracing::Level;

/// The API available to processes that Buck will need to handle
#[async_trait::async_trait]
pub trait DownwardApi {
    /// indicates to print to the console at a specific log level
    async fn console(&self, level: Level, msg: String) -> anyhow::Result<()>;

    /// indicates to log at a specified level
    /// TODO consider if we should have structured log instead of a String message
    async fn log(&self, level: Level, msg: String) -> anyhow::Result<()>;

    /// reports an externally consumable event containing some data that will be untouched by buck
    async fn external(&self, data: HashMap<String, String>) -> anyhow::Result<()>;

    // TODO map the StepEvent and TraceEvents in buckv1 to something. Maybe just a single trace event
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::io;

fn main() -> io::Result<()> {
    let proto_files = &["downward_api.proto"];

    buck2_protoc_dev::configure()
        .setup_protoc()
        .compile(proto_files, &["."])
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::collections::HashMap;

use anyhow::Context as _;
use tracing::Level;

use crate::proto;

impl TryInto<HashMap<String, String>> for proto::Event {
    type Error = anyhow::Error;

    fn try_into(self) -> Result<HashMap<String, String>, Self::Error> {
        use std::collections::hash_map::Entry;

        use proto::event::Item;

        let proto::Event { items } = self;

        let mut ret = HashMap::new();
        for Item { key, value } in items {
            match ret.entry(key) {
                Entry::Vacant(e) => {
                    e.insert(value);
                }
                Entry::Occupied(e) => {
                    anyhow::bail!("Duplicate key: {}", e.key());
                }
            }
        }

        Ok(ret)
    }
}

impl<T> From<T> for proto::Event
where
    T: IntoIterator<Item = (String, String)>,
{
    fn from(iter: T) -> proto::Event {
        use proto::event::Item;

        let items = iter
            .into_iter()
            .map(|(key, value)| Item { key, value })
            .collect();

        proto::Event { items }
    }
}

impl TryInto<Level> for proto::LogLevel {
    type Error = anyhow::Error;

    fn try_into(self) -> Result<Level, Self::Error> {
        use proto::log_level::Value;

        let proto::LogLevel { value } = self;
        let value = Value::from_i32(value).context("Invalid `value`")?;

        Ok(match value {
            Value::NotSet => anyhow::bail!("Missing `value`"),
            Value::Trace => Level::TRACE,
            Value::Debug => Level::DEBUG,
            Value::Info => Level::INFO,
            Value::Warn => Level::WARN,
            Value::Error => Level::ERROR,
        })
    }
}

impl TryFrom<Level> for proto::LogLevel {
    type Error = anyhow::Error;

    fn try_from(level: Level) -> Result<proto::LogLevel, Self::Error> {
        use proto::log_level::Value;

        let value = match level {
            v if v == Level::TRACE => Value::Trace,
            v if v == Level::DEBUG => Value::Debug,
            v if v == Level::INFO => Value::Info,
            v if v == Level::WARN => Value::Warn,
            v if v == Level::ERROR => Value::Error,
            v => anyhow::bail!("Unsupported Level: {:?}", v),
        };

        Ok(proto::LogLevel {
            value: value as i32,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_log_level_roundtrip() {
        for v in &[
            Level::TRACE,
            Level::DEBUG,
            Level::INFO,
            Level::WARN,
            Level::ERROR,
        ] {
            let p = proto::LogLevel::try_from(*v).unwrap();
            let v2: Level = p.try_into().unwrap();
            assert_eq!(*v, v2);
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

//! Protobufs for ineteracting with Buck's DownwardApi over GPRC. This isn't the protocol Buck v1
//! speaks, where the DownwardApi is accessed over named pipes with serialized JSON payloads. This
//! is a different way to make the same calls.

// We put this in a module for easier naming in convert.
mod proto {
    tonic::include_proto!("buck.downward_api");
}

pub use proto::*;

mod convert;