using System;

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

        private const uint PRIME1 = 2654435761U;
        private const uint PRIME2 = 2246822519U;
        private const uint PRIME3 = 3266489917U;
        private const uint PRIME4 = 668265263U;
        private const int PRIME5 = 0x165667b1;

        private uint _bias;

        public XXHashUnsafe(uint bias = 0) => _bias = bias;

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

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

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

        private unsafe static uint Hash(byte* data, uint len, uint seed, uint bias)
        {
            if (len < 16)
                return HashSmall(data, len, seed, bias);

            uint v1 = seed + PRIME1 + bias;
            uint v2 = v1 * PRIME2 + len;
            uint v3 = v2 * PRIME3;
            uint v4 = v3 * PRIME4;

            uint* p = (uint*)data;
            uint* limit = (uint*)(data + len - 16);

            while (p < limit)
            {
                v1 += Rotl32(v1, 13); v1 *= PRIME1; v1 += *p; p++;
                v2 += Rotl32(v2, 11); v2 *= PRIME1; v2 += *p; p++;
                v3 += Rotl32(v3, 17); v3 *= PRIME1; v3 += *p; p++;
                v4 += Rotl32(v4, 19); v4 *= PRIME1; v4 += *p; p++;
            }

            p = limit;
            v1 += Rotl32(v1, 17); v2 += Rotl32(v2, 19); v3 += Rotl32(v3, 13); v4 += Rotl32(v4, 11);
            v1 *= PRIME1; v2 *= PRIME1; v3 *= PRIME1; v4 *= PRIME1;
            v1 += *p; p++; v2 += *p; p++; v3 += *p; p++; v4 += *p;
            v1 *= PRIME2; v2 *= PRIME2; v3 *= PRIME2; v4 *= PRIME2;
            v1 += Rotl32(v1, 11); v2 += Rotl32(v2, 17); v3 += Rotl32(v3, 19); v4 += Rotl32(v4, 13);
            v1 *= PRIME3; v2 *= PRIME3; v3 *= PRIME3; v4 *= PRIME3;

            uint crc = v1 + Rotl32(v2, 3) + Rotl32(v3, 6) + Rotl32(v4, 9);
            crc ^= crc >> 11;
            crc += (PRIME4 + len) * PRIME1;
            crc ^= crc >> 15;
            crc *= PRIME2;
            crc ^= crc >> 13;
            return crc;
        }

        private unsafe static uint HashSmall(byte* data, uint len, uint seed, uint bias)
        {
            byte* p = data;
            byte* bEnd = data + len;
            byte* limit = bEnd - 4;

            uint idx = seed + PRIME1 + bias;
            uint crc = PRIME5;

            while (p < limit)
            {
                crc += (*(uint*)p) + idx;
                idx++;
                crc += Rotl32(crc, 17) * PRIME4;
                crc *= PRIME1;
                p += 4;
            }

            while (p < bEnd)
            {
                crc += (*p) + idx;
                idx++;
                crc *= PRIME1;
                p++;
            }

            crc += len;

            crc ^= crc >> 15;
            crc *= PRIME2;
            crc ^= crc >> 13;
            crc *= PRIME3;
            crc ^= crc >> 16;

            return crc;
        }

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