// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright Contributors to the Kokkos project

#include <Kokkos_BitManipulation.hpp>
#include <cstdint>

struct X {
  constexpr bool did_not_match() { return true; }
};

#define TEST_BIT_MANIPULATION(FUNC)                     \
  constexpr X test_##FUNC(...) { return {}; }           \
  static_assert(test_##FUNC((unsigned char)0));         \
  static_assert(test_##FUNC((unsigned short)0));        \
  static_assert(test_##FUNC((unsigned int)0));          \
  static_assert(test_##FUNC((unsigned long)0));         \
  static_assert(test_##FUNC((unsigned long long)0));    \
  static_assert(test_##FUNC((bool)0).did_not_match());  \
  static_assert(test_##FUNC((int)0).did_not_match());   \
  static_assert(test_##FUNC((float)0).did_not_match()); \
  static_assert(test_##FUNC((void *)0).did_not_match())

//<editor-fold desc="[bit.rotate]">
template <class UInt>
constexpr auto test_rotl(UInt x) -> decltype(Kokkos::rotl(x, 0)) {
  using Kokkos::rotl;

  static_assert(noexcept(rotl(x, 0)));
  static_assert(std::is_same_v<decltype(rotl(x, 0)), UInt>);

  constexpr auto dig = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(rotl(UInt(0), 0) == 0);
  static_assert(rotl(UInt(0), 1) == 0);
  static_assert(rotl(UInt(0), 4) == 0);
  static_assert(rotl(UInt(0), 8) == 0);
  static_assert(rotl(max, 0) == max);
  static_assert(rotl(max, 1) == max);
  static_assert(rotl(max, 4) == max);
  static_assert(rotl(max, 8) == max);
  static_assert(rotl(UInt(1), 0) == UInt(1) << 0);
  static_assert(rotl(UInt(1), 1) == UInt(1) << 1);
  static_assert(rotl(UInt(1), 4) == UInt(1) << 4);
  static_assert(rotl(UInt(1), dig) == UInt(1));
  static_assert(rotl(UInt(7), dig) == UInt(7));
  static_assert(rotl(UInt(6), dig - 1) == UInt(3));
  static_assert(rotl(UInt(3), 6) == UInt(3) << 6);

  static_assert(rotl(UInt(max - 1), 0) == UInt(max - 1));
  static_assert(rotl(UInt(max - 1), 1) == UInt(max - 2));
  static_assert(rotl(UInt(max - 1), 2) == UInt(max - 4));
  static_assert(rotl(UInt(max - 1), 3) == UInt(max - 8));
  static_assert(rotl(UInt(max - 1), 4) == UInt(max - 16));
  static_assert(rotl(UInt(max - 1), 5) == UInt(max - 32));
  static_assert(rotl(UInt(max - 1), 6) == UInt(max - 64));
  static_assert(rotl(UInt(max - 1), 7) == UInt(max - 128));
  static_assert(rotl(UInt(1), 0) == UInt(1));
  static_assert(rotl(UInt(1), 1) == UInt(2));
  static_assert(rotl(UInt(1), 2) == UInt(4));
  static_assert(rotl(UInt(1), 3) == UInt(8));
  static_assert(rotl(UInt(1), 4) == UInt(16));
  static_assert(rotl(UInt(1), 5) == UInt(32));
  static_assert(rotl(UInt(1), 6) == UInt(64));
  static_assert(rotl(UInt(1), 7) == UInt(128));

  return true;
}

TEST_BIT_MANIPULATION(rotl);

template <class UInt>
constexpr auto test_rotr(UInt x) -> decltype(Kokkos::rotr(x, 0)) {
  using Kokkos::rotr;

  static_assert(noexcept(rotr(x, 0)));
  static_assert(std::is_same_v<decltype(rotr(x, 0)), UInt>);

  constexpr auto dig     = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max     = Kokkos::Experimental::finite_max_v<UInt>;
  constexpr auto highbit = rotr(UInt(1), 1);

  static_assert(rotr(UInt(0), 0) == 0);
  static_assert(rotr(UInt(0), 1) == 0);
  static_assert(rotr(UInt(0), 4) == 0);
  static_assert(rotr(UInt(0), 8) == 0);
  static_assert(rotr(max, 0) == max);
  static_assert(rotr(max, 1) == max);
  static_assert(rotr(max, 4) == max);
  static_assert(rotr(max, 8) == max);
  static_assert(rotr(UInt(128), 0) == UInt(128) >> 0);
  static_assert(rotr(UInt(128), 1) == UInt(128) >> 1);
  static_assert(rotr(UInt(128), 4) == UInt(128) >> 4);
  static_assert(rotr(UInt(1), dig) == UInt(1));
  static_assert(rotr(UInt(7), dig) == UInt(7));
  static_assert(rotr(UInt(6), dig - 1) == UInt(12));
  static_assert(rotr(UInt(36), dig - 2) == UInt(144));

  static_assert(rotr(UInt(max - 1), 0) == UInt(max - 1));
  static_assert(rotr(UInt(max - 1), 1) == UInt(max - highbit));
  static_assert(rotr(UInt(max - 1), 2) == UInt(max - (highbit >> 1)));
  static_assert(rotr(UInt(max - 1), 3) == UInt(max - (highbit >> 2)));
  static_assert(rotr(UInt(max - 1), 4) == UInt(max - (highbit >> 3)));
  static_assert(rotr(UInt(max - 1), 5) == UInt(max - (highbit >> 4)));
  static_assert(rotr(UInt(max - 1), 6) == UInt(max - (highbit >> 5)));
  static_assert(rotr(UInt(max - 1), 7) == UInt(max - (highbit >> 6)));
  static_assert(rotr(UInt(128), 0) == UInt(128));
  static_assert(rotr(UInt(128), 1) == UInt(64));
  static_assert(rotr(UInt(128), 2) == UInt(32));
  static_assert(rotr(UInt(128), 3) == UInt(16));
  static_assert(rotr(UInt(128), 4) == UInt(8));
  static_assert(rotr(UInt(128), 5) == UInt(4));
  static_assert(rotr(UInt(128), 6) == UInt(2));
  static_assert(rotr(UInt(128), 7) == UInt(1));

  return true;
}

TEST_BIT_MANIPULATION(rotr);
//</editor-fold>

//<editor-fold desc="[bit.count]">
template <class UInt>
constexpr auto test_countl_zero(UInt x) -> decltype(Kokkos::countl_zero(x)) {
  using Kokkos::countl_zero;

  static_assert(noexcept(countl_zero(x)));
  static_assert(std::is_same_v<decltype(countl_zero(x)), int>);

  constexpr auto dig = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(countl_zero(UInt(0)) == dig);
  static_assert(countl_zero(UInt(1)) == dig - 1);
  static_assert(countl_zero(UInt(2)) == dig - 2);
  static_assert(countl_zero(UInt(3)) == dig - 2);
  static_assert(countl_zero(UInt(4)) == dig - 3);
  static_assert(countl_zero(UInt(5)) == dig - 3);
  static_assert(countl_zero(UInt(6)) == dig - 3);
  static_assert(countl_zero(UInt(7)) == dig - 3);
  static_assert(countl_zero(UInt(8)) == dig - 4);
  static_assert(countl_zero(UInt(9)) == dig - 4);
  static_assert(countl_zero(UInt(127)) == dig - 7);
  static_assert(countl_zero(UInt(128)) == dig - 8);
  static_assert(countl_zero(max) == 0);

  return true;
}

TEST_BIT_MANIPULATION(countl_zero);

template <class UInt>
constexpr auto test_countl_one(UInt x) -> decltype(Kokkos::countl_one(x)) {
  using Kokkos::countl_one;

  static_assert(noexcept(countl_one(x)));
  static_assert(std::is_same_v<decltype(countl_one(x)), int>);

  constexpr auto dig = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(countl_one(UInt(0)) == 0);
  static_assert(countl_one(UInt(1)) == 0);
  static_assert(countl_one(UInt(2)) == 0);
  static_assert(countl_one(UInt(3)) == 0);
  static_assert(countl_one(UInt(4)) == 0);
  static_assert(countl_one(UInt(5)) == 0);
  static_assert(countl_one(UInt(6)) == 0);
  static_assert(countl_one(UInt(7)) == 0);
  static_assert(countl_one(UInt(8)) == 0);
  static_assert(countl_one(UInt(9)) == 0);
  static_assert(countl_one(UInt(100)) == 0);
  static_assert(countl_one(max) == dig);
  static_assert(countl_one(UInt(max - 1)) == dig - 1);
  static_assert(countl_one(UInt(max - 2)) == dig - 2);
  static_assert(countl_one(UInt(max - 3)) == dig - 2);
  static_assert(countl_one(UInt(max - 4)) == dig - 3);
  static_assert(countl_one(UInt(max - 5)) == dig - 3);
  static_assert(countl_one(UInt(max - 6)) == dig - 3);
  static_assert(countl_one(UInt(max - 7)) == dig - 3);
  static_assert(countl_one(UInt(max - 8)) == dig - 4);
  static_assert(countl_one(UInt(max - 9)) == dig - 4);
  static_assert(countl_one(UInt(max - 126)) == dig - 7);
  static_assert(countl_one(UInt(max - 127)) == dig - 7);
  static_assert(countl_one(UInt(max - 128)) == dig - 8);
  static_assert(countl_one(UInt(UInt(1) << (dig - 1))) == 1);
  static_assert(countl_one(UInt(UInt(3) << (dig - 2))) == 2);
  static_assert(countl_one(UInt(UInt(7) << (dig - 3))) == 3);
  static_assert(countl_one(UInt(UInt(255) << (dig - 8))) == 8);

  return true;
}

TEST_BIT_MANIPULATION(countl_one);

template <class UInt>
constexpr auto test_countr_zero(UInt x) -> decltype(Kokkos::countr_zero(x)) {
  using Kokkos::countr_zero;

  static_assert(noexcept(countr_zero(x)));
  static_assert(std::is_same_v<decltype(countr_zero(x)), int>);

  constexpr auto dig = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(countr_zero(UInt(0)) == dig);
  static_assert(countr_zero(UInt(1)) == 0);
  static_assert(countr_zero(UInt(2)) == 1);
  static_assert(countr_zero(UInt(3)) == 0);
  static_assert(countr_zero(UInt(4)) == 2);
  static_assert(countr_zero(UInt(5)) == 0);
  static_assert(countr_zero(UInt(6)) == 1);
  static_assert(countr_zero(UInt(7)) == 0);
  static_assert(countr_zero(UInt(8)) == 3);
  static_assert(countr_zero(UInt(9)) == 0);
  static_assert(countr_zero(UInt(126)) == 1);
  static_assert(countr_zero(UInt(127)) == 0);
  static_assert(countr_zero(UInt(128)) == 7);
  static_assert(countr_zero(UInt(129)) == 0);
  static_assert(countr_zero(UInt(130)) == 1);
  static_assert(countr_zero(max) == 0);

  return true;
}

TEST_BIT_MANIPULATION(countr_zero);

template <class UInt>
constexpr auto test_countr_one(UInt x) -> decltype(Kokkos::countr_one(x)) {
  using Kokkos::countr_one;

  static_assert(noexcept(countr_one(x)));
  static_assert(std::is_same_v<decltype(countr_one(x)), int>);

  constexpr auto dig = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(countr_one(UInt(0)) == 0);
  static_assert(countr_one(UInt(1)) == 1);
  static_assert(countr_one(UInt(2)) == 0);
  static_assert(countr_one(UInt(3)) == 2);
  static_assert(countr_one(UInt(4)) == 0);
  static_assert(countr_one(UInt(5)) == 1);
  static_assert(countr_one(UInt(6)) == 0);
  static_assert(countr_one(UInt(7)) == 3);
  static_assert(countr_one(UInt(8)) == 0);
  static_assert(countr_one(UInt(9)) == 1);
  static_assert(countr_one(UInt(126)) == 0);
  static_assert(countr_one(UInt(127)) == 7);
  static_assert(countr_one(UInt(128)) == 0);
  static_assert(countr_one(UInt(max - 1)) == 0);
  static_assert(countr_one(max) == dig);

  return true;
}

TEST_BIT_MANIPULATION(countr_one);

template <class UInt>
constexpr auto test_popcount(UInt x) -> decltype(Kokkos::popcount(x)) {
  using Kokkos::popcount;

  static_assert(noexcept(popcount(x)));
  static_assert(std::is_same_v<decltype(popcount(x)), int>);

  constexpr auto dig = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(popcount(UInt(0)) == 0);
  static_assert(popcount(UInt(1)) == 1);
  static_assert(popcount(UInt(2)) == 1);
  static_assert(popcount(UInt(3)) == 2);
  static_assert(popcount(UInt(4)) == 1);
  static_assert(popcount(UInt(5)) == 2);
  static_assert(popcount(UInt(6)) == 2);
  static_assert(popcount(UInt(7)) == 3);
  static_assert(popcount(UInt(8)) == 1);
  static_assert(popcount(UInt(9)) == 2);
  static_assert(popcount(UInt(127)) == 7);
  static_assert(popcount(max) == dig);
  static_assert(popcount(UInt(max - 1)) == dig - 1);

  return true;
}

TEST_BIT_MANIPULATION(popcount);
//</editor-fold>

//<editor-fold desc="[bit.pow.two]">
template <class UInt>
constexpr auto test_has_single_bit(UInt x)
    -> decltype(Kokkos::has_single_bit(x)) {
  using Kokkos::has_single_bit;

  static_assert(noexcept(has_single_bit(x)));
  static_assert(std::is_same_v<decltype(has_single_bit(x)), bool>);

  static_assert(!has_single_bit(UInt(0)));
  static_assert(has_single_bit(UInt(1)));
  static_assert(has_single_bit(UInt(2)));
  static_assert(!has_single_bit(UInt(3)));
  static_assert(has_single_bit(UInt(4)));
  static_assert(!has_single_bit(UInt(5)));
  static_assert(!has_single_bit(UInt(6)));
  static_assert(!has_single_bit(UInt(7)));
  static_assert(has_single_bit(UInt(8)));
  static_assert(!has_single_bit(UInt(9)));

  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;
  static_assert(!has_single_bit(max));
  constexpr UInt one = 1;
  static_assert(has_single_bit(UInt(one << 0)));
  static_assert(has_single_bit(UInt(one << 1)));
  static_assert(has_single_bit(UInt(one << 2)));
  static_assert(has_single_bit(UInt(one << 3)));
  static_assert(has_single_bit(UInt(one << 4)));
  static_assert(has_single_bit(UInt(one << 5)));
  static_assert(has_single_bit(UInt(one << 6)));
  static_assert(has_single_bit(UInt(one << 7)));

  return true;
}

TEST_BIT_MANIPULATION(has_single_bit);

template <class UInt>
constexpr auto test_bit_floor(UInt x) -> decltype(Kokkos::bit_floor(x)) {
  using Kokkos::bit_floor;

  static_assert(noexcept(bit_floor(x)));
  static_assert(std::is_same_v<decltype(bit_floor(x)), UInt>);

  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(bit_floor(UInt(0)) == 0);
  static_assert(bit_floor(UInt(1)) == 1);
  static_assert(bit_floor(UInt(2)) == 2);
  static_assert(bit_floor(UInt(3)) == 2);
  static_assert(bit_floor(UInt(4)) == 4);
  static_assert(bit_floor(UInt(5)) == 4);
  static_assert(bit_floor(UInt(6)) == 4);
  static_assert(bit_floor(UInt(7)) == 4);
  static_assert(bit_floor(UInt(8)) == 8);
  static_assert(bit_floor(UInt(9)) == 8);
  static_assert(bit_floor(UInt(125)) == 64);
  static_assert(bit_floor(UInt(126)) == 64);
  static_assert(bit_floor(UInt(127)) == 64);
  static_assert(bit_floor(UInt(128)) == 128);
  static_assert(bit_floor(UInt(129)) == 128);
  static_assert(bit_floor(max) == UInt(max - (max >> 1)));

  return true;
}

TEST_BIT_MANIPULATION(bit_floor);

template <class UInt>
constexpr auto test_bit_ceil(UInt x) -> decltype(Kokkos::bit_ceil(x)) {
  using Kokkos::bit_ceil;

  static_assert(noexcept(bit_ceil(x)));
  static_assert(std::is_same_v<decltype(bit_ceil(x)), UInt>);

  static_assert(bit_ceil(UInt(0)) == 1);
  static_assert(bit_ceil(UInt(1)) == 1);
  static_assert(bit_ceil(UInt(2)) == 2);
  static_assert(bit_ceil(UInt(3)) == 4);
  static_assert(bit_ceil(UInt(4)) == 4);
  static_assert(bit_ceil(UInt(5)) == 8);
  static_assert(bit_ceil(UInt(6)) == 8);
  static_assert(bit_ceil(UInt(7)) == 8);
  static_assert(bit_ceil(UInt(8)) == 8);
  static_assert(bit_ceil(UInt(9)) == 16);
  static_assert(bit_ceil(UInt(60)) == 64);
  static_assert(bit_ceil(UInt(61)) == 64);
  static_assert(bit_ceil(UInt(62)) == 64);
  static_assert(bit_ceil(UInt(63)) == 64);
  static_assert(bit_ceil(UInt(64)) == 64);
  static_assert(bit_ceil(UInt(65)) == 128);
  static_assert(bit_ceil(UInt(66)) == 128);
  static_assert(bit_ceil(UInt(67)) == 128);
  static_assert(bit_ceil(UInt(68)) == 128);
  static_assert(bit_ceil(UInt(69)) == 128);

  return true;
}

TEST_BIT_MANIPULATION(bit_ceil);

template <class UInt>
constexpr auto test_bit_width(UInt x) -> decltype(Kokkos::bit_width(x)) {
  using Kokkos::bit_width;

  static_assert(noexcept(bit_width(x)));
  static_assert(std::is_same_v<decltype(bit_width(x)), int>);

  constexpr auto dig = Kokkos::Experimental::digits_v<UInt>;
  constexpr auto max = Kokkos::Experimental::finite_max_v<UInt>;

  static_assert(bit_width(UInt(0)) == 0);
  static_assert(bit_width(UInt(1)) == 1);
  static_assert(bit_width(UInt(2)) == 2);
  static_assert(bit_width(UInt(3)) == 2);
  static_assert(bit_width(UInt(4)) == 3);
  static_assert(bit_width(UInt(5)) == 3);
  static_assert(bit_width(UInt(6)) == 3);
  static_assert(bit_width(UInt(7)) == 3);
  static_assert(bit_width(UInt(8)) == 4);
  static_assert(bit_width(UInt(9)) == 4);

  static_assert(bit_width(UInt(max - 1)) == dig);
  static_assert(bit_width(max) == dig);

  return true;
}

TEST_BIT_MANIPULATION(bit_width);
//</editor-fold>

//<editor-fold desc="[bit.byteswap]">
template <class T>
constexpr auto test_byteswap(T x) -> decltype(Kokkos::byteswap(x)) {
  using Kokkos::byteswap;

  static_assert(noexcept(byteswap(x)));
  static_assert(std::is_same_v<decltype(byteswap(x)), T>);

  return true;
}

constexpr X test_byteswap(...) { return {}; }

static_assert(test_byteswap((void *)0).did_not_match());  // NOLINT
static_assert(test_byteswap((float)0).did_not_match());
constexpr char c2[2] = {};
static_assert(test_byteswap(c2).did_not_match());
static_assert(test_byteswap((char)0));
static_assert(test_byteswap((short)0));
static_assert(test_byteswap((int)0));
static_assert(test_byteswap((long)0));
static_assert(test_byteswap((long long)0));
static_assert(test_byteswap((unsigned char)0));
static_assert(test_byteswap((unsigned short)0));
static_assert(test_byteswap((unsigned int)0));
static_assert(test_byteswap((unsigned long)0));
static_assert(test_byteswap((unsigned long long)0));

constexpr bool test_byteswap2() {
  using Kokkos::byteswap;

  static_assert(byteswap<int8_t>(INT8_C(0x12)) == INT8_C(0x12));
  static_assert(byteswap<int16_t>(INT16_C(0x1234)) == INT16_C(0x3412));
  static_assert(byteswap<int32_t>(INT32_C(0x12345678)) == INT32_C(0x78563412));

  // These static_casts are a workaround for an nvcc 11.2 compiler bug
  static_assert(
      static_cast<uint64_t>(byteswap<int64_t>(INT64_C(0x123456789abcdef0))) ==
      static_cast<uint64_t>(INT64_C(0xf0debc9a78563412)));

  static_assert(byteswap<uint8_t>(UINT8_C(0x21)) == UINT8_C(0x21));
  static_assert(byteswap<uint16_t>(UINT16_C(0x4321)) == UINT16_C(0x2143));
  static_assert(byteswap<uint32_t>(UINT32_C(0x87654321)) ==
                UINT32_C(0x21436587));
  static_assert(byteswap<uint64_t>(UINT64_C(0xfedcba9876543210)) ==
                UINT64_C(0x1032547698badcfe));
  static_assert(byteswap<const uint32_t>(UINT32_C(0xdeadbeef)) ==
                UINT32_C(0xefbeadde));

  return true;
}
static_assert(test_byteswap2());
//</editor-fold>

#undef TEST_BIT_MANIPULATION

//<editor-fold desc="[bit.bit_cast]">
template <class To, class From>
constexpr auto test_bit_cast() -> typename std::is_same<
    decltype(Kokkos::bit_cast<To>(std::declval<From const &>())),
    To>::value_type {
  static_assert(
      std::is_same_v<
          decltype(Kokkos::bit_cast<To>(std::declval<From const &>())), To>);
  return true;
}
template <class To, class From>
constexpr X test_bit_cast(...) {
  return {};
}

namespace TypesNotTheSameSize {
struct To {
  char a;
};
struct From {
  char b;
  char c;
};
static_assert(test_bit_cast<To, From>().did_not_match());
}  // namespace TypesNotTheSameSize

namespace ToNotTriviallyCopyable {
struct To {
  char a;
  To(To const &);
};
struct From {
  char b;
};
static_assert(test_bit_cast<To, From>().did_not_match());
}  // namespace ToNotTriviallyCopyable

namespace FromNotTriviallyCopyable {
struct To {
  char a;
};
struct From {
  char b;
  From(From const &);
};
static_assert(test_bit_cast<To, From>().did_not_match());
}  // namespace FromNotTriviallyCopyable

namespace ReturnTypeIllFormed {
struct From {
  char a;
  char b;
};
static_assert(test_bit_cast<int(), From>().did_not_match());
static_assert(test_bit_cast<char[2], From>().did_not_match());
}  // namespace ReturnTypeIllFormed
   //</editor-fold>
