using System;

namespace ZeroLevel.Services.HashFunctions
{
    public class Murmur3Unsafe
        : IHash
    {
        private const uint Seed = 0xc58f1a7b;

        private const UInt32 c1 = 0xcc9e2d51;
        private const UInt32 c2 = 0x1b873593;

        public unsafe UInt32 Hash(string s)
        {
            fixed (char* input = s)
            {
                return Hash((byte*)input, (uint)s.Length * sizeof(char), Seed);
            }
        }

        public unsafe uint Hash(byte[] data)
        {
            fixed (byte* input = &data[0])
            {
                return Hash(input, (uint)data.Length, Seed);
            }
        }

        public unsafe uint Hash(byte[] data, int offset, uint len, uint seed)
        {
            fixed (byte* input = &data[offset])
            {
                return Hash(input, len, seed);
            }
        }

        private unsafe static uint Hash(byte* data, uint len, uint seed)
        {
            UInt32 nblocks = len / 4;
            UInt32 h1 = seed;

            //----------
            // body

            UInt32 k1;
            UInt32* block = (UInt32*)data;
            for (UInt32 i = nblocks; i > 0; --i, ++block)
            {
                k1 = *block;

                k1 *= c1;
                k1 = Rotl32(k1, 15);
                k1 *= c2;

                h1 ^= k1;
                h1 = Rotl32(h1, 13);
                h1 = h1 * 5 + 0xe6546b64;
            }

            //----------
            // tail


            k1 = 0;
            uint rem = len & 3;
            byte* tail = (byte*)block;
            if (rem >= 3)
                k1 ^= (uint)(tail[2] << 16);
            if (rem >= 2)
                k1 ^= (uint)(tail[1] << 8);
            if (rem > 0)
            {
                k1 ^= tail[0];
                k1 *= c1;
                k1 = Rotl32(k1, 15);
                k1 *= c2;
                h1 ^= k1;
            }

            //----------
            // finalization

            h1 ^= len;

            h1 ^= h1 >> 16;
            h1 *= 0x85ebca6b;
            h1 ^= h1 >> 13;
            h1 *= 0xc2b2ae35;
            h1 ^= h1 >> 16;

            return h1;
        }

        private static UInt32 Rotl32(UInt32 x, int r)
        {
            return (x << r) | (x >> (32 - r));
        }
    }
}