/**
 * An Intrusive Smart Pointer based on the proposal P0468R1
 * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0468r1.html
 *
 * Copyright (C) 2016, Isabella Muerte
 * Copyright (C) 2022, SUSE Software Solutions Germany GmbH
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright notice,
 *      this list of conditions and the following disclaimer in the documentation
 *      and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


#ifndef ZYPP_GLIB_UTIL_RETAINPTR_H
#define ZYPP_GLIB_UTIL_RETAINPTR_H

#include <zypp-core/ng/meta/TypeTraits>
#include <functional>
#include <utility>
#include <atomic>

namespace stdx {

using std::is_convertible;
using std::is_same;

using std::conditional_t;
using std::add_pointer_t;
using std::nullptr_t;
using std::is_detected;
using std::detected_t;
using std::detected_or_t;

using std::declval;

} /* namespace stdx */

namespace stdx::detail {

template <class T, template <class...> class U, class... Args>
using is_detected_exact = std::is_same<T, detected_t<U, Args...>>;

template <class T>
using has_pointer = typename T::pointer;

template <class T>
using has_default_action = typename T::default_action;

template <class T, class P>
using has_use_count = decltype(T::use_count(std::declval<P>()));

} /* namespace stdx::detail  */

namespace stdx {

template <class> struct retain_traits;

template <class T>
struct atomic_reference_count {
  template <class> friend struct retain_traits;
protected:
  atomic_reference_count () = default;
private:
  std::atomic<long> count { 1 };
};

template <class T>
struct reference_count {
  template <class> friend struct retain_traits;
protected:
  reference_count () = default;
private:
  long count { 1 };
};

struct retain_object_t {  retain_object_t () noexcept = default; };
struct adopt_object_t {  adopt_object_t () noexcept = default; };

constexpr retain_object_t retain_object { };
constexpr adopt_object_t adopt_object { };

template <class T>
struct retain_traits final {
  template <class U>
  using enable_if_base = std::enable_if_t<std::is_base_of_v<U, T>>;

  template <class U, class = enable_if_base<U>>
  static void increment (atomic_reference_count<U>* ptr) noexcept {
    ptr->count.fetch_add(1, std::memory_order_relaxed);
  }

  template <class U, class = enable_if_base<U>>
  static void decrement (atomic_reference_count<U>* ptr) noexcept {
    ptr->count.fetch_sub(1, std::memory_order_acq_rel);
    if (not use_count(ptr)) { delete static_cast<T*>(ptr); }
  }

  template <class U, class = enable_if_base<U>>
  static long use_count (atomic_reference_count<U>* ptr) noexcept {
    return ptr->count.load(std::memory_order_relaxed);
  }

  template <class U, class = enable_if_base<U>>
  static void increment (reference_count<U>* ptr) noexcept {
    ++ptr->count;
  }
  template <class U, class = enable_if_base<U>>
  static void decrement (reference_count<U>* ptr) noexcept {
    --ptr->count;
    if (not use_count(ptr)) { delete static_cast<T*>(ptr); }
  }
  template <class U, class = enable_if_base<U>>
  static long use_count (reference_count<U>* ptr) noexcept {
    return ptr->count;
  }
};

template <class T, class R=retain_traits<T>>
struct retain_ptr {
  using element_type = T;
  using traits_type = R;

  using pointer = detected_or_t<
    add_pointer_t<element_type>,
    detail::has_pointer,
    traits_type
  >;

  using default_action = detected_or_t<
    adopt_object_t,
    detail::has_default_action,
    traits_type
  >;

  static constexpr bool CheckAction = std::disjunction_v<
    std::is_same<default_action, adopt_object_t>,
    std::is_same<default_action, retain_object_t>
  >;

  static_assert(
    CheckAction,
    "traits_type::default_action must be adopt_object_t or retain_object_t");

  static constexpr auto has_use_count = is_detected<
    detail::has_use_count,
    traits_type,
    pointer
  > { };

  retain_ptr (pointer ptr, retain_object_t) :
    retain_ptr { ptr, adopt_object }
  { if (*this) { traits_type::increment(this->get()); } }

  retain_ptr (pointer ptr, adopt_object_t) : ptr { ptr } { }

  explicit retain_ptr (pointer ptr) :
    retain_ptr { ptr, default_action() }
  { }

  retain_ptr (nullptr_t) : retain_ptr { } { }

  retain_ptr (retain_ptr const& that) :
    ptr { that.ptr }
  { if (*this) { traits_type::increment(this->get()); } }

  retain_ptr (retain_ptr&& that) noexcept :
    ptr { that.detach() }
  { }

  retain_ptr () noexcept = default;
  ~retain_ptr () {
    if (*this) { traits_type::decrement(this->get()); }
  }

  retain_ptr& operator = (retain_ptr const& that) {
    retain_ptr(that).swap(*this);
    return *this;
  }

  retain_ptr& operator = (retain_ptr&& that) {
    retain_ptr(std::move(that)).swap(*this);
    return *this;
  }

  retain_ptr& operator = (nullptr_t) noexcept { this->reset( nullptr ); return *this; }

  void swap (retain_ptr& that) noexcept {
    using std::swap;
    swap(this->ptr, that.ptr);
  }

  explicit operator bool () const noexcept { return this->get(); }
  decltype(auto) operator * () const noexcept { return *this->get(); }
  pointer operator -> () const noexcept { return this->get(); }

  pointer get () const noexcept { return this->ptr; }

  long use_count () const {
    if constexpr (has_use_count) {
      return this->get() ? traits_type::use_count(this->get()) : 0;
    } else { return -1; }
  }

  pointer detach () noexcept {
    auto ptr = this->get();
    this->ptr = pointer { };
    return ptr;
  }

  void reset (pointer ptr, retain_object_t) {
    *this = retain_ptr(ptr, retain_object);
  }

  void reset (pointer ptr, adopt_object_t) noexcept {
    *this = retain_ptr(ptr, adopt_object);
  }

  void reset (pointer ptr) { *this = retain_ptr(ptr, default_action()); }

private:
  pointer ptr { };
};

template <class T, class R>
void swap (retain_ptr<T, R>& lhs, retain_ptr<T, R>& rhs) noexcept {
  lhs.swap(rhs);
}

template <class T, class R>
bool operator == (
  retain_ptr<T, R> const& lhs,
  retain_ptr<T, R> const& rhs
) noexcept { return lhs.get() == rhs.get(); }

template <class T, class R>
bool operator != (
  retain_ptr<T, R> const& lhs,
  retain_ptr<T, R> const& rhs
) noexcept { return lhs.get() != rhs.get(); }

template <class T, class R>
bool operator >= (
  retain_ptr<T, R> const& lhs,
  retain_ptr<T, R> const& rhs
) noexcept { return lhs.get() >= rhs.get(); }

template <class T, class R>
bool operator <= (
  retain_ptr<T, R> const& lhs,
  retain_ptr<T, R> const& rhs
) noexcept { return lhs.get() <= rhs.get(); }

template <class T, class R>
bool operator > (
  retain_ptr<T, R> const& lhs,
  retain_ptr<T, R> const& rhs
) noexcept { return lhs.get() > rhs.get(); }

template <class T, class R>
bool operator < (
  retain_ptr<T, R> const& lhs,
  retain_ptr<T, R> const& rhs
) noexcept { return lhs.get() < rhs.get(); }

template <class T, class R>
bool operator == (retain_ptr<T, R> const& lhs, nullptr_t) noexcept {
  return bool(lhs);
}

template <class T, class R>
bool operator != (retain_ptr<T, R> const& lhs, nullptr_t) noexcept {
  return not lhs;
}

template <class T, class R>
bool operator >= (retain_ptr<T, R> const& lhs, nullptr_t) noexcept {
  return not (lhs < nullptr);
}

template <class T, class R>
bool operator <= (retain_ptr<T, R> const& lhs, nullptr_t) noexcept {
  return nullptr < lhs;
}

template <class T, class R>
bool operator > (retain_ptr<T, R> const& lhs, nullptr_t) noexcept {
  return not (nullptr < lhs);
}

template <class T, class R>
bool operator < (retain_ptr<T, R> const& lhs, nullptr_t) noexcept {
  return std::less<>()(lhs.get(), nullptr);
}

template <class T, class R>
bool operator == (nullptr_t, retain_ptr<T, R> const& rhs) noexcept {
  return not rhs;
}

template <class T, class R>
bool operator != (nullptr_t, retain_ptr<T, R> const& rhs) noexcept {
  return bool(rhs);
}

template <class T, class R>
bool operator >= (nullptr_t, retain_ptr<T, R> const& rhs) noexcept {
  return not (nullptr < rhs);
}

template <class T, class R>
bool operator <= (nullptr_t, retain_ptr<T, R> const& rhs) noexcept;

template <class T, class R>
bool operator > (nullptr_t, retain_ptr<T, R> const& rhs) noexcept {
  return not (rhs < nullptr);
}

template <class T, class R>
bool operator < (nullptr_t, retain_ptr<T, R> const& rhs) noexcept {
  return std::less<>()(rhs.get(), nullptr);
}

template<typename _Tp, typename _Traits, typename _Up>
inline retain_ptr<_Tp, _Traits>
reinterpret_pointer_cast(const retain_ptr<_Up, _Traits>& __r) noexcept
{
  using _Sp = retain_ptr<_Tp, _Traits>;
  return _Sp( reinterpret_cast<typename _Sp::element_type*>(__r.get()), retain_object );
}

template<typename _Tp, typename _Traits, typename _Up>
inline retain_ptr<_Tp, _Traits>
static_pointer_cast(const retain_ptr<_Up, _Traits>& __r) noexcept
{
  using _Sp = retain_ptr<_Tp, _Traits>;
  return _Sp( static_cast<typename _Sp::element_type*>(__r.get()), retain_object );
}

} /* namespace stdx */

namespace zypp::glib {
  template <class T, class R=stdx::retain_traits<T>>
  using RetainPtr = stdx::retain_ptr<T,R>;

  constexpr stdx::retain_object_t retain_object { };
  constexpr stdx::adopt_object_t  adopt_object { };
}


#endif /* ZYPP_GLIB_UTIL_RETAINPTR_H */
