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

/// \file Kokkos_Pair.hpp
/// \brief Declaration and definition of Kokkos::pair.
///
/// This header file declares and defines Kokkos::pair and its related
/// nonmember functions.

#ifndef KOKKOS_PAIR_HPP
#define KOKKOS_PAIR_HPP
#ifndef KOKKOS_IMPL_PUBLIC_INCLUDE
#define KOKKOS_IMPL_PUBLIC_INCLUDE
#define KOKKOS_IMPL_PUBLIC_INCLUDE_NOTDEFINED_PAIR
#endif

#include <Kokkos_Macros.hpp>
#include <Kokkos_Swap.hpp>
#include <utility>

namespace Kokkos {
/// \struct pair
/// \brief Replacement for std::pair that works on CUDA devices.
///
/// The instance methods of std::pair, including its constructors, are
/// not marked as <tt>__device__</tt> functions.  Thus, they cannot be
/// called on a CUDA device, such as an NVIDIA GPU.  This struct
/// implements the same interface as std::pair, but can be used on a
/// CUDA device as well as on the host.
template <class T1, class T2>
struct pair {
  //! The first template parameter of this class.
  using first_type = T1;
  //! The second template parameter of this class.
  using second_type = T2;

  //! The first element of the pair.
  first_type first;
  //! The second element of the pair.
  second_type second;

  /// \brief Default constructor.
  ///
  /// This calls the default constructors of T1 and T2.  It won't
  /// compile if those default constructors are not defined and
  /// public.
  KOKKOS_DEFAULTED_FUNCTION constexpr pair() = default;

  /// \brief Constructor that takes both elements of the pair.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
#if defined(KOKKOS_COMPILER_NVHPC) && KOKKOS_COMPILER_NVHPC < 230700
  KOKKOS_FORCEINLINE_FUNCTION
#else
  KOKKOS_FORCEINLINE_FUNCTION constexpr
#endif
  pair(first_type const& f, second_type const& s) : first(f), second(s) {}

  /// \brief Copy constructor.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
  template <class U, class V>
#if defined(KOKKOS_COMPILER_NVHPC) && KOKKOS_COMPILER_NVHPC < 230700
  KOKKOS_FORCEINLINE_FUNCTION
#else
  KOKKOS_FORCEINLINE_FUNCTION constexpr
#endif
  pair(const pair<U, V>& p)
      : first(p.first), second(p.second) {
  }

  /// \brief Assignment operator.
  ///
  /// This calls the assignment operators of T1 and T2.  It won't
  /// compile if the assignment operators are not defined and public.
  template <class U, class V>
  KOKKOS_FORCEINLINE_FUNCTION pair<T1, T2>& operator=(const pair<U, V>& p) {
    first  = p.first;
    second = p.second;
    return *this;
  }

  // from std::pair<U,V>
  template <class U, class V>
  constexpr pair(const std::pair<U, V>& p) : first(p.first), second(p.second) {}

  /// \brief Return the std::pair version of this object.
  ///
  /// This is <i>not</i> a device function; you may not call it on a
  /// CUDA device.  It is meant to be called on the host, if the user
  /// wants an std::pair instead of a Kokkos::pair.
  ///
  /// \note This is not a conversion operator, since defining a
  ///   conversion operator made the relational operators have
  ///   ambiguous definitions.
  std::pair<T1, T2> to_std_pair() const {
    return std::make_pair(first, second);
  }
};

template <class T1, class T2>
struct pair<T1&, T2&> {
  //! The first template parameter of this class.
  using first_type = T1&;
  //! The second template parameter of this class.
  using second_type = T2&;

  //! The first element of the pair.
  first_type first;
  //! The second element of the pair.
  second_type second;

  /// \brief Constructor that takes both elements of the pair.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
  KOKKOS_FORCEINLINE_FUNCTION constexpr pair(first_type f, second_type s)
      : first(f), second(s) {}

  /// \brief Copy constructor.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
  template <class U, class V>
  KOKKOS_FORCEINLINE_FUNCTION constexpr pair(const pair<U, V>& p)
      : first(p.first), second(p.second) {}

  // from std::pair<U,V>
  template <class U, class V>
  constexpr pair(const std::pair<U, V>& p) : first(p.first), second(p.second) {}

  /// \brief Assignment operator.
  ///
  /// This calls the assignment operators of T1 and T2.  It won't
  /// compile if the assignment operators are not defined and public.
  template <class U, class V>
  KOKKOS_FORCEINLINE_FUNCTION pair<first_type, second_type>& operator=(
      const pair<U, V>& p) {
    first  = p.first;
    second = p.second;
    return *this;
  }

  /// \brief Return the std::pair version of this object.
  ///
  /// This is <i>not</i> a device function; you may not call it on a
  /// CUDA device.  It is meant to be called on the host, if the user
  /// wants an std::pair instead of a Kokkos::pair.
  ///
  /// \note This is not a conversion operator, since defining a
  ///   conversion operator made the relational operators have
  ///   ambiguous definitions.
  std::pair<T1, T2> to_std_pair() const {
    return std::make_pair(first, second);
  }
};

template <class T1, class T2>
struct pair<T1, T2&> {
  //! The first template parameter of this class.
  using first_type = T1;
  //! The second template parameter of this class.
  using second_type = T2&;

  //! The first element of the pair.
  first_type first;
  //! The second element of the pair.
  second_type second;

  /// \brief Constructor that takes both elements of the pair.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
  KOKKOS_FORCEINLINE_FUNCTION constexpr pair(first_type const& f, second_type s)
      : first(f), second(s) {}

  /// \brief Copy constructor.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
  template <class U, class V>
  KOKKOS_FORCEINLINE_FUNCTION constexpr pair(const pair<U, V>& p)
      : first(p.first), second(p.second) {}

  // from std::pair<U,V>
  template <class U, class V>
  constexpr pair(const std::pair<U, V>& p) : first(p.first), second(p.second) {}

  /// \brief Assignment operator.
  ///
  /// This calls the assignment operators of T1 and T2.  It won't
  /// compile if the assignment operators are not defined and public.
  template <class U, class V>
  KOKKOS_FORCEINLINE_FUNCTION pair<first_type, second_type>& operator=(
      const pair<U, V>& p) {
    first  = p.first;
    second = p.second;
    return *this;
  }

  /// \brief Return the std::pair version of this object.
  ///
  /// This is <i>not</i> a device function; you may not call it on a
  /// CUDA device.  It is meant to be called on the host, if the user
  /// wants an std::pair instead of a Kokkos::pair.
  ///
  /// \note This is not a conversion operator, since defining a
  ///   conversion operator made the relational operators have
  ///   ambiguous definitions.
  std::pair<T1, T2> to_std_pair() const {
    return std::make_pair(first, second);
  }
};

template <class T1, class T2>
struct pair<T1&, T2> {
  //! The first template parameter of this class.
  using first_type = T1&;
  //! The second template parameter of this class.
  using second_type = T2;

  //! The first element of the pair.
  first_type first;
  //! The second element of the pair.
  second_type second;

  /// \brief Constructor that takes both elements of the pair.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
  KOKKOS_FORCEINLINE_FUNCTION constexpr pair(first_type f, second_type const& s)
      : first(f), second(s) {}

  /// \brief Copy constructor.
  ///
  /// This calls the copy constructors of T1 and T2.  It won't compile
  /// if those copy constructors are not defined and public.
  template <class U, class V>
  KOKKOS_FORCEINLINE_FUNCTION constexpr pair(const pair<U, V>& p)
      : first(p.first), second(p.second) {}

  // from std::pair<U,V>
  template <class U, class V>
  constexpr pair(const std::pair<U, V>& p) : first(p.first), second(p.second) {}

  /// \brief Assignment operator.
  ///
  /// This calls the assignment operators of T1 and T2.  It won't
  /// compile if the assignment operators are not defined and public.
  template <class U, class V>
  KOKKOS_FORCEINLINE_FUNCTION pair<first_type, second_type>& operator=(
      const pair<U, V>& p) {
    first  = p.first;
    second = p.second;
    return *this;
  }

  /// \brief Return the std::pair version of this object.
  ///
  /// This is <i>not</i> a device function; you may not call it on a
  /// CUDA device.  It is meant to be called on the host, if the user
  /// wants an std::pair instead of a Kokkos::pair.
  ///
  /// \note This is not a conversion operator, since defining a
  ///   conversion operator made the relational operators have
  ///   ambiguous definitions.
  std::pair<T1, T2> to_std_pair() const {
    return std::make_pair(first, second);
  }
};

template <class T1, class T2>
pair(const std::pair<T1, T2>&) -> pair<T1, T2>;

//! Equality operator for Kokkos::pair.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION constexpr bool operator==(const pair<T1, T2>& lhs,
                                                      const pair<T1, T2>& rhs) {
  return lhs.first == rhs.first && lhs.second == rhs.second;
}

//! Inequality operator for Kokkos::pair.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION constexpr bool operator!=(const pair<T1, T2>& lhs,
                                                      const pair<T1, T2>& rhs) {
  return !(lhs == rhs);
}

//! Less-than operator for Kokkos::pair.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION constexpr bool operator<(const pair<T1, T2>& lhs,
                                                     const pair<T1, T2>& rhs) {
  return lhs.first < rhs.first ||
         (!(rhs.first < lhs.first) && lhs.second < rhs.second);
}

//! Less-than-or-equal-to operator for Kokkos::pair.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION constexpr bool operator<=(const pair<T1, T2>& lhs,
                                                      const pair<T1, T2>& rhs) {
  return !(rhs < lhs);
}

//! Greater-than operator for Kokkos::pair.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION constexpr bool operator>(const pair<T1, T2>& lhs,
                                                     const pair<T1, T2>& rhs) {
  return rhs < lhs;
}

//! Greater-than-or-equal-to operator for Kokkos::pair.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION constexpr bool operator>=(const pair<T1, T2>& lhs,
                                                      const pair<T1, T2>& rhs) {
  return !(lhs < rhs);
}

/// \brief Return a new pair.
///
/// This is a "nonmember constructor" for Kokkos::pair.  It works just
/// like std::make_pair.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION constexpr pair<T1, T2> make_pair(T1 x, T2 y) {
  return (pair<T1, T2>(x, y));
}

/// \brief Return a pair of references to the input arguments.
///
/// This compares to std::tie (new in C++11).  You can use it to
/// assign to two variables at once, from the result of a function
/// that returns a pair.  For example (<tt>__device__</tt> and
/// <tt>__host__</tt> attributes omitted for brevity):
/// \code
/// // Declaration of the function to call.
/// // First return value: operation count.
/// // Second return value: whether all operations succeeded.
/// Kokkos::pair<int, bool> someFunction ();
///
/// // Code that uses Kokkos::tie.
/// int myFunction () {
///   int count = 0;
///   bool success = false;
///
///   // This assigns to both count and success.
///   Kokkos::tie (count, success) = someFunction ();
///
///   if (! success) {
///     // ... Some operation failed;
///     //     take corrective action ...
///   }
///   return count;
/// }
/// \endcode
///
/// The line that uses tie() could have been written like this:
/// \code
///   Kokkos::pair<int, bool> result = someFunction ();
///   count = result.first;
///   success = result.second;
/// \endcode
///
/// Using tie() saves two lines of code and avoids a copy of each
/// element of the pair.  The latter could be significant if one or
/// both elements of the pair are more substantial objects than \c int
/// or \c bool.
template <class T1, class T2>
KOKKOS_FORCEINLINE_FUNCTION pair<T1&, T2&> tie(T1& x, T2& y) {
  return (pair<T1&, T2&>(x, y));
}

namespace Impl {
template <class T>
struct is_pair_like : std::false_type {};
template <class T, class U>
struct is_pair_like<Kokkos::pair<T, U>> : std::true_type {};
template <class T, class U>
struct is_pair_like<std::pair<T, U>> : std::true_type {};

template <typename T>
struct is_std_pair : std::false_type {};

template <typename T1, typename T2>
struct is_std_pair<std::pair<T1, T2>> : std::true_type {};

template <typename T>
constexpr auto convert_to_kokkos_pair_if_std_pair(T t) {
  if constexpr (is_std_pair<T>::value)
    return Kokkos::pair<typename T::first_type, typename T::second_type>{t};
  else
    return t;
}

// Concept that checks if ANY type in a pack is a std::pair
template <typename... Args>
concept ContainsStdPair = (is_std_pair<Args>::value || ...);

}  // end namespace Impl

// index-based overloads
template <std::size_t I, class T1, class T2>
KOKKOS_INLINE_FUNCTION constexpr auto& get(Kokkos::pair<T1, T2>& p) noexcept {
  static_assert(I < 2, "Kokkos::pair only has 2 elements");
  if constexpr (I == 0) {
    return p.first;
  } else {
    return p.second;
  }
}

template <std::size_t I, class T1, class T2>
KOKKOS_INLINE_FUNCTION constexpr const auto& get(
    const Kokkos::pair<T1, T2>& p) noexcept {
  static_assert(I < 2, "Kokkos::pair only has 2 elements");
  if constexpr (I == 0) {
    return p.first;
  } else {
    return p.second;
  }
}

// Suppress clang-tidy warnings for this function since moving would do the
// incorrect thing here
// NOLINTBEGIN(cppcoreguidelines-rvalue-reference-param-not-moved)
template <std::size_t I, class T1, class T2>
KOKKOS_INLINE_FUNCTION constexpr auto&& get(Kokkos::pair<T1, T2>&& p) noexcept {
  static_assert(I < 2, "Kokkos::pair only has 2 elements");
  if constexpr (I == 0) {
    return std::forward<T1>(p.first);
  } else {
    return std::forward<T2>(p.second);
  }
}
// NOLINTEND(cppcoreguidelines-rvalue-reference-param-not-moved)

template <std::size_t I, class T1, class T2>
KOKKOS_INLINE_FUNCTION constexpr const auto&& get(
    const Kokkos::pair<T1, T2>&& p) noexcept {
  static_assert(I < 2, "Kokkos::pair only has 2 elements");
  if constexpr (I == 0) {
    return std::forward<const T1>(p.first);
  } else {
    return std::forward<const T2>(p.second);
  }
}

// type-based overloads
template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr T& get(Kokkos::pair<T, U>& p) noexcept {
  return p.first;
}

template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr const T& get(
    const Kokkos::pair<T, U>& p) noexcept {
  return p.first;
}

// Suppress clang-tidy warnings for this function since moving would do the
// incorrect thing here
// NOLINTBEGIN(cppcoreguidelines-rvalue-reference-param-not-moved)
template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr T&& get(Kokkos::pair<T, U>&& p) noexcept {
  return std::forward<T>(p.first);
}
// NOLINTEND(cppcoreguidelines-rvalue-reference-param-not-moved)

template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr const T&& get(
    const Kokkos::pair<T, U>&& p) noexcept {
  return std::forward<const T>(p.first);
}

template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr T& get(Kokkos::pair<U, T>& p) noexcept {
  return p.second;
}

template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr const T& get(
    const Kokkos::pair<U, T>& p) noexcept {
  return p.second;
}

// Suppress clang-tidy warnings for this function since moving would do the
// incorrect thing here
// NOLINTBEGIN(cppcoreguidelines-rvalue-reference-param-not-moved)
template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr T&& get(Kokkos::pair<U, T>&& p) noexcept {
  return std::forward<T>(p.second);
}
// NOLINTEND(cppcoreguidelines-rvalue-reference-param-not-moved)

template <class T, class U>
KOKKOS_INLINE_FUNCTION constexpr const T&& get(
    const Kokkos::pair<U, T>&& p) noexcept {
  return std::forward<const T>(p.second);
}
}  // namespace Kokkos

// Suppress clang-tidy warnings complaining about modifying std namespace
// objects Specializing tuple_element and tuple_size are perfectly okay and
// necessary, so these linter warnings are spurious
// NOLINTBEGIN(bugprone-std-namespace-modification)
template <class T1, class T2>
struct std::tuple_size<Kokkos::pair<T1, T2>>
    : std::integral_constant<std::size_t, 2> {};

template <std::size_t I, class T1, class T2>
struct std::tuple_element<I, Kokkos::pair<T1, T2>> {
  static_assert(I < 2, "Kokkos::pair only has 2 elements");
};

template <class T1, class T2>
struct std::tuple_element<0, Kokkos::pair<T1, T2>> {
  using type = T1;
};

template <class T1, class T2>
struct std::tuple_element<1, Kokkos::pair<T1, T2>> {
  using type = T2;
};
// NOLINTEND(bugprone-std-namespace-modification)

#ifdef KOKKOS_IMPL_PUBLIC_INCLUDE_NOTDEFINED_PAIR
#undef KOKKOS_IMPL_PUBLIC_INCLUDE
#undef KOKKOS_IMPL_PUBLIC_INCLUDE_NOTDEFINED_PAIR
#endif
#endif  // KOKKOS_PAIR_HPP
