pub mod announce;
pub use announce::{Announcer, AnnouncerConfig, AnnouncerError, AnnouncerResult};

pub mod fetch;
pub use fetch::{Fetcher, FetcherConfig, FetcherError, FetcherResult};

use std::collections::BTreeSet;

use crate::identity::Visibility;
use crate::prelude::Doc;

use super::NodeId;

pub const DEFAULT_REPLICATION_FACTOR: usize = 3;

/// A set of nodes that form a private network for fetching from.
///
/// This could be the set of allowed nodes for a private repository, using
/// [`PrivateNetwork::private_repo`]
pub struct PrivateNetwork {
    allowed: BTreeSet<NodeId>,
}

impl PrivateNetwork {
    pub fn private_repo(doc: &Doc) -> Option<Self> {
        match doc.visibility() {
            Visibility::Public => None,
            Visibility::Private { allow } => {
                let allowed = doc
                    .delegates()
                    .iter()
                    .chain(allow.iter())
                    .map(|did| *did.as_key())
                    .collect();
                Some(Self { allowed })
            }
        }
    }

    /// Restrict the set of allowed nodes based on the `predicate`, where `true`
    /// keeps the `NodeId` in the allowed set.
    ///
    /// For example, this can be useful to restrict the set to only connected
    /// nodes.
    pub fn restrict<P>(self, predicate: P) -> Self
    where
        P: FnMut(&NodeId) -> bool,
    {
        Self {
            allowed: self.allowed.into_iter().filter(predicate).collect(),
        }
    }
}

/// The replication factor of a syncing operation.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ReplicationFactor {
    /// The syncing operation much reach the given value.
    ///
    /// See [`ReplicationFactor::must_reach`].
    MustReach(usize),
    /// The syncing operation must reach a minimum value, but may continue to
    /// reach a maximum value.
    ///
    /// See [`ReplicationFactor::range`].
    Range(ReplicationRange),
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ReplicationRange {
    lower: usize,
    upper: usize,
}

impl Default for ReplicationFactor {
    fn default() -> Self {
        Self::must_reach(DEFAULT_REPLICATION_FACTOR)
    }
}

impl ReplicationFactor {
    /// Construct a replication factor with the `lower` and `upper` bounds.
    ///
    /// If `lower >= upper`, then [`ReplicationFactor::MustReach`] is constructed instead of
    /// `ReplicationFactor::Range`.
    pub fn range(lower: usize, upper: usize) -> Self {
        if lower >= upper {
            Self::MustReach(lower)
        } else {
            Self::Range(ReplicationRange { lower, upper })
        }
    }

    /// Construct a replication factor where the `factor` must be reached.
    pub fn must_reach(factor: usize) -> Self {
        Self::MustReach(factor)
    }

    /// Get the lower bound of the replication factor.
    pub fn lower_bound(&self) -> usize {
        match self {
            Self::MustReach(lower) => *lower,
            Self::Range(ReplicationRange { lower: min, .. }) => *min,
        }
    }

    /// Get the upper bound of the replication factor, if the replication factor
    /// is a range.
    pub fn upper_bound(&self) -> Option<usize> {
        match self {
            Self::MustReach(_) => None,
            Self::Range(ReplicationRange { upper: max, .. }) => Some(*max),
        }
    }

    /// Set the minimum target of the [`ReplicationFactor`] to a new value.
    ///
    /// If the original value is smaller than the new value, then the original
    /// is kept.
    ///
    /// If the [`ReplicationFactor`] is a range, it performs `min` on the upper
    /// bound of the range.
    ///
    /// If `self` was originally a [`ReplicationFactor::Range`], and `min >= max`, then
    /// the returned value will be [`ReplicationFactor::MustReach`].
    pub fn min(self, new: usize) -> Self {
        match self {
            Self::MustReach(min) => Self::MustReach(min.min(new)),
            Self::Range(ReplicationRange {
                lower: min,
                upper: max,
            }) => Self::range(min, max.min(new)),
        }
    }
}

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

    #[test]
    fn ensure_replicas_construction() {
        let replicas = ReplicationFactor::range(1, 3);
        assert!(
            replicas.lower_bound()
                <= replicas
                    .upper_bound()
                    .expect("replicas should have max value")
        );
        let replicas = ReplicationFactor::range(1, 1);
        assert!(replicas.upper_bound().is_none());
        let replicas = ReplicationFactor::range(3, 1);
        assert!(replicas.upper_bound().is_none());
    }

    #[test]
    fn replicas_constrain_to() {
        let replicas = ReplicationFactor::must_reach(3).min(1);
        assert_eq!(replicas, ReplicationFactor::MustReach(1));
        let replicas = ReplicationFactor::must_reach(3).min(3);
        assert_eq!(replicas, ReplicationFactor::MustReach(3));
        let replicas = ReplicationFactor::must_reach(3).min(10);
        assert_eq!(replicas, ReplicationFactor::MustReach(3));

        let replicas = ReplicationFactor::range(1, 3).min(1);
        assert_eq!(replicas, ReplicationFactor::MustReach(1));
        let replicas = ReplicationFactor::range(1, 3).min(3);
        assert_eq!(
            replicas,
            ReplicationFactor::Range(ReplicationRange { lower: 1, upper: 3 })
        );
        let replicas = ReplicationFactor::range(1, 3).min(10);
        assert_eq!(
            replicas,
            ReplicationFactor::Range(ReplicationRange { lower: 1, upper: 3 })
        );
    }
}
