// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * Copyright (C) 2013      Technische Universität Dresden.
 * Copyright (C) 2014-2017, 2020, 2023-2024 Kernkonzept GmbH.
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */

#pragma once

#include "type_traits"

namespace cxx
{

template< typename T >
struct default_delete
{
  default_delete() {}

  template< typename U >
  default_delete(default_delete<U> const &) {}

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

template< typename T >
struct default_delete<T[]>
{
  default_delete() {}

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

template< typename T, typename C >
struct unique_ptr_index_op {};

template< typename T, typename C >
struct unique_ptr_index_op<T[], C>
{
  typedef T &reference;
  reference operator [] (int idx) const
  { return static_cast<C const *>(this)->get()[idx]; }
};

template< typename T, typename T_Del = default_delete<T> >
class unique_ptr : public unique_ptr_index_op<T, unique_ptr<T, T_Del> >
{
private:
  struct _unspec;
  typedef _unspec* _unspec_ptr_type;

public:
  typedef cxx::remove_extent_t<T> element_type;
  typedef element_type *pointer;
  typedef element_type &reference;
  typedef T_Del deleter_type;

  unique_ptr() : _ptr(pointer()) {}

  explicit unique_ptr(pointer p) : _ptr(p) {}

  unique_ptr(unique_ptr &&o) : _ptr(o.release()) {}

  ~unique_ptr() { reset(); }

  unique_ptr &operator = (unique_ptr &&o)
  {
    reset(o.release());
    return *this;
  }

  unique_ptr &operator = (_unspec_ptr_type)
  {
    reset();
    return *this;
  }

  element_type &operator * () const { return *get(); }
  pointer operator -> () const { return get(); }

  pointer get() const { return _ptr; }

  operator _unspec_ptr_type () const
  { return reinterpret_cast<_unspec_ptr_type>(get()); }

  pointer release()
  {
    pointer r = _ptr;
    _ptr = 0;
    return r;
  }

  void reset(pointer p = pointer())
  {
    if (p != get())
      {
        deleter_type()(get());
        _ptr = p;
      }
  }

  unique_ptr(unique_ptr const &) = delete;
  unique_ptr &operator = (unique_ptr const &) = delete;

private:
  pointer _ptr;
};

template< typename T >
unique_ptr<T>
make_unique_ptr(T *p)
{ return unique_ptr<T>(p); }

template< typename T >
cxx::enable_if_t<cxx::is_array<T>::value, unique_ptr<T>>
make_unique(unsigned long size)
{ return cxx::unique_ptr<T>(new cxx::remove_extent_t<T>[size]()); }

template< typename T, typename... Args >
cxx::enable_if_t<!cxx::is_array<T>::value, unique_ptr<T>>
make_unique(Args &&... args)
{ return cxx::unique_ptr<T>(new T(cxx::forward<Args>(args)...)); }

}
