#ifndef _ARC_
#define _ARC_

#include <atomic>
#include <cassert>
#include <utility>

namespace sharedjsi {

template <typename T> struct ArcPointee {
  T value;
  mutable std::atomic<size_t> strong_count;

  ArcPointee(T &&value, size_t strong_count)
      : value /* = */ (std::move(value)), strong_count /* = */ (strong_count) {}
};

template <typename T> class Arc {
public:
  // Post-condition: refcount = 1.
  Arc(T value) noexcept
      : ptr /* = */ (new ArcPointee<T>(std::move(value), 1)) {}

  // Post-condition: refcount ±= 0; schedules dtor glue to `-= 1`.
  // There has to be a 1-to-1 relation between `into_raw()` and `from_raw()`.
  static Arc<T> from_raw(void *ptr) noexcept {
    return Arc(static_cast<ArcPointee<T> *>(ptr));
  }

  // The equivalent of boost's `detach()`.
  // Post-condition: refcount ±= 0; disables the `-= 1` eventual dtor glue.
  // There has to be a 1-to-1 relation between `into_raw()` and `from_raw()`.
  static void *into_raw(Arc<T> &&self) noexcept {
    auto ptr = self.ptr;
    self.ptr = nullptr;
    return static_cast<void *>(ptr);
  }

  // The equivalent of boost's `get()`.
  // Post-condition: refcount ±= 0; dtor untouched.
  static void *as_raw(Arc<T> const &arc) noexcept { return arc.ptr; }

  // Borrow a `void *` as a `Arc<T>`, *temporarily*.
  // (i.e., without touching the refcount nor setting up any ownership).
  //   - You could then use the copy (clone) ctor to get back an owned `Arc<T>`,
  //     via the retain therein.
  // Post-condition: refcount ±= 0; dtor not enabled.
  static Arc<T> &borrow_from_raw(void *&ptr) noexcept {
    return *reinterpret_cast<Arc<T> *>(&ptr);
  }

  // Copy (clone) constructor
  // Post-condition: refcount += 1; schedules new `-= 1` dtor glue.
  Arc(Arc<T> const &other) noexcept : ptr /* = */ (other.ptr) {
    ++(ptr->strong_count);
  }

  // Destructor / dtor.
  // Post-condition: refcount = -1.
  ~Arc() {
    if (ptr != nullptr) { // <- if not moved from.
      size_t count = --(ptr->strong_count);
      if (count == 0) {
        delete ptr;
      }
    }
  }

  // Move constructor
  // Post-condition: refcount ±= 0 (and `rhs.ptr == nullptr`); moves dtor glue
  // from `rhs` to `this`.
  Arc(Arc<T> &&rhs) noexcept : ptr /* = */ (rhs.ptr) { rhs.ptr = nullptr; }

  // Using `swap()`, we can use temporaries to derive the tricky assignments
  // semantics off the above constructor & destructor operations, thanks to the
  // C++ RAII semantics of the temporary. Post-condition: refcount ±= 0.
  void swap(Arc<T> &rhs) noexcept {
    ArcPointee<T> *rhs_ptr = rhs.ptr;
    rhs.ptr = this->ptr;
    this->ptr = rhs_ptr;
  }

  // Copy (clone) assignment.
  // Post-condition: rhs refcount += 1; orig `*this` destroyed; schedules new
  // `-= 1` dtor glue.
  Arc<T> &operator=(Arc<T> const &rhs) noexcept {
    {
      Arc temporary{rhs}; // copy ctor: +1 of rhs
      temporary.swap(*this);
    } // dtor: -1 on what used to be `*this`.
    return *this;
  }

  // Move assignment
  // Post-condition: rhs refcount ±= 0; `rhs.ptr == nullptr`; orig `*this`
  // destroyed. Moved dtor.
  Arc<T> &operator=(Arc<T> &&rhs) noexcept {
    {
      Arc<T> temporary{
          std::move(rhs)}; // move ctor: +0 of rhs; `rhs` is now `nullptr`.
      temporary.swap(*this);
    } // dtor: -1 on what used to be `*this`.
    return *this;
  }

  // Deref
  // Post-condition: rhs refcount ±= 0; dtor untouched.
  T &operator*() const noexcept {
    assert(ptr != nullptr);
    return ptr->value;
  }

  // Post-condition: rhs refcount ±= 0; dtor untouched.
  T *operator->() const noexcept {
    assert(ptr != nullptr);
    return &ptr->value;
  }

private:
  ArcPointee<T> *ptr;

  // from raw
  Arc<T>(ArcPointee<T> *ptr) : ptr(ptr) {}
};

#define DEFINE_RETAIN_RELEASE_FOR(T, retain, release)                          \
  extern "C" {                                                                 \
  /* Post-condition: refcount += 1; dtor untouched. */                         \
  static void retain(void *ptr) {                                              \
    ++(static_cast<ArcPointee<T> *>(ptr)->strong_count);                \
  }                                                                            \
                                                                               \
  /* Post-condition: refcount -= 1; dtor untouched. */                         \
  static void release(void *ptr) { Arc<T>::from_raw(ptr); }             \
  }                                                                            \
  static_assert(true,                                                          \
                "") // <- to require a semi-colon when the macro is invoked.

#define DEFINE_RETAIN_RELEASE_ERASED_T_FOR(T, retain, release)                 \
  extern "C" {                                                                 \
  /* Post-condition: refcount += 1; dtor untouched. */                         \
  static Erased *retain(Erased_t const *ptr) {                                 \
    ++(((ArcPointee<T> *)(ptr))->strong_count);                         \
    return const_cast<Erased_t *>(ptr);                                        \
  }                                                                            \
                                                                               \
  /* Post-condition: refcount -= 1; dtor untouched. */                         \
  static void release(Erased_t *ptr) {                                         \
    Arc<T>::from_raw(static_cast<void *>(ptr));                         \
  }                                                                            \
  }                                                                            \
  static_assert(true,                                                          \
                "") // <- to require a semi-colon when the macro is invoked.


} // namespace sharedjsi


#endif // _ARC_
