use crate::hash_value::HashValue;

/// A rolling hash function based on Cyclic Polynomials.
///
/// This is the generic hash that supports 32bit or 64bit hash values.
pub struct CyclicPoly<T: HashValue> {
    current: T,
    reverse: [T; 256],
}

impl<T: HashValue> CyclicPoly<T> {
    /// Create a new hash function that will calculate the hash of blocks with the given size.
    ///
    /// # Example
    /// ```rust
    /// use cyclic_poly_23::CyclicPoly32;
    ///
    /// let data = vec![0, 1, 2, 3];
    /// let mut hasher = CyclicPoly32::new(4);
    /// let hash_value = hasher.calculate(&data);
    /// assert_eq!(hash_value, 0xecf6e475);
    /// ```
    pub fn new(block_size: usize) -> Self {
        let n: u32 = block_size as u32;
        let mut ret = Self {
            current: T::zero(),
            reverse: [T::zero(); 256],
        };
        for i in 0..ret.reverse.len() {
            ret.reverse[i] = T::TRANSFORMATION[i].rotate_left(n);
        }
        ret
    }

    /// Initialize a hash function from the given block.
    ///
    /// # Example
    /// ```rust
    /// use cyclic_poly_23::CyclicPoly32;
    ///
    /// let data = vec![0, 1, 2, 3];
    /// let mut hasher = CyclicPoly32::from_block(&data);
    /// let hash_value = hasher.value();
    /// assert_eq!(hash_value, 0xecf6e475);
    /// ```
    pub fn from_block(block: &[u8]) -> Self {
        let mut ret = Self::new(block.len());
        ret.calculate(block);
        ret
    }

    /// Get the current hash value.
    pub fn value(&self) -> T {
        self.current
    }

    /// Calculate a hash value of a new block of data by rotating out an
    /// old byte and adding a new byte.
    ///
    /// # Example
    /// ```rust
    /// use cyclic_poly_23::CyclicPoly32;
    ///
    /// let data = vec![8, 0, 1, 2, 3];
    /// let mut hasher = CyclicPoly32::new(4);
    /// hasher.calculate(&data[0..4]);
    /// let hash_value = hasher.rotate(data[0], data[4]);
    /// assert_eq!(hash_value, 0xecf6e475);
    /// ```
    pub fn rotate(&mut self, old_byte: u8, new_byte: u8) -> T {
        let t_in = T::TRANSFORMATION[usize::from(new_byte)];
        let t_out = self.reverse[usize::from(old_byte)];
        let current = self.current.rotate_left(1);
        let current = current ^ t_in;
        self.current = current ^ t_out;
        self.current
    }

    /// Reset the hash function and calculate the hash value of the given block.
    pub fn calculate(&mut self, block: &[u8]) -> T {
        for v in block.iter() {
            let current = self.current.rotate_left(1);
            let b = usize::from(*v);
            let t = T::TRANSFORMATION[b];
            self.current = current ^ t;
        }
        self.current
    }

    /// Given the hash value of a parent, and the second child block, calculate the hash
    /// value of the first child.
    ///
    /// # Example
    /// ```rust
    /// use cyclic_poly_23::CyclicPoly32;
    ///
    /// let data = vec![0, 1, 2, 3, 4, 5, 6, 7];
    /// let parent = CyclicPoly32::from_block(&data).value();
    /// let second_child = CyclicPoly32::from_block(&data[4..8]).value();
    /// let first_child = CyclicPoly32::first_child(parent, second_child, 4);
    /// assert_eq!(first_child, 0xecf6e475);
    /// ```
    pub fn first_child(parent: T, second_child: T, second_block_size: usize) -> T {
        let second_block_size = second_block_size as u32;
        let first_brotl = parent ^ second_child;
        first_brotl.rotate_right(second_block_size)
    }

    /// Given the hash value of a parent, and the first child block, calculate the hash
    /// value of the second child.
    ///
    /// # Example
    /// ```rust
    /// use cyclic_poly_23::CyclicPoly32;
    ///
    /// let data = vec![7, 6, 5, 4, 0, 1, 2, 3];
    /// let parent = CyclicPoly32::from_block(&data).value();
    /// let first_child = CyclicPoly32::from_block(&data[0..4]).value();
    /// let second_child = CyclicPoly32::second_child(parent, first_child, 4);
    /// assert_eq!(second_child, 0xecf6e475);
    /// ```
    pub fn second_child(parent: T, first_child: T, second_block_size: usize) -> T {
        let second_block_size = second_block_size as u32;
        let first_brotl = first_child.rotate_left(second_block_size);
        parent ^ first_brotl
    }

    /// Given the hash value of two consecutive blocks, calculate the hash value
    /// of both blocks together.
    ///
    /// # Example
    /// ```rust
    /// use cyclic_poly_23::CyclicPoly32;
    ///
    /// let data = vec![0, 1, 2, 3, 4, 5, 6, 7];
    /// let expected_parent = CyclicPoly32::from_block(&data).value();
    /// let first_child = CyclicPoly32::from_block(&data[0..4]).value();
    /// let second_child = CyclicPoly32::from_block(&data[4..8]).value();
    /// let parent = CyclicPoly32::parent(first_child, second_child, 4);
    /// assert_eq!(parent, expected_parent);
    /// ```
    pub fn parent(first_child: T, second_child: T, second_block_size: usize) -> T {
        let second_block_size = second_block_size as u32;
        let first_brotl = first_child.rotate_left(second_block_size);
        first_brotl ^ second_child
    }
}

#[cfg(test)]
mod tests {
    use crate::{cyclic_poly::CyclicPoly, hash_value::HashValue};
    use quickcheck::TestResult;
    use quickcheck_macros::quickcheck;

    #[quickcheck]
    fn sensitivity64(data0: Vec<u8>, i: usize) -> TestResult {
        sensitivity_check::<u64>(data0, i)
    }

    #[quickcheck]
    fn sensitivity32(data0: Vec<u8>, i: usize) -> TestResult {
        sensitivity_check::<u32>(data0, i)
    }

    fn sensitivity_check<T: HashValue>(data0: Vec<u8>, i: usize) -> TestResult {
        if data0.is_empty() {
            return TestResult::discard();
        }
        let mut data1 = data0.clone();
        data1[i % data0.len()] ^= 0xaa;
        let hash0: T = CyclicPoly::from_block(&data0).value();
        let hash1: T = CyclicPoly::from_block(&data1).value();
        TestResult::from_bool(hash0 != hash1)
    }

    #[quickcheck]
    fn roll64(data: Vec<u8>) -> TestResult {
        roll_check::<u64>(data)
    }

    #[quickcheck]
    fn roll32(data: Vec<u8>) -> TestResult {
        roll_check::<u32>(data)
    }

    fn roll_check<T: HashValue>(data: Vec<u8>) -> TestResult {
        if data.len() % 2 != 0 {
            return TestResult::discard();
        }
        let data = data.as_slice();
        let data_len = data.len();
        let n = data_len / 2;
        let last_block = &data[(data_len - n)..data_len];
        let full: T = CyclicPoly::from_block(last_block).value();
        let mut rolling: CyclicPoly<T> = CyclicPoly::new(n);
        rolling.calculate(&data[0..n]);
        for i in n..data_len {
            rolling.rotate(data[i - n], data[i]);
        }
        TestResult::from_bool(full == rolling.value())
    }

    #[quickcheck]
    fn decompose64(data: Vec<u8>, cut: usize) -> TestResult {
        decompose_check::<u64>(data, cut)
    }

    #[quickcheck]
    fn decompose32(data: Vec<u8>, cut: usize) -> TestResult {
        decompose_check::<u32>(data, cut)
    }

    // Test the first_child, second_child and parent methods by randomly cutting
    // a random vector.
    fn decompose_check<T: HashValue>(data: Vec<u8>, cut: usize) -> TestResult {
        let data = data.as_slice();
        let data_len = data.len();
        // Cut the data 0..(data_len - 1)
        let cut = match (data_len, cut) {
            (0, _) => 0,
            (_, 0) => 0,
            (_, _) => cut % data_len,
        };
        let first_block = &data[0..cut];
        let second_block = &data[cut..data_len];
        let parent: T = CyclicPoly::from_block(data).value();
        let first: T = CyclicPoly::from_block(first_block).value();
        let second: T = CyclicPoly::from_block(second_block).value();
        TestResult::from_bool(
            first == CyclicPoly::first_child(parent, second, second_block.len())
                && second == CyclicPoly::second_child(parent, first, second_block.len())
                && parent == CyclicPoly::parent(first, second, second_block.len()),
        )
    }
}
