// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#![cfg_attr(test, allow(clippy::needless_pass_by_value))]
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/apache/datafusion/19fe44cf2f30cbdd63d4a4f52c74055163c6cc38/docs/logos/standalone_logo/logo_original.svg",
    html_favicon_url = "https://raw.githubusercontent.com/apache/datafusion/19fe44cf2f30cbdd63d4a4f52c74055163c6cc38/docs/logos/standalone_logo/logo_original.svg"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
// Make sure fast / cheap clones on Arc are explicit:
// https://github.com/apache/datafusion/issues/11143
#![deny(clippy::clone_on_ref_ptr)]

//! Aggregate Function packages for [DataFusion].
//!
//! This crate contains a collection of various aggregate function packages for DataFusion,
//! implemented using the extension API. Users may wish to control which functions
//! are available to control the binary size of their application as well as
//! use dialect specific implementations of functions (e.g. Spark vs Postgres)
//!
//! Each package is implemented as a separate
//! module, activated by a feature flag.
//!
//! [DataFusion]: https://crates.io/crates/datafusion
//!
//! # Available Packages
//! See the list of [modules](#modules) in this crate for available packages.
//!
//! # Using A Package
//! You can register all functions in all packages using the [`register_all`] function.
//!
//! Each package also exports an `expr_fn` submodule to help create [`Expr`]s that invoke
//! functions using a fluent style. For example:
//!
//![`Expr`]: datafusion_expr::Expr
//!
//! # Implementing A New Package
//!
//! To add a new package to this crate, you should follow the model of existing
//! packages. The high level steps are:
//!
//! 1. Create a new module with the appropriate [AggregateUDF] implementations.
//!
//! 2. Use the macros in [`macros`] to create standard entry points.
//!
//! 3. Add a new feature to `Cargo.toml`, with any optional dependencies
//!
//! 4. Use the `make_package!` macro to expose the module when the
//!    feature is enabled.

#[macro_use]
pub mod macros;

pub mod approx_distinct;
pub mod approx_median;
pub mod approx_percentile_cont;
pub mod approx_percentile_cont_with_weight;
pub mod array_agg;
pub mod average;
pub mod bit_and_or_xor;
pub mod bool_and_or;
pub mod correlation;
pub mod count;
pub mod covariance;
pub mod first_last;
pub mod grouping;
pub mod hyperloglog;
pub mod median;
pub mod min_max;
pub mod nth_value;
pub mod percentile_cont;
pub mod regr;
pub mod stddev;
pub mod string_agg;
pub mod sum;
pub mod variance;

pub mod planner;
mod utils;

use crate::approx_percentile_cont::approx_percentile_cont_udaf;
use crate::approx_percentile_cont_with_weight::approx_percentile_cont_with_weight_udaf;
use datafusion_common::Result;
use datafusion_execution::FunctionRegistry;
use datafusion_expr::AggregateUDF;
use log::debug;
use std::sync::Arc;

/// Fluent-style API for creating `Expr`s
pub mod expr_fn {
    pub use super::approx_distinct::approx_distinct;
    pub use super::approx_median::approx_median;
    pub use super::approx_percentile_cont::approx_percentile_cont;
    pub use super::approx_percentile_cont_with_weight::approx_percentile_cont_with_weight;
    pub use super::array_agg::array_agg;
    pub use super::average::avg;
    pub use super::average::avg_distinct;
    pub use super::bit_and_or_xor::bit_and;
    pub use super::bit_and_or_xor::bit_or;
    pub use super::bit_and_or_xor::bit_xor;
    pub use super::bool_and_or::bool_and;
    pub use super::bool_and_or::bool_or;
    pub use super::correlation::corr;
    pub use super::count::count;
    pub use super::count::count_distinct;
    pub use super::covariance::covar_pop;
    pub use super::covariance::covar_samp;
    pub use super::first_last::first_value;
    pub use super::first_last::last_value;
    pub use super::grouping::grouping;
    pub use super::median::median;
    pub use super::min_max::max;
    pub use super::min_max::min;
    pub use super::nth_value::nth_value;
    pub use super::percentile_cont::percentile_cont;
    pub use super::regr::regr_avgx;
    pub use super::regr::regr_avgy;
    pub use super::regr::regr_count;
    pub use super::regr::regr_intercept;
    pub use super::regr::regr_r2;
    pub use super::regr::regr_slope;
    pub use super::regr::regr_sxx;
    pub use super::regr::regr_sxy;
    pub use super::regr::regr_syy;
    pub use super::stddev::stddev;
    pub use super::stddev::stddev_pop;
    pub use super::sum::sum;
    pub use super::sum::sum_distinct;
    pub use super::variance::var_pop;
    pub use super::variance::var_sample;
}

/// Returns all default aggregate functions
pub fn all_default_aggregate_functions() -> Vec<Arc<AggregateUDF>> {
    vec![
        array_agg::array_agg_udaf(),
        first_last::first_value_udaf(),
        first_last::last_value_udaf(),
        covariance::covar_samp_udaf(),
        covariance::covar_pop_udaf(),
        correlation::corr_udaf(),
        sum::sum_udaf(),
        min_max::max_udaf(),
        min_max::min_udaf(),
        median::median_udaf(),
        count::count_udaf(),
        regr::regr_slope_udaf(),
        regr::regr_intercept_udaf(),
        regr::regr_count_udaf(),
        regr::regr_r2_udaf(),
        regr::regr_avgx_udaf(),
        regr::regr_avgy_udaf(),
        regr::regr_sxx_udaf(),
        regr::regr_syy_udaf(),
        regr::regr_sxy_udaf(),
        variance::var_samp_udaf(),
        variance::var_pop_udaf(),
        stddev::stddev_udaf(),
        stddev::stddev_pop_udaf(),
        approx_median::approx_median_udaf(),
        approx_distinct::approx_distinct_udaf(),
        approx_percentile_cont_udaf(),
        approx_percentile_cont_with_weight_udaf(),
        percentile_cont::percentile_cont_udaf(),
        string_agg::string_agg_udaf(),
        bit_and_or_xor::bit_and_udaf(),
        bit_and_or_xor::bit_or_udaf(),
        bit_and_or_xor::bit_xor_udaf(),
        bool_and_or::bool_and_udaf(),
        bool_and_or::bool_or_udaf(),
        average::avg_udaf(),
        grouping::grouping_udaf(),
        nth_value::nth_value_udaf(),
    ]
}

/// Registers all enabled packages with a [`FunctionRegistry`]
pub fn register_all(registry: &mut dyn FunctionRegistry) -> Result<()> {
    let functions: Vec<Arc<AggregateUDF>> = all_default_aggregate_functions();

    functions.into_iter().try_for_each(|udf| {
        let existing_udaf = registry.register_udaf(udf)?;
        if let Some(existing_udaf) = existing_udaf {
            debug!("Overwrite existing UDAF: {}", existing_udaf.name());
        }
        Ok(()) as Result<()>
    })?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use crate::all_default_aggregate_functions;
    use datafusion_common::Result;
    use std::collections::HashSet;

    #[test]
    fn test_no_duplicate_name() -> Result<()> {
        let mut names = HashSet::new();
        for func in all_default_aggregate_functions() {
            assert!(
                names.insert(func.name().to_string().to_lowercase()),
                "duplicate function name: {}",
                func.name()
            );
            for alias in func.aliases() {
                assert!(
                    names.insert(alias.to_string().to_lowercase()),
                    "duplicate function name: {alias}"
                );
            }
        }
        Ok(())
    }
}
