//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

// <memory>

// unique_ptr

// Example move-only deleter

#ifndef SUPPORT_DELETER_TYPES_H
#define SUPPORT_DELETER_TYPES_H

#include <cuda/std/cassert>

#include <type_traits>
#include <utility>

#include "min_allocator.h"
#include "test_macros.h"

template <class T>
class Deleter
{
  int state_;

  Deleter(const Deleter&);
  Deleter& operator=(const Deleter&);

public:
  Deleter(Deleter&& r)
      : state_(r.state_)
  {
    r.state_ = 0;
  }
  Deleter& operator=(Deleter&& r)
  {
    state_   = r.state_;
    r.state_ = 0;
    return *this;
  }

  Deleter()
      : state_(0)
  {}
  explicit Deleter(int s)
      : state_(s)
  {}
  ~Deleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  template <class U>
  Deleter(Deleter<U>&& d, typename std::enable_if<!std::is_same<U, T>::value>::type* = 0)
      : state_(d.state())
  {
    d.set_state(0);
  }

private:
  template <class U>
  Deleter(const Deleter<U>& d, typename std::enable_if<!std::is_same<U, T>::value>::type* = 0);

public:
  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete p;
  }
};

template <class T>
class Deleter<T[]>
{
  int state_;

  Deleter(const Deleter&);
  Deleter& operator=(const Deleter&);

public:
  Deleter(Deleter&& r)
      : state_(r.state_)
  {
    r.state_ = 0;
  }
  Deleter& operator=(Deleter&& r)
  {
    state_   = r.state_;
    r.state_ = 0;
    return *this;
  }

  Deleter()
      : state_(0)
  {}
  explicit Deleter(int s)
      : state_(s)
  {}
  ~Deleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete[] p;
  }
};

template <class T>
void swap(Deleter<T>& x, Deleter<T>& y)
{
  Deleter<T> t(std::move(x));
  x = std::move(y);
  y = std::move(t);
}

template <class T>
class CDeleter
{
  int state_;

public:
  CDeleter()
      : state_(0)
  {}
  explicit CDeleter(int s)
      : state_(s)
  {}
  ~CDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  template <class U>
  CDeleter(const CDeleter<U>& d)
      : state_(d.state())
  {}

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete p;
  }
};

template <class T>
class CDeleter<T[]>
{
  int state_;

public:
  CDeleter()
      : state_(0)
  {}
  explicit CDeleter(int s)
      : state_(s)
  {}
  template <class U>
  CDeleter(const CDeleter<U>& d)
      : state_(d.state())
  {}

  ~CDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete[] p;
  }
};

template <class T>
void swap(CDeleter<T>& x, CDeleter<T>& y)
{
  CDeleter<T> t(std::move(x));
  x = std::move(y);
  y = std::move(t);
}

// Non-copyable deleter
template <class T>
class NCDeleter
{
  int state_;
  NCDeleter(NCDeleter const&);
  NCDeleter& operator=(NCDeleter const&);

public:
  NCDeleter()
      : state_(0)
  {}
  explicit NCDeleter(int s)
      : state_(s)
  {}
  ~NCDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete p;
  }
};

template <class T>
class NCDeleter<T[]>
{
  int state_;
  NCDeleter(NCDeleter const&);
  NCDeleter& operator=(NCDeleter const&);

public:
  NCDeleter()
      : state_(0)
  {}
  explicit NCDeleter(int s)
      : state_(s)
  {}
  ~NCDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete[] p;
  }
};

// Non-copyable deleter
template <class T>
class NCConstDeleter
{
  int state_;
  NCConstDeleter(NCConstDeleter const&);
  NCConstDeleter& operator=(NCConstDeleter const&);

public:
  NCConstDeleter()
      : state_(0)
  {}
  explicit NCConstDeleter(int s)
      : state_(s)
  {}
  ~NCConstDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p) const
  {
    delete p;
  }
};

template <class T>
class NCConstDeleter<T[]>
{
  int state_;
  NCConstDeleter(NCConstDeleter const&);
  NCConstDeleter& operator=(NCConstDeleter const&);

public:
  NCConstDeleter()
      : state_(0)
  {}
  explicit NCConstDeleter(int s)
      : state_(s)
  {}
  ~NCConstDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p) const
  {
    delete[] p;
  }
};

// Non-copyable deleter
template <class T>
class CopyDeleter
{
  int state_;

public:
  CopyDeleter()
      : state_(0)
  {}
  explicit CopyDeleter(int s)
      : state_(s)
  {}
  ~CopyDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  CopyDeleter(CopyDeleter const& other)
      : state_(other.state_)
  {}
  CopyDeleter& operator=(CopyDeleter const& other)
  {
    state_ = other.state_;
    return *this;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete p;
  }
};

template <class T>
class CopyDeleter<T[]>
{
  int state_;

public:
  CopyDeleter()
      : state_(0)
  {}
  explicit CopyDeleter(int s)
      : state_(s)
  {}
  ~CopyDeleter()
  {
    assert(state_ >= 0);
    state_ = -1;
  }

  CopyDeleter(CopyDeleter const& other)
      : state_(other.state_)
  {}
  CopyDeleter& operator=(CopyDeleter const& other)
  {
    state_ = other.state_;
    return *this;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    delete[] p;
  }
};

struct test_deleter_base
{
  static int count;
  static int dealloc_count;
};

int test_deleter_base::count         = 0;
int test_deleter_base::dealloc_count = 0;

template <class T>
class test_deleter : public test_deleter_base
{
  int state_;

public:
  test_deleter()
      : state_(0)
  {
    ++count;
  }
  explicit test_deleter(int s)
      : state_(s)
  {
    ++count;
  }
  test_deleter(const test_deleter& d)
      : state_(d.state_)
  {
    ++count;
  }
  ~test_deleter()
  {
    assert(state_ >= 0);
    --count;
    state_ = -1;
  }

  int state() const
  {
    return state_;
  }
  void set_state(int i)
  {
    state_ = i;
  }

  void operator()(T* p)
  {
    assert(state_ >= 0);
    ++dealloc_count;
    delete p;
  }
  test_deleter* operator&() const = delete;
};

template <class T>
void swap(test_deleter<T>& x, test_deleter<T>& y)
{
  test_deleter<T> t(std::move(x));
  x = std::move(y);
  y = std::move(t);
}

template <class T, size_t ID = 0>
class PointerDeleter
{
  PointerDeleter(const PointerDeleter&);
  PointerDeleter& operator=(const PointerDeleter&);

public:
  typedef min_pointer<T, std::integral_constant<size_t, ID>> pointer;

  PointerDeleter()                            = default;
  PointerDeleter(PointerDeleter&&)            = default;
  PointerDeleter& operator=(PointerDeleter&&) = default;
  explicit PointerDeleter(int) {}

  template <class U>
  PointerDeleter(PointerDeleter<U, ID>&&, typename std::enable_if<!std::is_same<U, T>::value>::type* = 0)
  {}

  void operator()(pointer p)
  {
    if (p)
    {
      delete std::addressof(*p);
    }
  }

private:
  template <class U>
  PointerDeleter(const PointerDeleter<U, ID>&, typename std::enable_if<!std::is_same<U, T>::value>::type* = 0);
};

template <class T, size_t ID>
class PointerDeleter<T[], ID>
{
  PointerDeleter(const PointerDeleter&);
  PointerDeleter& operator=(const PointerDeleter&);

public:
  typedef min_pointer<T, std::integral_constant<size_t, ID>> pointer;

  PointerDeleter()                            = default;
  PointerDeleter(PointerDeleter&&)            = default;
  PointerDeleter& operator=(PointerDeleter&&) = default;
  explicit PointerDeleter(int) {}

  template <class U>
  PointerDeleter(PointerDeleter<U, ID>&&, typename std::enable_if<!std::is_same<U, T>::value>::type* = 0)
  {}

  void operator()(pointer p)
  {
    if (p)
    {
      delete[] std::addressof(*p);
    }
  }

private:
  template <class U>
  PointerDeleter(const PointerDeleter<U, ID>&, typename std::enable_if<!std::is_same<U, T>::value>::type* = 0);
};

#endif // SUPPORT_DELETER_TYPES_H
