use std::{
    hash::Hasher,
    io::{self, BufWriter, Read, Write},
};

use crate::{
    hash::fnv::Fnv1Hasher,
    read_flags,
    utils::{read_le_f64, read_le_u64},
    Flags, Params,
};

use super::utils;

// this is the largest prime number < 2^64. As we will probably never encounter
// a Bloom filter with a number of bits > m (if yes sorry to you people from,
// the future, I envy your available RAM though) we can use the pseudorandom
// sequence generated by repeatedly multiplying the initial hash value h
// with g and taking the modulo over m to generate a sequence of hash values
// that has a uniform distribution.
const M: u64 = 18446744073709551557;

// this is our multiplier. It has a very large primitive root
// so that it will not repeat a given cycle for any practically meaningful
// value of k.
const G: u64 = 18446744073709550147;

#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct Fingerprint {
    h: u64,
    m: u64,
    i: u64,
    count: u64,
}

impl Fingerprint {
    fn new(count: u64, modulo: u64) -> Self {
        Self {
            m: modulo,
            count,
            ..Default::default()
        }
    }

    #[inline(always)]
    fn fingerprint<S: AsRef<[u8]>>(mut self, data: S) -> Self {
        let mut hasher = Fnv1Hasher::default();
        hasher.write(data.as_ref());
        self.h = hasher.finish() % M;
        self.i = 0;
        self
    }
}

impl Iterator for Fingerprint {
    type Item = u64;

    fn next(&mut self) -> Option<Self::Item> {
        if self.i < self.count {
            self.h = self.h.wrapping_mul(G) % M;
            self.i = self.i.wrapping_add(1);
            return Some(self.h % self.m);
        }
        None
    }
}
type Error = crate::Error;

/// This structure implements a bloom filter compatible with
/// [DSCO implementation](https://github.com/DCSO/bloom). It must
/// be use for compatibility purposes only. Its use is deprecated
/// as its fpp is not guaranteed to be correct. Use [crate::v2::BloomFilter]
/// instead.
#[derive(Debug, Clone)]
pub struct BloomFilter {
    flags: Flags,
    // M == bitset.len()
    pub(crate) bitset: Vec<u64>,
    // n: desired maximum number of elements
    pub(crate) capacity: u64,
    // desired false positive probability
    pub(crate) fpp: f64,
    // k: number of hash functions
    pub(crate) n_hash: u64,
    // m: number of bits
    pub(crate) bit_size: u64,
    // N: number of elements in the filter
    pub(crate) count: u64,
    // arbitrary data that we can attach to the filter
    pub data: Vec<u8>,
    fingerprint: Fingerprint,
}

impl From<Params> for BloomFilter {
    fn from(p: Params) -> Self {
        Self::with_capacity(p.capacity as u64, p.fpp)
    }
}

impl BloomFilter {
    pub fn with_capacity(cap: u64, proba: f64) -> Self {
        // size in bits, computed from the capacity we want and the probability
        let bit_size = utils::bit_size(cap, proba);

        // size in u64
        let u64_size = f64::ceil(bit_size as f64 / 64.0) as usize;

        let n_hash_fn = utils::k(bit_size, cap);

        Self {
            flags: Flags::new(1),
            bitset: vec![0; u64_size],
            capacity: cap,
            fpp: proba,
            n_hash: n_hash_fn,
            bit_size,
            count: 0,
            data: vec![],
            fingerprint: Fingerprint::new(n_hash_fn, bit_size),
        }
    }

    #[inline(always)]
    pub fn estimated_p(&self) -> f64 {
        utils::estimate_p(self.count_estimate(), self.bit_size)
    }

    #[inline(always)]
    pub fn n_hash_fn(&self) -> u64 {
        self.n_hash
    }

    #[inline(always)]
    pub fn n_bits(&self) -> u64 {
        self.bit_size
    }

    #[inline]
    pub fn from_reader<R: Read>(r: R) -> Result<Self, Error> {
        let mut br = io::BufReader::new(r);
        let r = &mut br;

        let flags = read_flags(r)?;
        if flags.version != 1 {
            return Err(Error::InvalidVersion(flags.version));
        }
        Self::from_reader_with_flags(r, flags)
    }

    #[inline]
    pub(crate) fn from_reader_with_flags<R: Read>(r: R, flags: Flags) -> Result<Self, Error> {
        let mut br = io::BufReader::new(r);
        let r = &mut br;

        let cap = read_le_u64(r)?;
        let p = read_le_f64(r)?;
        let n_hash = read_le_u64(r)?;
        let bit_size = read_le_u64(r)?;
        let count = read_le_u64(r)?;

        // initializing bitset
        let u64_size = f64::ceil((bit_size as f64) / 64.0) as u64;
        let mut bitset = vec![0; u64_size as usize];

        // reading the bloom filter
        for i in bitset.iter_mut() {
            *i = read_le_u64(r)?;
        }

        // reading data
        let mut data = Vec::new();
        r.read_to_end(&mut data)?;

        let fingerprint = Fingerprint::new(n_hash, bit_size);

        let b = BloomFilter {
            flags,
            bitset,
            capacity: cap,
            fpp: p,
            n_hash,
            bit_size,
            count,
            data,
            fingerprint,
        };

        Ok(b)
    }

    #[inline]
    pub fn write<W: Write>(&self, w: &mut W) -> Result<(), Error> {
        let mut w = BufWriter::new(w);

        // writing version
        w.write_all(&Flags::new(1).to_bytes())?;

        w.write_all(&self.capacity.to_le_bytes())?;
        w.write_all(&self.fpp.to_le_bytes())?;
        w.write_all(&self.n_hash.to_le_bytes())?;
        w.write_all(&self.bit_size.to_le_bytes())?;
        w.write_all(&self.count.to_le_bytes())?;

        for i in self.bitset.iter() {
            w.write_all(&i.to_le_bytes())?;
        }

        w.write_all(&self.data)?;
        Ok(())
    }

    #[inline(always)]
    /// get the nth bit value
    fn get_nth_bit(&self, index: u64) -> bool {
        let iblock = index / 64;
        let ibit = index % 64;
        // we cannot overflow shift as ibit < 64
        let bit = 1u64.wrapping_shl(ibit as u32);
        self.bitset[iblock as usize] & bit == bit
    }

    pub fn bits(&self) -> Vec<bool> {
        (0..self.bit_size)
            .map(|i| self.get_nth_bit(i))
            .collect::<Vec<bool>>()
    }

    /// inserts a value into the bloom filter, as bloom filters are not easily
    /// growable an error is returned if we try to insert too many entries
    #[inline(always)]
    pub fn insert_bytes<S: AsRef<[u8]>>(&mut self, value: S) -> Result<bool, Error> {
        let mut new = false;

        if self.capacity == 0 {
            return Err(Error::TooManyEntries);
        }

        for index in self.fingerprint.fingerprint(value) {
            let iblock = index / 64;
            let ibit = index % 64;

            let entry = self
                .bitset
                .get_mut(iblock as usize)
                .expect("block index out of bound");

            // we cannot overflow shift as ibit < 64
            let bit = 1u64.wrapping_shl(ibit as u32);

            // this is the old bit value
            let old = *entry & bit == bit;
            if !old && self.count >= self.capacity {
                return Err(Error::TooManyEntries);
            }

            // we update entry
            *entry |= bit;

            if !old {
                new = true
            }

            debug_assert!(self.get_nth_bit(index))
        }

        if new {
            self.count += 1;
        }

        Ok(new)
    }

    /// clears out the bloom filter
    #[inline(always)]
    pub fn clear(&mut self) {
        self.bitset.iter_mut().for_each(|bucket| *bucket = 0);
        self.count = 0;
    }

    /// checks if an entry is contained in the bloom filter
    #[inline(always)]
    pub fn contains_bytes<S: AsRef<[u8]>>(&self, value: S) -> bool {
        let mut ret = false;
        for index in self.fingerprint.fingerprint(value) {
            ret |= self.get_nth_bit(index);
            // if one bit is missing we early return
            if !ret {
                return false;
            }
        }
        ret
    }

    /// counts all the set bits in the bloom filter
    #[inline(always)]
    pub fn count_ones(&self) -> usize {
        self.bitset.iter().map(|u| u.count_ones() as usize).sum()
    }

    /// counts all the unset bits in the bloom filter
    #[inline(always)]
    pub fn count_zeros(&self) -> usize {
        self.bitset.iter().map(|u| u.count_zeros() as usize).sum()
    }

    /// function used to update the estimated count of entries
    fn update_count(&mut self) {
        self.count = (-(self.bit_size as f64
            * f64::ln(1.0 - (self.count_ones() as f64 / self.bit_size as f64)))
            / self.n_hash as f64) as u64;
    }

    /// returns an estimate of the number of element in the filter
    /// the exact number of element cannot be known as there might
    /// be collisions
    #[inline(always)]
    pub fn count_estimate(&self) -> u64 {
        self.count
    }

    #[inline(always)]
    pub fn size_in_u64(&self) -> usize {
        self.bitset.len()
    }

    #[inline(always)]
    pub fn size_in_bytes(&self) -> usize {
        self.bitset.len() * core::mem::size_of::<u64>()
    }

    #[inline(always)]
    pub fn has_same_params(&self, other: &Self) -> bool {
        self.flags == other.flags
            && self.capacity == other.capacity
            && self.fpp == other.fpp
            && self.n_hash == other.n_hash
            && self.bit_size == other.bit_size
            && self.bitset.len() == other.bitset.len()
    }

    #[inline(always)]
    fn count_ones_in_common(&self, other: &Self) -> usize {
        assert_eq!(
            self.bitset.len(),
            other.bitset.len(),
            "filters must have same lengths"
        );
        (0..self.bitset.len())
            .map(|i| (self.bitset[i] & other.bitset[i]).count_ones() as usize)
            .sum()
    }

    /// this function estimates the number of common elements between two bloom filters
    /// without having to intersect them
    pub fn count_common_entries(&self, other: &Self) -> Result<usize, Error> {
        if !self.has_same_params(other) {
            return Err(Error::Merge(
                "cannot compare filters with different parameters".into(),
            ));
        }

        Ok((-(self.bit_size as f64
            * f64::ln(1.0 - (self.count_ones_in_common(other) as f64 / self.bit_size as f64)))
            / self.n_hash as f64) as usize)
    }

    /// makes the union of self with another bloom filter (having the same
    /// parameters)
    #[inline]
    pub fn union_merge(&mut self, other: &Self) -> Result<(), Error> {
        if !self.has_same_params(other) {
            return Err(Error::Merge(
                "cannot make union of bloom filters with different parameters".into(),
            ));
        }

        for (i, e) in self.bitset.iter_mut().enumerate() {
            *e |= other.bitset[i];
        }

        // we need to update the estimated number of elements after a union
        self.update_count();

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use std::{
        collections::HashSet,
        fs,
        io::{self, BufRead},
    };

    use rand::{rngs::StdRng, Rng, SeedableRng};

    use crate::utils::{benchmark, ByteSize, Stats};

    use super::*;

    macro_rules! bloom {
        ($cap:expr, $proba:expr) => {
            $crate::v1::BloomFilter::with_capacity($cap, $proba)
        };
        ($cap:expr, $proba:expr, [$($values:literal),*]) => {
            {
                let mut b=bloom!($cap, $proba);
                $(b.insert_bytes($values).unwrap();)*
                b
            }
        };
    }

    #[test]
    fn test_bloom() {
        let mut b = bloom!(100000, 0.001);
        assert!(!b.contains_bytes("value"));
        b.insert_bytes("value").unwrap();
        assert!(b.contains_bytes("value"));
        assert_eq!(b.count, 1);
        assert!(!b.contains_bytes("unknown"));
    }

    #[test]
    fn test_macro() {
        let b = bloom!(1000, 0.0001, ["hello", "world"]);
        assert!(b.contains_bytes("hello"));
        assert!(b.contains_bytes("world"));
    }

    #[test]
    fn test_serialization() {
        let b = bloom!(1000, 0.0001, ["deserialization", "test"]);
        let mut cursor = io::Cursor::new(vec![]);
        b.write(&mut cursor).unwrap();
        cursor.set_position(0);
        // deserializing the stuff out
        let b = BloomFilter::from_reader(cursor).unwrap();
        assert_eq!(b.fpp, 0.0001);
        assert!(b.contains_bytes("deserialization"));
        assert!(b.contains_bytes("test"));
        assert!(!b.contains_bytes("hello"));
        assert!(!b.contains_bytes("world"));
    }

    #[test]
    fn test_deserialization() {
        // this test file has been generated with Go bloom cli
        let data = include_bytes!("../data/test.bloom");
        let pb = bloom!(10000, 0.01);
        let r = io::BufReader::new(io::Cursor::new(data));

        let b = BloomFilter::from_reader(r).unwrap();
        // proba it's been serialzed with with Go implementation
        assert!(pb.has_same_params(&b));
        assert!(b.contains_bytes("hello"));
        assert!(b.contains_bytes("world"));
        assert!(!b.contains_bytes("hello world"));
    }

    #[test]
    fn test_union() {
        let mut b = bloom!(1000, 0.0001, ["hello", "world"]);
        let o = bloom!(1000, 0.0001, ["union", "test"]);

        b.union_merge(&o).unwrap();

        ["hello", "world", "union", "test"]
            .into_iter()
            .for_each(|v| {
                assert!(b.contains_bytes(v));
            });

        // estimate count should be exact for a small test like this
        assert_eq!(b.count_estimate(), 4);
    }

    #[test]
    fn test_union_failure() {
        let mut b = bloom!(1000, 0.0001, ["hello", "world"]);
        let o = bloom!(100, 0.0001, ["union", "test"]);

        assert!(b.union_merge(&o).is_err())
    }

    #[test]
    fn test_clear() {
        let mut b = bloom!(1000, 0.0001, ["hello", "world"]);

        assert_eq!(b.count_estimate(), 2);
        b.clear();
        assert!(!b.contains_bytes("hello"));
        assert!(!b.contains_bytes("world"));
        assert_eq!(b.count_estimate(), 0);
    }

    #[test]
    fn test_too_many_entries() {
        let mut b = bloom!(5, 0.0001, ["hello", "world", "toasting", "bloom", "filter"]);

        assert_eq!(b.count_estimate(), 5);
        assert!(matches!(
            b.insert_bytes("everything should explode, OMG !"),
            Err(Error::TooManyEntries)
        ));
    }

    #[test]
    fn test_pow_2() {
        let n = 483409697;
        let proba = 0.001;

        let bucket_size = 4096;
        let c = utils::cap_from_bit_size(bucket_size * 8, proba);
        let n_bucket = (n as f64 / c as f64).ceil() as u64;
        println!(
            "cap_per_bucket={c} n_bucket={n_bucket} size={} one_vec={}",
            ByteSize::from_bytes((bucket_size * n_bucket) as usize),
            ByteSize::from_bits(utils::bit_size(n, proba) as usize),
        )
    }

    #[test]
    fn test_show_bug_in_v1() {
        let fpp = 0.01;
        // 50% tolerance on the fpp
        let tolerance = 0.5;

        // this value of n produces a bitset with a size power of 2
        let n = 109397;
        let mut b = bloom!(n, fpp);

        assert!(b.bit_size.is_power_of_two());

        for i in 0..n {
            b.insert_bytes(i.to_le_bytes()).unwrap();
            assert!(b.contains_bytes(i.to_le_bytes()));
        }

        let mut s = Stats::new();
        for i in n..n * 2 {
            if b.contains_bytes(i.to_le_bytes()) {
                s.inc_fp()
            } else {
                s.inc_tn()
            }
        }

        assert!(s.fp_rate() > fpp + fpp * tolerance);
        println!("real fp rate = {} VS expected={fpp}", s.fp_rate());
    }

    #[test]
    fn test_contains_on_empty() {
        let b = bloom!(0, 0.001);
        assert!(!b.contains_bytes("42"))
    }

    #[test]
    #[ignore]
    fn benchmark_bloom() {
        let mut rng: StdRng = SeedableRng::from_seed([42; 32]);

        let root = std::env::current_dir().unwrap();
        let test_files = vec![root.join("src/data/sample.txt")];

        let mut lines = HashSet::new();
        let mut dataset_size = 0;
        for t in test_files {
            let f = fs::File::open(t).unwrap();
            let reader = io::BufReader::new(f);
            for line in reader.lines() {
                let line = line.unwrap();
                let size = line.as_bytes().len();
                if lines.insert(line) {
                    dataset_size += size
                }
            }
        }

        let count = lines.len() as u64;
        let fp_rate = 0.001;

        let p = Params::new(lines.len(), fp_rate)
            .opt(crate::OptLevel::None)
            .version(1);

        let mut b = p.into_v1();
        let mb_size = dataset_size as f64 / 1_048_576.0;
        let runs = 5;

        let insert_dur = benchmark(
            || {
                lines.iter().for_each(|l| {
                    b.insert_bytes(l).unwrap();
                })
            },
            runs,
        );

        let bit_size = utils::bit_size(count, fp_rate);
        eprintln!("count: {}", count);
        eprintln!("proba: {}", fp_rate);
        eprintln!(
            "bit_size:{} optimized: {} expected_proba: {}",
            ByteSize::from_bits(bit_size as usize),
            bit_size.is_power_of_two(),
            utils::estimate_p(lines.len() as u64, bit_size),
        );
        eprintln!(
            "data: {} entries -> {}",
            count,
            ByteSize::from_bytes(dataset_size)
        );

        eprintln!("\nInsertion performance:");
        eprintln!("\tinsert duration: {:?}", insert_dur);
        eprintln!(
            "\tinsertion speed: {:.1} entries/s -> {:.1} MB/s",
            count as f64 / insert_dur.as_secs_f64(),
            mb_size / insert_dur.as_secs_f64()
        );

        eprintln!("\nQuery performance:");
        for mut_prob in (0..=100).step_by(10) {
            let mut tmp = lines.iter().cloned().collect::<Vec<String>>();
            let mutated_lines = tmp
                .iter_mut()
                .map(|e| {
                    let mut mutated = false;
                    if rng.gen_range(0..=100) < mut_prob {
                        mutated = true;
                        unsafe { e.as_bytes_mut() }
                            .iter_mut()
                            .for_each(|b| *b ^= rng.gen_range(0..=255));
                    }
                    (mutated, e.clone())
                })
                .collect::<Vec<(bool, String)>>();

            let mut fp_count = 0usize;
            let mut tn_count = 0usize;
            let query_dur = benchmark(
                || {
                    mutated_lines.iter().for_each(|(m, l)| {
                        let is_in_bf = b.contains_bytes(l);
                        // data has been mutated
                        if *m {
                            if is_in_bf {
                                fp_count += 1
                            } else {
                                tn_count += 1
                            }
                        }
                    })
                },
                runs,
            );

            eprintln!(
                "\tcondition: {}% of queried values are in filter",
                100 - mut_prob
            );
            eprintln!("\tquery duration: {:?}", query_dur);
            eprintln!(
                "\tquery speed: {:.1} entries/s -> {:.1} MB/s",
                count as f64 / query_dur.as_secs_f64(),
                mb_size / query_dur.as_secs_f64()
            );
            eprintln!("\tfp = {}", fp_count);
            eprintln!("\ttn = {}", tn_count);
            eprintln!(
                "\tfp rate = {:3}",
                fp_count as f64 / (fp_count + tn_count) as f64
            );
            eprintln!();
        }

        eprintln!("Size in bytes: {}", ByteSize::from_bytes(b.size_in_bytes()));
        eprintln!("\nCollision information:");
        eprintln!("\texpected collision rate: {}", fp_rate);
        eprintln!(
            "\treal collision rate: {:.5}%",
            1.0 - (b.count_estimate() as f64) / count as f64
        );
    }
}
