//! Bob Jenkins' Hash algorithm, ported from <https://github.com/ceph/libcrush/blob/v1.0.0/crush/hash.c>.

// Below is the origianl copyright from libcrush.
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2015 Intel Corporation All Rights Reserved
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

const HASH_SEED: u32 = 1315423911;

macro_rules! hashmix {
    ($a:ident, $b:ident, $c:ident) => {
        $a = $a - $b;
        $a = $a - $c;
        $a = $a ^ ($c >> 13);
        $b = $b - $c;
        $b = $b - $a;
        $b = $b ^ ($a << 8);
        $c = $c - $a;
        $c = $c - $b;
        $c = $c ^ ($b >> 13);
        $a = $a - $b;
        $a = $a - $c;
        $a = $a ^ ($c >> 12);
        $b = $b - $c;
        $b = $b - $a;
        $b = $b ^ ($a << 16);
        $c = $c - $a;
        $c = $c - $b;
        $c = $c ^ ($b >> 5);
        $a = $a - $b;
        $a = $a - $c;
        $a = $a ^ ($c >> 3);
        $b = $b - $c;
        $b = $b - $a;
        $b = $b ^ ($a << 10);
        $c = $c - $a;
        $c = $c - $b;
        $c = $c ^ ($b >> 15);
    };
}

/// Hash32 algorithm with 1 parameter.
pub fn hash32_1(mut a: u32) -> u32 {
    let mut hash: u32 = HASH_SEED ^ a;
    let mut b: u32 = a;
    let mut x: u32 = 231232;
    let mut y: u32 = 1232;
    hashmix!(b, x, hash);
    hashmix!(y, a, hash);
    hash
}

/// Hash32 algorithm with 2 parameters.
pub fn hash32_2(mut a: u32, mut b: u32) -> u32 {
    let mut hash: u32 = HASH_SEED ^ a ^ b;
    let mut x: u32 = 231232;
    let mut y: u32 = 1232;
    hashmix!(a, b, hash);
    hashmix!(x, a, hash);
    hashmix!(b, y, hash);
    hash
}

/// Hash32 algorithm with 3 parameters.
pub fn hash32_3(mut a: u32, mut b: u32, mut c: u32) -> u32 {
    let mut hash: u32 = HASH_SEED ^ a ^ b ^ c;
    let mut x: u32 = 231232;
    let mut y: u32 = 1232;
    hashmix!(a, b, hash);
    hashmix!(c, x, hash);
    hashmix!(y, a, hash);
    hashmix!(b, x, hash);
    hashmix!(y, c, hash);
    hash
}

/// Hash32 algorithm with 4 parameters.
pub fn hash32_4(mut a: u32, mut b: u32, mut c: u32, mut d: u32) -> u32 {
    let mut hash: u32 = HASH_SEED ^ a ^ b ^ c ^ d;
    let mut x: u32 = 231232;
    let mut y: u32 = 1232;
    hashmix!(a, b, hash);
    hashmix!(c, d, hash);
    hashmix!(a, x, hash);
    hashmix!(y, b, hash);
    hashmix!(c, x, hash);
    hashmix!(y, d, hash);
    hash
}

/// Hash32 algorithm with 5 parameters.
pub fn hash32_5(mut a: u32, mut b: u32, mut c: u32, mut d: u32, mut e: u32) -> u32 {
    let mut hash: u32 = HASH_SEED ^ a ^ b ^ c ^ d ^ e;
    let mut x: u32 = 231232;
    let mut y: u32 = 1232;
    hashmix!(a, b, hash);
    hashmix!(c, d, hash);
    hashmix!(e, x, hash);
    hashmix!(y, a, hash);
    hashmix!(b, x, hash);
    hashmix!(y, c, hash);
    hashmix!(d, x, hash);
    hashmix!(y, e, hash);
    hash
}

#[cfg(any(
    all(target_os = "linux", target_arch = "x86_64"),
    all(target_os = "macos", target_arch = "aarch64")
))]
#[cfg(test)]
mod tests {
    use super::*;
    use rand::prelude::*;

    // from bindgen
    pub const CRUSH_HASH_RJENKINS1: u32 = 0;
    #[allow(non_camel_case_types)]
    pub type __u32 = ::std::os::raw::c_uint;
    unsafe extern "C" {
        pub fn crush_hash32(type_: ::std::os::raw::c_int, a: __u32) -> __u32;
        pub fn crush_hash32_2(type_: ::std::os::raw::c_int, a: __u32, b: __u32) -> __u32;
        pub fn crush_hash32_3(type_: ::std::os::raw::c_int, a: __u32, b: __u32, c: __u32) -> __u32;
        pub fn crush_hash32_4(
            type_: ::std::os::raw::c_int,
            a: __u32,
            b: __u32,
            c: __u32,
            d: __u32,
        ) -> __u32;
        pub fn crush_hash32_5(
            type_: ::std::os::raw::c_int,
            a: __u32,
            b: __u32,
            c: __u32,
            d: __u32,
            e: __u32,
        ) -> __u32;
    }

    #[test]
    fn test_hash32_3() {
        let mut rng = rand::rng();
        let mut data = [0u32; 5];
        let type_ = CRUSH_HASH_RJENKINS1 as ::std::os::raw::c_int;
        let (mut c_res, mut rs_res): (u32, u32);
        for _ in 0..1000000 {
            rng.fill(&mut data[..]);
            c_res = unsafe { crush_hash32(type_, data[0]) };
            rs_res = hash32_1(data[0]);
            assert_eq!(c_res, rs_res);
            c_res = unsafe { crush_hash32_2(type_, data[0], data[1]) };
            rs_res = hash32_2(data[0], data[1]);
            assert_eq!(c_res, rs_res);
            c_res = unsafe { crush_hash32_3(type_, data[0], data[1], data[2]) };
            rs_res = hash32_3(data[0], data[1], data[2]);
            assert_eq!(c_res, rs_res);
            c_res = unsafe { crush_hash32_4(type_, data[0], data[1], data[2], data[3]) };
            rs_res = hash32_4(data[0], data[1], data[2], data[3]);
            assert_eq!(c_res, rs_res);
            c_res = unsafe { crush_hash32_5(type_, data[0], data[1], data[2], data[3], data[4]) };
            rs_res = hash32_5(data[0], data[1], data[2], data[3], data[4]);
            assert_eq!(c_res, rs_res);
        }
    }
}
