// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

//! Providers that filter resource requests.
//!
//! Requests that fail a filter test will return [`DataError`] of kind [`Filtered`](
//! DataErrorKind::IdentifierNotFound) and will not appear in [`IterableDynamicDataProvider`] iterators.
//!
//! # Examples
//!
//! ```
//! use icu_locale::subtags::language;
//! use icu_provider::hello_world::*;
//! use icu_provider::prelude::*;
//! use icu_provider_adapters::filter::FilterDataProvider;
//!
//! // Only return German data from a HelloWorldProvider:
//! FilterDataProvider::new(HelloWorldProvider, "Demo German-only filter")
//!     .with_filter(|id| id.locale.language == language!("de"));
//! ```

mod impls;

use alloc::collections::BTreeSet;
#[cfg(feature = "export")]
use icu_provider::export::ExportableProvider;
use icu_provider::prelude::*;

/// A data provider that selectively filters out data requests.
///
/// Data requests that are rejected by the filter will return a [`DataError`] with kind
/// [`Filtered`](DataErrorKind::IdentifierNotFound), and they will not be returned
/// by [`IterableDynamicDataProvider::iter_ids_for_marker`].
///
/// Although this struct can be created directly, the traits in this module provide helper
/// functions for common filtering patterns.
#[allow(clippy::exhaustive_structs)] // this type is stable
#[derive(Debug)]
pub struct FilterDataProvider<D, F>
where
    F: Fn(DataIdentifierBorrowed) -> bool,
{
    /// The data provider to which we delegate requests.
    pub inner: D,

    /// The predicate function. A return value of `true` indicates that the request should
    /// proceed as normal; a return value of `false` will reject the request.
    pub predicate: F,

    /// A name for this filter, used in error messages.
    pub filter_name: &'static str,
}

impl<D, F> FilterDataProvider<D, F>
where
    F: Fn(DataIdentifierBorrowed) -> bool,
{
    fn check(&self, marker: DataMarkerInfo, req: DataRequest) -> Result<(), DataError> {
        if !(self.predicate)(req.id) {
            return Err(DataErrorKind::IdentifierNotFound
                .with_str_context(self.filter_name)
                .with_req(marker, req));
        }
        Ok(())
    }
}

impl<D, F, M> DynamicDataProvider<M> for FilterDataProvider<D, F>
where
    F: Fn(DataIdentifierBorrowed) -> bool,
    M: DynamicDataMarker,
    D: DynamicDataProvider<M>,
{
    fn load_data(
        &self,
        marker: DataMarkerInfo,
        req: DataRequest,
    ) -> Result<DataResponse<M>, DataError> {
        self.check(marker, req)?;
        self.inner.load_data(marker, req)
    }
}

impl<D, F, M> DynamicDryDataProvider<M> for FilterDataProvider<D, F>
where
    F: Fn(DataIdentifierBorrowed) -> bool,
    M: DynamicDataMarker,
    D: DynamicDryDataProvider<M>,
{
    fn dry_load_data(
        &self,
        marker: DataMarkerInfo,
        req: DataRequest,
    ) -> Result<DataResponseMetadata, DataError> {
        self.check(marker, req)?;
        self.inner.dry_load_data(marker, req)
    }
}

impl<D, F, M> DataProvider<M> for FilterDataProvider<D, F>
where
    F: Fn(DataIdentifierBorrowed) -> bool,
    M: DataMarker,
    D: DataProvider<M>,
{
    fn load(&self, req: DataRequest) -> Result<DataResponse<M>, DataError> {
        self.check(M::INFO, req)?;
        self.inner.load(req)
    }
}

impl<D, F, M> DryDataProvider<M> for FilterDataProvider<D, F>
where
    F: Fn(DataIdentifierBorrowed) -> bool,
    M: DataMarker,
    D: DryDataProvider<M>,
{
    fn dry_load(&self, req: DataRequest) -> Result<DataResponseMetadata, DataError> {
        self.check(M::INFO, req)?;
        self.inner.dry_load(req)
    }
}

impl<M, D, F> IterableDynamicDataProvider<M> for FilterDataProvider<D, F>
where
    M: DynamicDataMarker,
    F: Fn(DataIdentifierBorrowed) -> bool,
    D: IterableDynamicDataProvider<M>,
{
    fn iter_ids_for_marker(
        &self,
        marker: DataMarkerInfo,
    ) -> Result<BTreeSet<DataIdentifierCow<'_>>, DataError> {
        self.inner.iter_ids_for_marker(marker).map(|set| {
            // Use filter_map instead of filter to avoid cloning the locale
            set.into_iter()
                .filter(|id| (self.predicate)(id.as_borrowed()))
                .collect()
        })
    }
}

impl<M, D, F> IterableDataProvider<M> for FilterDataProvider<D, F>
where
    M: DataMarker,
    F: Fn(DataIdentifierBorrowed) -> bool,
    D: IterableDataProvider<M>,
{
    fn iter_ids(&self) -> Result<BTreeSet<DataIdentifierCow<'_>>, DataError> {
        self.inner.iter_ids().map(|vec| {
            // Use filter_map instead of filter to avoid cloning the locale
            vec.into_iter()
                .filter(|id| (self.predicate)(id.as_borrowed()))
                .collect()
        })
    }
}

#[cfg(feature = "export")]
impl<P0, F> ExportableProvider for FilterDataProvider<P0, F>
where
    P0: ExportableProvider,
    F: Fn(DataIdentifierBorrowed) -> bool + Sync,
{
    fn supported_markers(&self) -> BTreeSet<DataMarkerInfo> {
        // The predicate only takes DataIdentifier, not DataMarker, so we are not impacted
        self.inner.supported_markers()
    }
}
