//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// Modifications Copyright (c) 2025 Advanced Micro Devices, Inc.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#ifndef TEST_SUPPORT_UNIQUE_PTR_TEST_HELPER_H
#define TEST_SUPPORT_UNIQUE_PTR_TEST_HELPER_H

#include <cuda/std/__memory_>
#include <cuda/std/type_traits>

#include "deleter_types.h"
#include "test_macros.h"

#if TEST_CUDA_COMPILER(NVCC) || TEST_COMPILER(NVRTC)
TEST_NV_DIAG_SUPPRESS(3060) // call to __builtin_is_constant_evaluated appearing in a non-constexpr function
#endif // TEST_CUDA_COMPILER(NVCC) || TEST_COMPILER(NVRTC)
TEST_DIAG_SUPPRESS_GCC("-Wtautological-compare")
TEST_DIAG_SUPPRESS_CLANG("-Wtautological-compare")

TEST_GLOBAL_VARIABLE int A_count = 0;

struct A
{
  __host__ __device__ A()
  {
    ++A_count;
  }
  __host__ __device__ A(const A&)
  {
    ++A_count;
  }
  __host__ __device__ virtual ~A()
  {
    --A_count;
  }
};

TEST_GLOBAL_VARIABLE int B_count = 0;

struct B : public A
{
  __host__ __device__ B()
  {
    ++B_count;
  }
  __host__ __device__ B(const B&)
      : A()
  {
    ++B_count;
  }
  __host__ __device__ virtual ~B()
  {
    --B_count;
  }
};

template <class T>
typename cuda::std::enable_if<!cuda::std::is_array<T>::value, T*>::type __host__ __device__ newValue(int num_elements)
{
  assert(num_elements == 1);
  return new T;
}

template <class T>
typename cuda::std::enable_if<cuda::std::is_array<T>::value, typename cuda::std::remove_all_extents<T>::type*>::type
  __host__ __device__
  newValue(int num_elements)
{
  typedef typename cuda::std::remove_all_extents<T>::type VT;
  assert(num_elements >= 1);
  return new VT[num_elements];
}

struct IncompleteType;

__host__ __device__ void checkNumIncompleteTypeAlive(int i);
__host__ __device__ int getNumIncompleteTypeAlive();
__host__ __device__ IncompleteType* getNewIncomplete();
__host__ __device__ IncompleteType* getNewIncompleteArray(int size);

template <class ThisT, class... Args>
struct args_is_this_type : cuda::std::false_type
{};

template <class ThisT, class A1>
struct args_is_this_type<ThisT, A1> : cuda::std::is_same<ThisT, typename cuda::std::decay<A1>::type>
{};

template <class IncompleteT = IncompleteType, class Del = cuda::std::default_delete<IncompleteT>>
struct StoresIncomplete
{
  static_assert((cuda::std::is_same<IncompleteT, IncompleteType>::value
                 || cuda::std::is_same<IncompleteT, IncompleteType[]>::value),
                "");

  cuda::std::unique_ptr<IncompleteT, Del> m_ptr;

  StoresIncomplete(StoresIncomplete const&) = delete;
  StoresIncomplete(StoresIncomplete&&)      = default;

  template <class... Args>
  __host__ __device__ StoresIncomplete(Args&&... args)
      : m_ptr(cuda::std::forward<Args>(args)...)
  {
    static_assert(!args_is_this_type<StoresIncomplete, Args...>::value, "");
  }

  __host__ __device__ ~StoresIncomplete();

  __host__ __device__ IncompleteType* get() const
  {
    return m_ptr.get();
  }
  __host__ __device__ Del& get_deleter()
  {
    return m_ptr.get_deleter();
  }
};

template <class IncompleteT = IncompleteType, class Del = cuda::std::default_delete<IncompleteT>, class... Args>
__host__ __device__ void doIncompleteTypeTest(int expect_alive, Args&&... ctor_args)
{
  checkNumIncompleteTypeAlive(expect_alive);
  {
    StoresIncomplete<IncompleteT, Del> sptr(cuda::std::forward<Args>(ctor_args)...);
    checkNumIncompleteTypeAlive(expect_alive);
    if (expect_alive == 0)
    {
      assert(sptr.get() == nullptr);
    }
    else
    {
      assert(sptr.get() != nullptr);
    }
  }
  checkNumIncompleteTypeAlive(0);
}

// TODO(HIP/AMD): add __CCL_DEVICE to is_incomplete_test_anchor asap
#define INCOMPLETE_TEST_EPILOGUE()                                            \
  int is_incomplete_test_anchor = is_incomplete_test();                       \
                                                                              \
  TEST_GLOBAL_VARIABLE int IncompleteType_count = 0;                          \
  struct IncompleteType                                                       \
  {                                                                           \
    __host__ __device__ IncompleteType()                                      \
    {                                                                         \
      ++IncompleteType_count;                                                 \
    }                                                                         \
    __host__ __device__ ~IncompleteType()                                     \
    {                                                                         \
      --IncompleteType_count;                                                 \
    }                                                                         \
  };                                                                          \
                                                                              \
  __host__ __device__ void checkNumIncompleteTypeAlive(int i)                 \
  {                                                                           \
    assert(IncompleteType_count == i);                                        \
  }                                                                           \
  __host__ __device__ int getNumIncompleteTypeAlive()                         \
  {                                                                           \
    return IncompleteType_count;                                              \
  }                                                                           \
  __host__ __device__ IncompleteType* getNewIncomplete()                      \
  {                                                                           \
    return new IncompleteType;                                                \
  }                                                                           \
  __host__ __device__ IncompleteType* getNewIncompleteArray(int size)         \
  {                                                                           \
    return new IncompleteType[size];                                          \
  }                                                                           \
                                                                              \
  template <class IncompleteT, class Del>                                     \
  __host__ __device__ StoresIncomplete<IncompleteT, Del>::~StoresIncomplete() \
  {}

// TODO(HIP/AMD): add constexpr to is_incomplete_test if required (and possible) 
#define DEFINE_AND_RUN_IS_INCOMPLETE_TEST(...)                  \
  __host__ __device__ static int is_incomplete_test()           \
  {                                                             \
    __VA_ARGS__ return 0;                                       \
  }                                                             \
  INCOMPLETE_TEST_EPILOGUE()

#endif // TEST_SUPPORT_UNIQUE_PTR_TEST_HELPER_H
