use std::hash::{BuildHasher, Hasher};

#[inline(always)]
pub fn hash64(mut hash: u64) -> u64 {
    hash = (hash ^ hash >> 32).wrapping_mul(1645605008518198613);
    hash = (hash ^ hash >> 32).wrapping_mul(1645605008518198613);
    hash ^ hash >> 32
}
#[inline(always)]
pub fn hash64_simple(mut hash: u64) -> u64 {
    hash = (hash ^ hash >> 32).wrapping_mul(1645605008518198613);
    hash ^ hash >> 32
}
#[inline(always)]
pub fn mix2_64(x: u64, y: u64) -> u64 {
    x.wrapping_add(y) ^ y << 10
}
#[inline(always)]
pub fn mix2_64_alternative(x: u64, y: u64) -> u64 {
    x.wrapping_add(y) ^ x << 12
}
#[inline(always)]
pub fn mix3_64(x: u64, y: u64, z: u64) -> u64 {
    x ^ 7587920117383716314 ^ (y.wrapping_add(z) ^ z << 22)
}

trait AsUIntPtr {
    fn u8_ptr(self) -> *const u8;
    fn u16_ptr(self) -> *const u16;
    fn u32_ptr(self) -> *const u32;
    fn u64_ptr(self) -> *const u64;
}
impl<T> AsUIntPtr for *const T {
    #[inline(always)]
    fn u8_ptr(self) -> *const u8 {
        self as *const u8
    }
    #[inline(always)]
    fn u16_ptr(self) -> *const u16 {
        self as *const u16
    }
    #[inline(always)]
    fn u32_ptr(self) -> *const u32 {
        self as *const u32
    }
    #[inline(always)]
    fn u64_ptr(self) -> *const u64 {
        self as *const u64
    }
}

pub struct FHasher {
    hash: u64,
}
impl FHasher {
    #[inline(always)]
    pub fn new() -> Self {
        Self { hash: 0 }
    }
}
impl Default for FHasher {
    #[inline(always)]
    fn default() -> Self {
        Self { hash: 0 }
    }
}
impl Hasher for FHasher {
    #[inline(always)]
    fn finish(&self) -> u64 {
        hash64_simple(self.hash)
    }
    #[inline(always)]
    fn write_u8(&mut self, i: u8) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_u16(&mut self, i: u16) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_u32(&mut self, i: u32) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_u64(&mut self, i: u64) {
        self.hash = mix2_64_alternative(self.hash, i);
    }
    #[inline(always)]
    fn write_u128(&mut self, i: u128) {
        self.hash = mix2_64_alternative(self.hash, unsafe {
            mix2_64(
                *(&i as *const _ as *const u64),
                *(&i as *const _ as *const u64).add(8),
            )
        });
    }

    #[inline(always)]
    fn write_i8(&mut self, i: i8) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_i16(&mut self, i: i16) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_i32(&mut self, i: i32) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_i64(&mut self, i: i64) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_i128(&mut self, i: i128) {
        self.hash = mix2_64_alternative(self.hash, unsafe {
            mix2_64(
                *(&i as *const _ as *const u64),
                *(&i as *const _ as *const u64).add(8),
            )
        });
    }

    #[inline(always)]
    fn write_usize(&mut self, i: usize) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }
    #[inline(always)]
    fn write_isize(&mut self, i: isize) {
        self.hash = mix2_64_alternative(self.hash, i as u64);
    }

    #[inline(always)]
    fn write(&mut self, bytes: &[u8]) {
        let bytes_ptr = bytes.as_ptr();
        let bytes_len = bytes.len();

        let mut hash = bytes_len as u64 ^ self.hash;

        self.hash = unsafe {
            if bytes_len <= 16 {
                mix2_64_alternative(
                    hash,
                    if bytes_len < 9 {
                        if bytes_len <= 1 {
                            if bytes_len < 1 {
                                // 0 bytes
                                0
                            } else {
                                // 1 bytes
                                bytes_ptr.read() as u64
                            }
                        } else {
                            if bytes_len < 4 {
                                // 2-3 bytes
                                (bytes_ptr.u16_ptr().read() as u64) << 8
                                    | bytes_ptr.add(bytes_len).sub(1).read() as u64
                            } else {
                                // 4-8 bytes
                                (bytes_ptr.u32_ptr().read() as u64) << 32
                                    | bytes_ptr.add(bytes_len).sub(4).u32_ptr().read_unaligned()
                                        as u64
                            }
                        }
                    } else {
                        mix2_64(
                            // Read first 8 bytes / 64 bits
                            bytes_ptr.u64_ptr().read(),
                            // Read last 8 bytes / 64 bits
                            bytes_ptr.add(bytes_len).sub(8).u64_ptr().read_unaligned(),
                        )
                    },
                )
            } else {
                if bytes_len <= 32 {
                    hash = mix3_64(
                        hash,
                        bytes_ptr.u64_ptr().read(),
                        bytes_ptr.add(8).u64_ptr().read(),
                    );
                    let last_ptr = bytes_ptr.add(bytes_len);
                    if bytes_len < 25 {
                        mix2_64(hash, last_ptr.sub(8).u64_ptr().read_unaligned())
                    } else {
                        mix3_64(
                            hash,
                            last_ptr.sub(16).u64_ptr().read_unaligned(),
                            last_ptr.sub(8).u64_ptr().read_unaligned(),
                        )
                    }
                } else {
                    let end_bytes = bytes_len & (32 - 1);
                    let end_ptr = bytes_ptr.add(bytes_len & !end_bytes);

                    let mut chunk_ptr = bytes_ptr;
                    loop {
                        // Read 16 bytes / 128 bits chunk
                        hash = mix3_64(
                            mix3_64(
                                hash,
                                chunk_ptr.u64_ptr().read(),
                                chunk_ptr.add(8).u64_ptr().read(),
                            ),
                            chunk_ptr.add(16).u64_ptr().read(),
                            chunk_ptr.add(24).u64_ptr().read(),
                        );

                        chunk_ptr = chunk_ptr.add(32);
                        if chunk_ptr >= end_ptr {
                            break;
                        }
                    }

                    // println!("{}", end_bytes);
                    let last_ptr = bytes_ptr.add(bytes_len);
                    if end_bytes < 9 {
                        mix2_64(
                            hash,
                            // Hash last 16 bytes / 128 bits
                            // read_unaligned beacause it can be undefined behavior
                            // to read unaligned pointers on some systems
                            last_ptr.sub(8).u64_ptr().read_unaligned(),
                        )
                    } else {
                        if end_bytes < 17 {
                            // last_ptr = last_ptr.sub(16);
                            mix3_64(
                                hash,
                                // Hash last 16 bytes / 128 bits
                                // read_unaligned beacause it can be undefined behavior
                                // to read unaligned pointers on some systems
                                last_ptr.sub(16).u64_ptr().read_unaligned(),
                                last_ptr.sub(8).u64_ptr().read_unaligned(),
                            )
                        } else {
                            // last_ptr = last_ptr.sub(32);
                            mix3_64(
                                mix3_64(
                                    hash,
                                    // Hash last 16 bytes / 128 bits
                                    // read_unaligned beacause it can be undefined behavior
                                    // to read unaligned pointers on some systems
                                    last_ptr.sub(32).u64_ptr().read_unaligned(),
                                    last_ptr.sub(24).u64_ptr().read_unaligned(),
                                ),
                                // Hash last 16 bytes / 128 bits
                                // read_unaligned beacause it can be undefined behavior
                                // to read unaligned pointers on some systems
                                last_ptr.sub(16).u64_ptr().read_unaligned(),
                                last_ptr.sub(8).u64_ptr().read_unaligned(),
                            )
                        }
                    }
                }
            }
        };
    }
}

#[derive(Clone)]
pub struct FHasherBuilder {
    seed: u64,
}
impl FHasherBuilder {
    #[inline]
    fn new() -> Self {
        Self {
            seed: frand::Rand::new().gen::<u64>(),
        }
    }
}
impl Default for FHasherBuilder {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}
impl BuildHasher for FHasherBuilder {
    type Hasher = FHasher;

    fn build_hasher(&self) -> Self::Hasher {
        FHasher { hash: self.seed }
    }
}

pub type HashMap<K, V> = hashbrown::HashMap<K, V, FHasherBuilder>;
pub type HashSet<K, V> = hashbrown::HashSet<K, V, FHasherBuilder>;
