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

//  Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).

// Adapted from RocksDB
// https://github.com/facebook/rocksdb/blob/main/util/math.h

#pragma once

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <type_traits>

#include "fmt/format.h"
#include "paimon/common/utils/options_utils.h"
#include "paimon/status.h"

namespace paimon {

template <typename To, typename From>
constexpr bool InRange(From value) {
    static_assert(std::is_integral_v<To> && std::is_integral_v<From>,
                  "InRange only supports integral types");

    if constexpr (std::is_signed_v<From> && std::is_unsigned_v<To>) {
        if (value < 0) {
            return false;
        }
        using CompareType = std::common_type_t<std::make_unsigned_t<From>, To>;
        return static_cast<CompareType>(value) <=
               static_cast<CompareType>(std::numeric_limits<To>::max());
    } else if constexpr (std::is_unsigned_v<From> && std::is_signed_v<To>) {
        using CompareType = std::common_type_t<From, std::make_unsigned_t<To>>;
        return static_cast<CompareType>(value) <=
               static_cast<CompareType>(std::numeric_limits<To>::max());
    } else {
        using CompareType = std::common_type_t<To, From>;
        const auto numeric_value = static_cast<CompareType>(value);
        const auto min_value = static_cast<CompareType>(std::numeric_limits<To>::min());
        const auto max_value = static_cast<CompareType>(std::numeric_limits<To>::max());
        return numeric_value >= min_value && numeric_value <= max_value;
    }
}

template <typename To, typename From>
Status ValidateValueInRange(From value, const char* value_name) {
    if (PAIMON_UNLIKELY(!InRange<To>(value))) {
        return Status::Invalid(fmt::format("{} {} is out of bound of type {}", value_name, value,
                                           OptionsUtils::GetTypeName<To>()));
    }
    return Status::OK();
}

template <typename T>
Status ValidateValueNonNegative(T value, const char* value_name) {
    if (PAIMON_UNLIKELY(value < 0)) {
        return Status::Invalid(fmt::format("{} {} is less than 0", value_name, value));
    }
    return Status::OK();
}

// Swaps between big and little endian. Can be used in combination with the
// little-endian encoding/decoding functions in coding_lean.h and coding.h to
// encode/decode big endian.
template <typename T>
inline T EndianSwapValue(T v) {
    static_assert(std::is_standard_layout_v<T> && std::is_trivially_copyable_v<T>,
                  "Type must be standard-layout and trivially copyable (e.g., integral or "
                  "floating-point types).");
    if constexpr (sizeof(T) == 1) {
        return v;
    } else if constexpr (std::is_same_v<T, float> ||  // NOLINT(readability/braces)
                         std::is_same_v<T, double> || std::is_integral_v<T>) {
        using UintType = std::conditional_t<
            sizeof(T) == 2, uint16_t,
            std::conditional_t<sizeof(T) == 4, uint32_t,
                               std::conditional_t<sizeof(T) == 8, uint64_t, void>>>;

        static_assert(!std::is_same_v<UintType, void>,
                      "Unsupported size: only 2-byte, 4-byte, and 8-byte types are supported.");

        UintType int_repr;
        std::memcpy(&int_repr, &v, sizeof(T));

#ifdef _MSC_VER
        if constexpr (sizeof(T) == 2) {
            int_repr = _byteswap_ushort(static_cast<uint16_t>(int_repr));
        } else if constexpr (sizeof(T) == 4) {
            int_repr = _byteswap_ulong(static_cast<uint32_t>(int_repr));
        } else if constexpr (sizeof(T) == 8) {
            int_repr = _byteswap_uint64(static_cast<uint64_t>(int_repr));
        }
#else
        if constexpr (sizeof(T) == 2) {
            int_repr = __builtin_bswap16(static_cast<uint16_t>(int_repr));
        } else if constexpr (sizeof(T) == 4) {
            int_repr = __builtin_bswap32(static_cast<uint32_t>(int_repr));
        } else if constexpr (sizeof(T) == 8) {
            int_repr = __builtin_bswap64(static_cast<uint64_t>(int_repr));
        }
#endif
        T result;
        std::memcpy(&result, &int_repr, sizeof(T));
        return result;
    } else {
        // Fallback for unsupported sizes (e.g., 16-bit integers on some platforms)
        T ret_val{};
        for (std::size_t i = 0; i < sizeof(T); ++i) {
            reinterpret_cast<unsigned char*>(&ret_val)[sizeof(T) - 1 - i] =
                reinterpret_cast<unsigned char*>(&v)[i];
        }
        return ret_val;
    }
}

}  // namespace paimon
