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

#include <gtest/gtest.h>
#include <Kokkos_Macros.hpp>
#ifdef KOKKOS_ENABLE_EXPERIMENTAL_CXX20_MODULES
import kokkos.core;
#else
#include <Kokkos_Core.hpp>
#endif
#include <numeric>

namespace {

TEST(TEST_CATEGORY, array_capacity) {
  using A = Kokkos::Array<int, 2>;
  A a{{3, 5}};

  static_assert(noexcept(a.empty()));
  ASSERT_FALSE(a.empty());

  static_assert(noexcept(a.size()));
  ASSERT_EQ(a.size(), 2u);

  static_assert(noexcept(a.max_size()));
  ASSERT_EQ(a.max_size(), 2u);
}

enum Enum { EZero, EOne };
enum EnumShort : short { ESZero, ESOne };

TEST(TEST_CATEGORY, array_element_access) {
  using A = Kokkos::Array<int, 2>;
  A a{{3, 5}};
  A const& ca = a;

  size_t index = 1;
  ASSERT_EQ(a[index], 5);

  auto sc = static_cast<signed char>(index);
  ASSERT_EQ(a[sc], a[index]);
  ASSERT_EQ(ca[sc], a[index]);

  auto uc = static_cast<unsigned char>(index);
  ASSERT_EQ(a[uc], a[index]);
  ASSERT_EQ(ca[uc], a[index]);

  auto s = static_cast<short>(index);
  ASSERT_EQ(a[s], a[index]);
  ASSERT_EQ(ca[s], a[index]);

  auto us = static_cast<unsigned short>(index);
  ASSERT_EQ(a[us], a[index]);
  ASSERT_EQ(ca[us], a[index]);

  auto i = static_cast<int>(index);
  ASSERT_EQ(a[i], a[index]);
  ASSERT_EQ(ca[i], a[index]);

  auto ui = static_cast<unsigned int>(index);
  ASSERT_EQ(a[ui], a[index]);
  ASSERT_EQ(ca[ui], a[index]);

  auto l = static_cast<long>(index);
  ASSERT_EQ(a[l], a[index]);
  ASSERT_EQ(ca[l], a[index]);

  auto ul = static_cast<unsigned long>(index);
  ASSERT_EQ(a[ul], a[index]);
  ASSERT_EQ(ca[ul], a[index]);

  auto ll = static_cast<long long>(index);
  ASSERT_EQ(a[ll], a[index]);
  ASSERT_EQ(ca[ll], a[index]);

  auto ull = static_cast<unsigned long long>(index);
  ASSERT_EQ(a[ull], a[index]);
  ASSERT_EQ(ca[ull], a[index]);

  auto e = static_cast<Enum>(index);
  ASSERT_EQ(a[e], a[index]);
  ASSERT_EQ(ca[e], a[index]);

  auto es = static_cast<EnumShort>(index);
  ASSERT_EQ(a[es], a[index]);
  ASSERT_EQ(ca[es], a[index]);

  static_assert(noexcept(a.data()));
  ASSERT_EQ(a.data()[index], a[index]);

  static_assert(noexcept(ca.data()));
  ASSERT_EQ(ca.data()[index], a[index]);
}

TEST(TEST_CATEGORY, array_operator_equal) {
  using A = Kokkos::Array<int, 2>;
  constexpr A a{{3, 5}};
  constexpr A b{{3, 5}};
  constexpr A c{{5, 3}};

  static_assert(a == b);
  static_assert(!(a == c));
  static_assert(a != c);

  ASSERT_TRUE(a == b);
  ASSERT_FALSE(a == c);
  ASSERT_TRUE(a != c);

  using E = Kokkos::Array<int, 0>;
  constexpr E e;
  constexpr E f;

  static_assert(e == f);
  static_assert(!(e != f));

  ASSERT_TRUE(e == f);
  ASSERT_FALSE(e != f);
}

TEST(TEST_CATEGORY, array_zero_capacity) {
  using A = Kokkos::Array<int, 0>;
  A e;

  static_assert(noexcept(e.empty()));
  ASSERT_TRUE(e.empty());

  static_assert(noexcept(e.size()));
  ASSERT_EQ(e.size(), 0u);

  static_assert(noexcept(e.max_size()));
  ASSERT_EQ(e.max_size(), 0u);
}

TEST(TEST_CATEGORY, array_zero_data_nullptr) {
  using A = Kokkos::Array<int, 0>;

  A e;
  static_assert(noexcept(e.data()));
  ASSERT_EQ(e.data(), nullptr);

  const A& ce = e;
  static_assert(noexcept(ce.data()));
  ASSERT_EQ(ce.data(), nullptr);
}

TEST(TEST_CATEGORY, array_stl_compatibility) {
  using A = Kokkos::Array<int, 3>;
  A a{1, 2, 3};

  int sum  = std::reduce(a.begin(), a.end(), int{0}, std::plus<int>{});
  int csum = std::reduce(a.cbegin(), a.cend(), int{0}, std::plus<int>{});
  int expected_sum = 1 + 2 + 3;
  EXPECT_EQ(sum, expected_sum);
  EXPECT_EQ(csum, expected_sum);
}

#ifdef KOKKOS_ENABLE_DEPRECATED_CODE_4
#ifdef KOKKOS_ENABLE_DEPRECATION_WARNINGS
KOKKOS_IMPL_DISABLE_DEPRECATED_WARNINGS_PUSH()
#endif
TEST(TEST_CATEGORY, array_contiguous_capacity) {
  using A =
      Kokkos::Array<int, KOKKOS_INVALID_INDEX, Kokkos::Array<>::contiguous>;

  A e(nullptr, 0);

  ASSERT_TRUE(e.empty());
  ASSERT_EQ(e.size(), 0u);
  ASSERT_EQ(e.max_size(), 0u);

  int aa[] = {3, 5};
  A a(aa, std::size(aa));

  ASSERT_EQ(a.empty(), 0 == std::size(aa));
  ASSERT_EQ(a.size(), std::size(aa));
  ASSERT_EQ(a.max_size(), std::size(aa));
}

TEST(TEST_CATEGORY, array_contiguous_element_access) {
  int aa[] = {3, 5};
  using A =
      Kokkos::Array<int, KOKKOS_INVALID_INDEX, Kokkos::Array<>::contiguous>;
  A a(aa, std::size(aa));
  A const& ca = a;

  size_t index = 1;
  ASSERT_EQ(std::addressof(a[index]), std::addressof(aa[index]));

  auto sc = static_cast<signed char>(index);
  ASSERT_EQ(a[sc], aa[index]);
  ASSERT_EQ(ca[sc], aa[index]);

  auto uc = static_cast<unsigned char>(index);
  ASSERT_EQ(a[uc], aa[index]);
  ASSERT_EQ(ca[uc], aa[index]);

  auto s = static_cast<short>(index);
  ASSERT_EQ(a[s], aa[index]);
  ASSERT_EQ(ca[s], aa[index]);

  auto us = static_cast<unsigned short>(index);
  ASSERT_EQ(a[us], aa[index]);
  ASSERT_EQ(ca[us], aa[index]);

  auto i = static_cast<int>(index);
  ASSERT_EQ(a[i], aa[index]);
  ASSERT_EQ(ca[i], aa[index]);

  auto ui = static_cast<unsigned int>(index);
  ASSERT_EQ(a[ui], aa[index]);
  ASSERT_EQ(ca[ui], aa[index]);

  auto l = static_cast<long>(index);
  ASSERT_EQ(a[l], aa[index]);
  ASSERT_EQ(ca[l], aa[index]);

  auto ul = static_cast<unsigned long>(index);
  ASSERT_EQ(a[ul], aa[index]);
  ASSERT_EQ(ca[ul], aa[index]);

  auto ll = static_cast<long long>(index);
  ASSERT_EQ(a[ll], aa[index]);
  ASSERT_EQ(ca[ll], aa[index]);

  auto ull = static_cast<unsigned long long>(index);
  ASSERT_EQ(a[ull], aa[index]);
  ASSERT_EQ(ca[ull], aa[index]);

  auto e = static_cast<Enum>(index);
  ASSERT_EQ(a[e], aa[index]);
  ASSERT_EQ(ca[e], aa[index]);

  auto es = static_cast<EnumShort>(index);
  ASSERT_EQ(a[es], aa[index]);
  ASSERT_EQ(ca[es], aa[index]);

  ASSERT_EQ(a.data(), aa);
  ASSERT_EQ(ca.data(), aa);
}

TEST(TEST_CATEGORY, array_contiguous_assignment) {
  using A =
      Kokkos::Array<int, KOKKOS_INVALID_INDEX, Kokkos::Array<>::contiguous>;

  int aa[] = {3, 5};
  A a(aa, std::size(aa));

  // operator=(Array<T, N, P> const&) semantics when lhs size a > rhs size b
  using B = Kokkos::Array<int, 1>;
  static_assert(std::size(aa) > B::size());
  B b{{7}};

  ASSERT_GT(std::size(a), std::size(b));
  a = b;
  ASSERT_GT(std::size(a), std::size(b));

  ASSERT_EQ(a.size(), std::size(aa));
  ASSERT_EQ(a.max_size(), std::size(aa));
  ASSERT_EQ(a[0], 7);
  ASSERT_EQ(a[1], 5);

  // operator=(Array<T, N, P> const&) semantics when lhs size a < rhs size d
  using D = Kokkos::Array<int, 4>;
  static_assert(std::size(aa) < D::size());
  D d{{11, 13, 17, 19}};

  ASSERT_LT(std::size(a), std::size(d));
  a = d;
  ASSERT_LT(std::size(a), std::size(d));

  ASSERT_EQ(a.size(), std::size(aa));
  ASSERT_EQ(a.max_size(), std::size(aa));
  ASSERT_EQ(a[0], 11);
  ASSERT_EQ(a[1], 13);

  // Copy assignment operator semantics when lhs size a > rhs size e
  int ee[] = {23};
  A e(ee, std::size(ee));

  ASSERT_GT(a.size(), e.size());
  a = e;
  ASSERT_GT(a.size(), e.size());

  ASSERT_EQ(a.size(), std::size(aa));
  ASSERT_EQ(a.max_size(), std::size(aa));
  ASSERT_EQ(a[0], 23);
  ASSERT_EQ(a[1], 13);

  // Copy assignment operator semantics when lhs size e < rhs size a
  ASSERT_LT(e.size(), a.size());
  e[0] = 29;  // To check that e[0] is overwritten by e = a
  e    = a;
  ASSERT_LT(e.size(), a.size());

  ASSERT_EQ(e.size(), std::size(ee));
  ASSERT_EQ(e.max_size(), std::size(ee));
  ASSERT_EQ(e[0], 23);
}

TEST(TEST_CATEGORY, array_strided_capacity) {
  using A = Kokkos::Array<int, KOKKOS_INVALID_INDEX, Kokkos::Array<>::strided>;

  A e(nullptr, 0, 0);

  ASSERT_TRUE(e.empty());
  ASSERT_EQ(e.size(), 0u);
  ASSERT_EQ(e.max_size(), 0u);

  int aa[]                 = {5, 7, 11, 13, 17, 19};
  constexpr size_t aStride = 2;
  A a(aa, std::size(aa) / aStride, aStride);

  ASSERT_EQ(a.empty(), 0 == std::size(aa) / aStride);
  ASSERT_EQ(a.size(), std::size(aa) / aStride);
  ASSERT_EQ(a.max_size(), std::size(aa) / aStride);
}

TEST(TEST_CATEGORY, array_strided_element_access) {
  using A = Kokkos::Array<int, KOKKOS_INVALID_INDEX, Kokkos::Array<>::strided>;

  int aa[]                 = {5, 7, 11, 13, 17, 19};
  constexpr size_t aStride = 2;

  A a(aa, std::size(aa) / aStride, aStride);
  A const& ca = a;

  size_t index = 1;
  ASSERT_EQ(std::addressof(a[index]), std::addressof(aa[index * aStride]));

  auto sc = static_cast<signed char>(index);
  ASSERT_EQ(a[sc], aa[index * aStride]);
  ASSERT_EQ(ca[sc], aa[index * aStride]);

  auto uc = static_cast<unsigned char>(index);
  ASSERT_EQ(a[uc], aa[index * aStride]);
  ASSERT_EQ(ca[uc], aa[index * aStride]);

  auto s = static_cast<short>(index);
  ASSERT_EQ(a[s], aa[index * aStride]);
  ASSERT_EQ(ca[s], aa[index * aStride]);

  auto us = static_cast<unsigned short>(index);
  ASSERT_EQ(a[us], aa[index * aStride]);
  ASSERT_EQ(ca[us], aa[index * aStride]);

  auto i = static_cast<int>(index);
  ASSERT_EQ(a[i], aa[index * aStride]);
  ASSERT_EQ(ca[i], aa[index * aStride]);

  auto ui = static_cast<unsigned int>(index);
  ASSERT_EQ(a[ui], aa[index * aStride]);
  ASSERT_EQ(ca[ui], aa[index * aStride]);

  auto l = static_cast<long>(index);
  ASSERT_EQ(a[l], aa[index * aStride]);
  ASSERT_EQ(ca[l], aa[index * aStride]);

  auto ul = static_cast<unsigned long>(index);
  ASSERT_EQ(a[ul], aa[index * aStride]);
  ASSERT_EQ(ca[ul], aa[index * aStride]);

  auto ll = static_cast<long long>(index);
  ASSERT_EQ(a[ll], aa[index * aStride]);
  ASSERT_EQ(ca[ll], aa[index * aStride]);

  auto ull = static_cast<unsigned long long>(index);
  ASSERT_EQ(a[ull], aa[index * aStride]);
  ASSERT_EQ(ca[ull], aa[index * aStride]);

  auto e = static_cast<Enum>(index);
  ASSERT_EQ(a[e], aa[index * aStride]);
  ASSERT_EQ(ca[e], aa[index * aStride]);

  auto es = static_cast<EnumShort>(index);
  ASSERT_EQ(a[es], aa[index * aStride]);
  ASSERT_EQ(ca[es], aa[index * aStride]);

  ASSERT_EQ(a.data(), aa);
  ASSERT_EQ(ca.data(), aa);
}

TEST(TEST_CATEGORY, array_strided_assignment) {
  using A  = Kokkos::Array<int, KOKKOS_INVALID_INDEX, Kokkos::Array<>::strided>;
  int aa[] = {5, 7, 11, 13, 17, 19};
  constexpr size_t aStride = 2;
  A a(aa, std::size(aa) / aStride, aStride);

  // operator=(Array<T, N, P> const&) semantics when lhs size a > rhs size b
  using B = Kokkos::Array<int, 1>;
  static_assert(std::size(aa) / aStride > B::size());
  B b{{23}};

  ASSERT_GT(std::size(a), std::size(b));
  a = b;
  ASSERT_GT(std::size(a), std::size(b));

  ASSERT_EQ(a.size(), std::size(aa) / aStride);
  ASSERT_EQ(a.max_size(), std::size(aa) / aStride);
  ASSERT_EQ(a[0], b[0]);
  ASSERT_EQ(a[1], aa[1 * aStride]);

  // operator=(Array<T, N, P> const&) semantics when lhs size a < rhs size d
  using D = Kokkos::Array<int, 7>;
  static_assert(std::size(aa) / aStride < D::size());
  D d{{29, 31, 37, 41, 43, 47, 53}};

  ASSERT_LT(std::size(a), std::size(d));
  a = d;
  ASSERT_LT(std::size(a), std::size(d));

  ASSERT_EQ(a.size(), std::size(aa) / aStride);
  ASSERT_EQ(a.max_size(), std::size(aa) / aStride);
  ASSERT_EQ(a[0], d[0]);
  ASSERT_EQ(a[1], d[1]);

  // Copy assignment operator semantics when lhs size a > rhs size e
  int ee[]                 = {59, 61, 67, 71, 73, 79};
  constexpr size_t eStride = 3;
  A e(ee, std::size(ee) / eStride, eStride);

  ASSERT_GT(a.size(), e.size());
  a = e;
  ASSERT_GT(a.size(), e.size());

  ASSERT_EQ(a.size(), std::size(aa) / aStride);
  ASSERT_EQ(a.max_size(), std::size(aa) / aStride);
  ASSERT_EQ(a[0], ee[0 * eStride]);
  ASSERT_EQ(a[1], ee[1 * eStride]);

  // Copy assignment operator semantics when lhs size e < rhs size a
  e[0] = 83;  // To check that e[0] is overwritten by e = a
  ASSERT_LT(e.size(), a.size());
  e = a;
  ASSERT_LT(e.size(), a.size());
  ASSERT_EQ(e.size(), std::size(ee) / eStride);
  ASSERT_EQ(e.max_size(), std::size(ee) / eStride);
  ASSERT_EQ(e[0], ee[0]);
}
#ifdef KOKKOS_ENABLE_DEPRECATION_WARNINGS
KOKKOS_IMPL_DISABLE_DEPRECATED_WARNINGS_POP()
#endif

#endif

}  // namespace
