/*
 * 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.
 */

/*
 * xxHash - Extremely Fast Hash algorithm
 * Header File
 * Copyright (C) 2012-2023 Yann Collet
 *
 * BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following disclaimer
 *      in the documentation and/or other materials provided with the
 *      distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * You can contact the author at:
 *   - xxHash homepage: https://www.xxhash.com
 *   - xxHash source repository: https://github.com/Cyan4973/xxHash
 */

// MMH_rotl32 utility is adapted from xxHash
// https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h

#pragma once
#include <cassert>
#include <cstdint>
#include <cstring>
#include <memory>

#include "paimon/memory/bytes.h"
#include "paimon/memory/memory_segment.h"

namespace paimon {

#ifdef __has_builtin
#define MMH_HAS_BUILTIN(x) __has_builtin(x)
#else
#define MMH_HAS_BUILTIN(x) 0
#endif
/*!
 * @internal
 * @def MMH_rotl32(x,r)
 * @brief 32-bit rotate left.
 *
 * @param x The 32-bit integer to be rotated.
 * @param r The number of bits to rotate.
 * @pre
 *   @p r > 0 && @p r < 32
 * @note
 *   @p x and @p r may be evaluated multiple times.
 * @return The rotated result.
 */
#if !defined(NO_CLANG_BUILTIN) && MMH_HAS_BUILTIN(__builtin_rotateleft32) && \
    MMH_HAS_BUILTIN(__builtin_rotateleft64)
#define MMH_rotl32 __builtin_rotateleft32
/* Note: although _rotl exists for minGW (GCC under windows), performance seems poor */
#elif defined(_MSC_VER)
#define MMH_rotl32(x, r) _rotl(x, r)
#else
#define MMH_rotl32(x, r) (((x) << (r)) | ((x) >> (32 - (r))))
#endif

class MurmurHashUtils {
 public:
    static constexpr int32_t DEFAULT_SEED = 42;

    MurmurHashUtils() = delete;

    /// Hash unsafe bytes, length must be aligned to 4 bytes.
    ///
    /// @param base base unsafe object
    /// @param offset offset for unsafe object
    /// @param length_in_bytes length in bytes
    /// @return hash code
    static int32_t HashUnsafeBytesByWords(const void* base, int64_t offset,
                                          int32_t length_in_bytes) {
        return HashUnsafeBytesByWords(base, offset, length_in_bytes, DEFAULT_SEED);
    }

    /// Hash bytes.
    static int32_t HashBytesPositive(const std::shared_ptr<Bytes>& bytes) {
        return HashBytes(bytes) & 0x7fffffff;
    }

    /// Hash bytes.
    static int32_t HashBytes(const std::shared_ptr<Bytes>& bytes) {
        return HashUnsafeBytes(reinterpret_cast<const void*>(bytes->data()), 0, bytes->size(),
                               DEFAULT_SEED);
    }

    static int32_t HashUnsafeBytes(const void* base, int64_t offset, int32_t length_in_bytes,
                                   int32_t seed) {
        assert(length_in_bytes >= 0);
        int32_t length_aligned = length_in_bytes - length_in_bytes % 4;
        int32_t h1 = HashUnsafeBytesByInt(base, offset, length_aligned, seed);
        for (int32_t i = length_aligned; i < length_in_bytes; i++) {
            int32_t half_word = GetByte(base, offset + i);
            int32_t k1 = MixK1(half_word);
            h1 = MixH1(h1, k1);
        }
        return Fmix(h1, length_in_bytes);
    }

    /// Hash unsafe bytes.
    ///
    /// @param base base unsafe object
    /// @param offset offset for unsafe object
    /// @param length_in_bytes length in bytes
    /// @return hash code
    static int32_t HashUnsafeBytes(const void* base, int64_t offset, int32_t length_in_bytes) {
        return HashUnsafeBytes(base, offset, length_in_bytes, DEFAULT_SEED);
    }

    /// Hash bytes in MemorySegment, length must be aligned to 4 bytes.
    ///
    /// @param segment segment.
    /// @param offset offset for MemorySegment
    /// @param length_in_bytes length in MemorySegment
    /// @return hash code
    static int32_t HashBytesByWords(const MemorySegment& segment, int32_t offset,
                                    int32_t length_in_bytes) {
        return HashBytesByWords(segment, offset, length_in_bytes, DEFAULT_SEED);
    }

    /// Hash bytes in MemorySegment.
    ///
    /// @param segment segment.
    /// @param offset offset for MemorySegment
    /// @param length_in_bytes length in MemorySegment
    /// @return hash code
    static int32_t HashBytes(const MemorySegment& segment, int32_t offset,
                             int32_t length_in_bytes) {
        return HashBytes(segment, offset, length_in_bytes, DEFAULT_SEED);
    }

 private:
    static int32_t HashUnsafeBytesByWords(const void* base, int64_t offset, int32_t length_in_bytes,
                                          int32_t seed) {
        int32_t h1 = HashUnsafeBytesByInt(base, offset, length_in_bytes, seed);
        return Fmix(h1, length_in_bytes);
    }

    static int32_t HashBytesByWords(const MemorySegment& segment, int32_t offset,
                                    int32_t length_in_bytes, int32_t seed) {
        int32_t h1 = HashBytesByInt(segment, offset, length_in_bytes, seed);
        return Fmix(h1, length_in_bytes);
    }

    static int32_t HashBytes(const MemorySegment& segment, int32_t offset, int32_t length_in_bytes,
                             int32_t seed) {
        int32_t length_aligned = length_in_bytes - length_in_bytes % 4;
        int32_t h1 = HashBytesByInt(segment, offset, length_aligned, seed);
        for (int32_t i = length_aligned; i < length_in_bytes; i++) {
            auto byte = static_cast<uint8_t>(segment.Get(offset + i));
            int32_t signed_byte = byte < 128 ? byte : static_cast<int32_t>(byte) - 256;
            int32_t k1 = MixK1(signed_byte);
            h1 = MixH1(h1, k1);
        }
        return Fmix(h1, length_in_bytes);
    }

    static int32_t HashUnsafeBytesByInt(const void* base, int64_t offset, int32_t length_in_bytes,
                                        int32_t seed) {
        assert(length_in_bytes % 4 == 0);
        int32_t h1 = seed;
        for (int32_t i = 0; i < length_in_bytes; i += 4) {
            int32_t half_word = GetInt(base, offset + i);
            int32_t k1 = MixK1(half_word);
            h1 = MixH1(h1, k1);
        }
        return h1;
    }

    static int32_t HashBytesByInt(const MemorySegment& segment, int32_t offset,
                                  int32_t length_in_bytes, int32_t seed) {
        assert(length_in_bytes % 4 == 0);
        int32_t h1 = seed;
        for (int32_t i = 0; i < length_in_bytes; i += 4) {
            auto half_word = segment.GetValue<int32_t>(offset + i);
            int32_t k1 = MixK1(half_word);
            h1 = MixH1(h1, k1);
        }
        return h1;
    }

    static int32_t MixK1(uint32_t k1) {
        k1 *= C1;
        k1 = MMH_rotl32(k1, 15);
        k1 *= C2;
        return k1;
    }

    static int32_t MixH1(uint32_t h1, uint32_t k1) {
        h1 ^= k1;
        h1 = MMH_rotl32(h1, 13);
        h1 = h1 * 5 + 0xe6546b64;
        return h1;
    }

    // Finalization mix - force all bits of a hash block to avalanche
    static int32_t Fmix(uint32_t h1, uint32_t length) {
        h1 ^= length;
        return Fmix(h1);
    }

    static int32_t GetInt(const void* base, int64_t offset) {
        int32_t value;
        std::memcpy(&value, static_cast<const char*>(base) + offset, sizeof(int32_t));
        return value;
    }

    static int32_t GetByte(const void* base, int64_t offset) {
        uint8_t value;
        std::memcpy(&value, static_cast<const char*>(base) + offset, sizeof(uint8_t));
        return value < 128 ? value : static_cast<int32_t>(value) - 256;
    }

 public:
    static int32_t Fmix(uint32_t h) {
        h ^= h >> 16;
        h *= 0x85ebca6b;
        h ^= h >> 13;
        h *= 0xc2b2ae35;
        h ^= h >> 16;
        return h;
    }

 private:
    static constexpr int32_t C1 = 0xcc9e2d51;
    static constexpr int32_t C2 = 0x1b873593;
};

}  // namespace paimon
