/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#pragma once
#include 
#include 
#include 
#include 
#include "td/utils/StringBuilder.h"
#include "td/utils/logging.h"
namespace td {
template 
class Ref;
class CntObject {
 private:
  mutable std::atomic cnt_;
  template 
  friend class Ref;
  void inc() const {
    cnt_.fetch_add(1, std::memory_order_relaxed);
  }
  bool dec() const {
    return cnt_.fetch_sub(1, std::memory_order_acq_rel) == 1;
  }
  void inc(int cnt) const {
    cnt_.fetch_add(cnt, std::memory_order_relaxed);
  }
  bool dec(int cnt) const {
    return cnt_.fetch_sub(cnt, std::memory_order_acq_rel) == cnt;
  }
 public:
  struct WriteError {};
  CntObject() : cnt_(1) {
  }
  CntObject(const CntObject& other) : CntObject() {
  }
  CntObject(CntObject&& other) : CntObject() {
  }
  CntObject& operator=(const CntObject& other) {
    return *this;
  }
  CntObject& operator=(CntObject&& other) {
    return *this;
  }
  virtual ~CntObject() {
    auto cnt = cnt_.load(std::memory_order_relaxed);
    (void)cnt;
    //TODO: assert(cnt == 0) will fail if object is allocated on stack
    assert(cnt == 0 || cnt == 1);
  }
  virtual CntObject* make_copy() const {
    throw WriteError();
  }
  bool is_unique() const {
    return cnt_.load(std::memory_order_acquire) == 1;
  }
  int get_refcnt() const {
    // use std::memory_order_acquire
    return cnt_.load(std::memory_order_acquire);
  }
  void assert_unique() const {
    assert(is_unique());
  }
  Ref clone() const;
};
typedef Ref RefAny;
template 
class Cnt : public CntObject {
  T value;
 public:
  template 
  Cnt(Args&&... args) : value(std::forward(args)...) {
    ///std::cout << "(N " << (void*)this << ")";
  }
  Cnt(const Cnt& x) : CntObject(), value(x.value) {
    ///std::cout << "(C)";
  }
  virtual ~Cnt() {
    ///std::cout << "(D " << (void*)this << ")";
  }
  T* operator->() {
    return &value;
  }
  const T* operator->() const {
    return &value;
  }
  T& operator*() {
    return value;
  }
  const T& operator*() const {
    return value;
  }
  Cnt* make_copy() const override {
    ///std::cout << "(c " << (const void*)this << ")";
    return new Cnt{value};
  }
};
template 
struct RefValue {
  using Type = T;
  static Type& make_ref(T* ptr) {
    return *ptr;
  }
  static const Type& make_const_ref(const T* ptr) {
    return *ptr;
  }
  static Type* make_ptr(T* ptr) {
    return ptr;
  }
  static const Type* make_const_ptr(const T* ptr) {
    return ptr;
  }
};
template 
struct RefValue> {
  using Type = T;
  static Type& make_ref(Cnt* ptr) {
    return **ptr;
  }
  static const Type& make_const_ref(const Cnt* ptr) {
    return **ptr;
  }
  static Type* make_ptr(Cnt* ptr) {
    return &(**ptr);
  }
  static const Type* make_const_ptr(const Cnt* ptr) {
    return &(**ptr);
  }
};
struct static_cast_ref {};
namespace detail {
void safe_delete(const CntObject* ptr);
}
template 
class Ref {
  T* ptr;
  template 
  friend class Ref;
 public:
  struct NullRef {};
  Ref() : ptr(0) {
  }
  //explicit Ref(bool init) : ptr(init ? new T : 0) {
  //}
  template 
  explicit Ref(bool init, Args&&... args) : ptr(0) {
    //assert(init);
    ptr = new T(std::forward(args)...);
  }
  /*
  explicit Ref(const T& c) : ptr(&c) {
    ptr.inc();
  }
  */
  explicit Ref(T* pc) : ptr(pc) {
    if (ptr) {
      acquire_shared(ptr);
    }
  }
  explicit Ref(const T* pc) : ptr(const_cast(pc)) {
    if (ptr) {
      acquire_shared(ptr);
    }
  }
  explicit Ref(const T& obj) : ptr(obj.make_copy()) {
  }
  Ref(const Ref& r) : ptr(r.ptr) {
    if (ptr) {
      acquire_shared(ptr);
      ///std::cout << "(rc+ " << (const void*)ptr << ")";
    }
  }
  Ref(Ref&& r) noexcept : ptr(std::move(r.ptr)) {
    r.ptr = 0;
  }
  T* release() {
    auto res = ptr;
    ptr = nullptr;
    return res;
  }
  struct acquire_t {};
  Ref(T* ptr, acquire_t) : ptr(ptr) {
  }
  template 
  Ref(const Ref& r, std::enable_if_t::value, int> t = 0) : ptr(static_cast(r.ptr)) {
    static_assert(std::is_base_of::value, "Invalid static Ref conversion");
    if (ptr) {
      acquire_shared(ptr);
    }
  }
  template 
  explicit Ref(const Ref& r,
               std::enable_if_t::value && std::is_base_of::value, int> t = 0)
      : ptr(dynamic_cast(r.ptr)) {
    static_assert(std::is_base_of::value, "Invalid dynamic Ref conversion");
    if (ptr) {
      acquire_shared(ptr);
      //std::cout << "(rv+ " << (const void*)ptr << ")";
    } else {
      //std::cout << "(error converting " << (const void*)r.ptr << ")";
    }
  }
  template 
  Ref(static_cast_ref, const Ref& r, std::enable_if_t::value, int> t = 0)
      : ptr(static_cast(r.ptr)) {
    static_assert(std::is_base_of::value, "Invalid static Ref downcast");
    if (r.ptr) {
      acquire_shared(ptr);
    } else {
      ptr = nullptr;
    }
  }
  template 
  Ref(Ref&& r, std::enable_if_t::value, int> t = 0) : ptr(static_cast(r.ptr)) {
    static_assert(std::is_base_of::value, "Invalid static Ref conversion");
    r.ptr = nullptr;
  }
  template 
  explicit Ref(Ref&& r, std::enable_if_t::value && std::is_base_of::value, int> t = 0)
      : ptr(dynamic_cast(r.ptr)) {
    static_assert(std::is_base_of::value, "Invalid dynamic Ref conversion");
    if (!ptr && r.ptr) {
      release_shared(r.ptr);
    }
    r.ptr = nullptr;
  }
  template 
  Ref(static_cast_ref, Ref&& r, std::enable_if_t::value, int> t = 0) noexcept
      : ptr(static_cast(r.ptr)) {
    static_assert(std::is_base_of::value, "Invalid static Ref downcast");
    if (r.ptr) {
      r.ptr = nullptr;
    } else {
      ptr = nullptr;
    }
  }
  ~Ref() {
    clear();
  }
  Ref& operator=(const Ref& r);
  template 
  Ref& operator=(const Ref& r);
  Ref& operator=(Ref&& r);
  template 
  Ref& operator=(Ref&& r);
  const typename RefValue::Type* operator->() const {
    if (!ptr) {
      CHECK(ptr && "deferencing null Ref");
      throw NullRef{};
    }
    return RefValue::make_const_ptr(ptr);
  }
  const typename RefValue::Type& operator*() const {
    if (!ptr) {
      CHECK(ptr && "deferencing null Ref");
      throw NullRef{};
    }
    return RefValue::make_const_ref(ptr);
  }
  const T* get() const {
    return ptr;
  }
  bool is_null() const {
    return ptr == 0;
  }
  bool not_null() const {
    return ptr != 0;
  }
  bool is_unique() const {
    if (!ptr) {
      CHECK(ptr && "defererencing null Ref");
      throw NullRef{};
    }
    return ptr->is_unique();
  }
  void clear() {
    if (ptr) {
      ///std::cout << "(r- " << (const void*)ptr << ")";
      release_shared(ptr);
      ptr = 0;
    }
  }
  void swap(Ref& r) {
    std::swap(ptr, r.ptr);
  }
  Ref& operator^=(const Ref& r);
  Ref& operator^=(Ref&& r);
  Ref& operator&=(bool retain);
  bool operator==(const Ref& r) const;
  bool operator!=(const Ref& r) const;
  typename RefValue::Type& write();
  typename RefValue::Type& unique_write() const;
 public:
  template 
  static void release_shared(S* obj, int cnt = 1) {
    if (obj->dec(cnt)) {
      detail::safe_delete(obj);
    }
  }
  template 
  static void acquire_shared(S* obj, int cnt = 1) {
    obj->inc(cnt);
  }
 private:
  void assign(T* p) {
    ptr = p;
    if (p) {
      acquire_shared(p);
      ///std::cout << "(r+ " << (const void*)ptr << ")";
    }
  }
};
template 
Ref make_ref(Args&&... args) {
  return Ref{true, std::forward(args)...};
}
template 
Ref> make_cnt_ref(Args&&... args) {
  return Ref>{true, std::forward(args)...};
}
template 
td::StringBuilder& operator<<(td::StringBuilder& sb, const Ref& ref) {
  if (ref.is_null()) {
    return sb << "nullptr";
  }
  return sb << *ref;
}
template 
Ref& Ref::operator=(const Ref& r) {
  if (ptr != r.ptr) {
    clear();
    assign(r.ptr);
  }
  return *this;
}
template 
template 
Ref& Ref::operator=(const Ref& r) {
  if (ptr != static_cast(r.ptr)) {
    clear();
    assign(r.ptr);
  }
  return *this;
}
template 
Ref& Ref::operator=(Ref&& r) {
  clear();
  ptr = r.ptr;
  r.ptr = 0;
  return *this;
}
template 
template 
Ref& Ref::operator=(Ref&& r) {
  clear();
  ptr = r.ptr;
  r.ptr = 0;
  return *this;
}
template 
typename RefValue::Type& Ref::write() {
  if (!ptr) {
    throw CntObject::WriteError();
  }
  if (!ptr->is_unique()) {
    T* copy = dynamic_cast(ptr->make_copy());
    if (!copy) {
      throw CntObject::WriteError();
    }
    release_shared(ptr);
    ptr = copy;
  }
  return RefValue::make_ref(ptr);
}
template 
typename RefValue::Type& Ref::unique_write() const {
  if (!ptr || !ptr->is_unique()) {
    throw CntObject::WriteError();
  }
  return RefValue::make_ref(ptr);
}
template 
Ref& Ref::operator^=(const Ref& r) {
  if (r.ptr && r.ptr != ptr) {
    clear();
    assign(r.ptr);
  }
  return *this;
}
template 
Ref& Ref::operator^=(Ref&& r) {
  if (r.ptr && r.ptr != ptr) {
    clear();
    ptr = r.ptr;
    r.ptr = 0;
  }
  return *this;
}
template 
Ref& Ref::operator&=(bool retain) {
  if (!retain && ptr) {
    clear();
  }
  return *this;
}
template 
bool Ref::operator==(const Ref& r) const {
  return ptr == r.ptr;
}
template 
bool Ref::operator!=(const Ref& r) const {
  return ptr != r.ptr;
}
template 
void swap(Ref& r1, Ref& r2) {
  r1.swap(r2);
}
int64 ref_get_delete_count();
}  // namespace td