// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

using System.Buffers.Binary;

namespace Apache.Fory;

internal static class MurmurHash3
{
    public static (ulong H1, ulong H2) X64_128(ReadOnlySpan<byte> bytes, ulong seed = 47)
    {
        const ulong c1 = 0x87c37b91114253d5;
        const ulong c2 = 0x4cf5ad432745937f;

        ulong h1 = seed;
        ulong h2 = seed;

        int length = bytes.Length;
        int nblocks = length / 16;
        for (int i = 0; i < nblocks; i++)
        {
            int offset = i * 16;
            ulong k1 = BinaryPrimitives.ReadUInt64LittleEndian(bytes.Slice(offset, 8));
            ulong k2 = BinaryPrimitives.ReadUInt64LittleEndian(bytes.Slice(offset + 8, 8));

            k1 *= c1;
            k1 = RotateLeft(k1, 31);
            k1 *= c2;
            h1 ^= k1;

            h1 = RotateLeft(h1, 27);
            h1 += h2;
            h1 = h1 * 5 + 0x52dce729;

            k2 *= c2;
            k2 = RotateLeft(k2, 33);
            k2 *= c1;
            h2 ^= k2;

            h2 = RotateLeft(h2, 31);
            h2 += h1;
            h2 = h2 * 5 + 0x38495ab5;
        }

        ulong tk1 = 0;
        ulong tk2 = 0;
        int tailStart = nblocks * 16;
        ReadOnlySpan<byte> tail = bytes.Slice(tailStart);
        switch (length & 15)
        {
            case 15:
                tk2 ^= (ulong)tail[14] << 48;
                goto case 14;
            case 14:
                tk2 ^= (ulong)tail[13] << 40;
                goto case 13;
            case 13:
                tk2 ^= (ulong)tail[12] << 32;
                goto case 12;
            case 12:
                tk2 ^= (ulong)tail[11] << 24;
                goto case 11;
            case 11:
                tk2 ^= (ulong)tail[10] << 16;
                goto case 10;
            case 10:
                tk2 ^= (ulong)tail[9] << 8;
                goto case 9;
            case 9:
                tk2 ^= tail[8];
                tk2 *= c2;
                tk2 = RotateLeft(tk2, 33);
                tk2 *= c1;
                h2 ^= tk2;
                goto case 8;
            case 8:
                tk1 ^= (ulong)tail[7] << 56;
                goto case 7;
            case 7:
                tk1 ^= (ulong)tail[6] << 48;
                goto case 6;
            case 6:
                tk1 ^= (ulong)tail[5] << 40;
                goto case 5;
            case 5:
                tk1 ^= (ulong)tail[4] << 32;
                goto case 4;
            case 4:
                tk1 ^= (ulong)tail[3] << 24;
                goto case 3;
            case 3:
                tk1 ^= (ulong)tail[2] << 16;
                goto case 2;
            case 2:
                tk1 ^= (ulong)tail[1] << 8;
                goto case 1;
            case 1:
                tk1 ^= tail[0];
                tk1 *= c1;
                tk1 = RotateLeft(tk1, 31);
                tk1 *= c2;
                h1 ^= tk1;
                break;
        }

        h1 ^= (ulong)length;
        h2 ^= (ulong)length;
        h1 += h2;
        h2 += h1;
        h1 = Fmix64(h1);
        h2 = Fmix64(h2);
        h1 += h2;
        h2 += h1;
        return (h1, h2);
    }

    private static ulong RotateLeft(ulong x, int r)
    {
        return (x << r) | (x >> (64 - r));
    }

    private static ulong Fmix64(ulong x)
    {
        x ^= x >> 33;
        x *= 0xff51afd7ed558ccd;
        x ^= x >> 33;
        x *= 0xc4ceb9fe1a85ec53;
        x ^= x >> 33;
        return x;
    }
}
