use crate::filter::Filter;
use std::fmt;
use std::hash::Hash;
use std::sync::{Arc, RwLock};

#[derive(Debug, Clone)]
pub struct SyncFilter<K> {
    inner: Arc<RwLock<Filter<K>>>,
}

impl<K> fmt::Display for SyncFilter<K>
where
    K: Hash,
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "(sync) {}", self.inner.read().unwrap())
    }
}

/// Thread-safe opinonated Bloom filter.
///
/// Same as the standard non thread-safe version, simply
/// save to use in concurrent environments.
///
/// # Examples
///
/// ```
/// use std::thread;
/// use ofilter::SyncFilter;
///
/// let filter = SyncFilter::new(100);
///
/// let f = filter.clone();
/// let handle1 = thread::spawn(move || {
///     for i in 0..10_000 {
///         f.set(&i);
///     }
/// });
///
/// let f = filter.clone();
/// let handle2 = thread::spawn(move || {
///     for i in 0..10_000 {
///         f.check(&i);
///     }
/// });
///
/// handle1.join().unwrap();
/// handle2.join().unwrap();
/// ```
impl<K> SyncFilter<K> {
    pub fn new(capacity: usize) -> Self {
        SyncFilter {
            inner: Arc::new(RwLock::new(Filter::new(capacity))),
        }
    }

    pub fn level(&self) -> f64 {
        self.inner.read().unwrap().level()
    }

    pub fn age(&self) -> usize {
        self.inner.read().unwrap().age()
    }

    pub fn capacity(&self) -> usize {
        self.inner.read().unwrap().capacity()
    }

    pub fn resize(&self, capacity: usize) {
        self.inner.write().unwrap().resize(capacity);
    }

    pub fn clear(&self) {
        self.inner.write().unwrap().clear();
    }
}

impl<K> SyncFilter<K>
where
    K: Hash,
{
    pub fn set(&self, key: &K) {
        self.inner.write().unwrap().set(key);
    }

    pub fn check(&self, key: &K) -> bool {
        self.inner.read().unwrap().check(key)
    }
}

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

    #[test]
    fn test_sync_filter_concurrent() {
        let filter = SyncFilter::new(100);
        let f = filter.clone();
        let handle1 = thread::spawn(move || {
            for i in 0..10_000 {
                f.set(&i);
            }
        });
        let f = filter.clone();
        let handle2 = thread::spawn(move || {
            for i in 0..10_000 {
                f.check(&i);
            }
        });
        handle1.join().unwrap();
        handle2.join().unwrap();
    }
}
