// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_BIN_REFERENCE_COUNTING_H_
#define RUNTIME_BIN_REFERENCE_COUNTING_H_

#include <atomic>

#include "platform/assert.h"
#include "platform/globals.h"

namespace dart {
namespace bin {

// Forward declaration.
template <class Target>
class RefCntReleaseScope;

// Inherit from this class where instances of the derived class should be
// reference counted. Reference counts on instances are incremented and
// decremented explicitly with calls to Retain() and Release(). E.g.:
//
// class Foo : public ReferenceCounted<Foo> {
//  public:
//   Foo() : ReferenceCounted() {}
//   ...
// };
//
// void DoStuffWithAFoo() {
//   Foo* foo = new Foo();  // Reference count starts at 1, so no explicit
//                          // call to Retain is needed after allocation.
//   ...
//   foo->Release();
// }
template <class Derived>
class ReferenceCounted {
 public:
  ReferenceCounted() : ref_count_(1) {
#if defined(DEBUG)
    instances_.fetch_add(1u, std::memory_order_relaxed);
#endif  // defined(DEBUG)
  }

  virtual ~ReferenceCounted() {
    ASSERT(ref_count_ == 0);
#if defined(DEBUG)
    instances_.fetch_sub(1u, std::memory_order_relaxed);
#endif  // defined(DEBUG)
  }

  void Retain() {
    intptr_t old = ref_count_.fetch_add(1u, std::memory_order_relaxed);
    ASSERT(old > 0);
  }

  void Release() {
    intptr_t old = ref_count_.fetch_sub(1u, std::memory_order_acq_rel);
    ASSERT(old > 0);
    if (old == 1) {
      delete static_cast<Derived*>(this);
    }
  }

#if defined(DEBUG)
  static intptr_t instances() {
    return instances_.load(std::memory_order_relaxed);
  }
#endif  // defined(DEBUG)

 private:
#if defined(DEBUG)
  static std::atomic<intptr_t> instances_;
#endif  // defined(DEBUG)

  std::atomic<intptr_t> ref_count_;

  // These are used only in the ASSERT below in RefCntReleaseScope.
  intptr_t ref_count() const {
    return ref_count_.load(std::memory_order_relaxed);
  }
  friend class RefCntReleaseScope<Derived>;
  DISALLOW_COPY_AND_ASSIGN(ReferenceCounted);
};

#if defined(DEBUG)
template <class Derived>
std::atomic<intptr_t> ReferenceCounted<Derived>::instances_ = {0};
#endif

// Creates a scope at the end of which a reference counted object is
// Released. This is useful for reference counted objects received by the IO
// Service, which have already been Retained E.g.:
//
// CObject* Foo::FooRequest(const CObjectArray& request) {
//   Foo* foo = CObjectToFoo(request[0]);
//   RefCntReleaseScope<Foo> rs(foo);
//   ...
// }
template <class Target>
class RefCntReleaseScope {
 public:
  explicit RefCntReleaseScope(ReferenceCounted<Target>* t) : target_(t) {
    ASSERT(target_ != nullptr);
    ASSERT(target_->ref_count() > 0);
  }
  ~RefCntReleaseScope() { target_->Release(); }

 private:
  ReferenceCounted<Target>* target_;

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(RefCntReleaseScope);
};

// Instances of RetainedPointer manage Retaining and Releasing reference counted
// objects. There are two ways to use it. First, it can be used as a field in
// a class, e.g.:
//
// class Foo {
//  private:
//   RetainedPointer<Bar> bar_;
//  public:
//   explicit Foo(Bar* b) : bar_(b) {}
// }
//
// In this case, b will be Retained in Foo's constructor, and Released
// automatically during Foo's destructor.
//
// RetainedPointer can also be used as a scope, as with RefCntReleaseScope,
// with the difference that entering the scope also Retains the pointer, e.g.:
//
// void RetainAndDoStuffWithFoo(Foo* foo) {
//   RetainedPointer<Foo> retained(foo);
//   ..
// }
//
// This Retains foo on entry and Releases foo at every exit from the scope.
//
// The underlying pointer can be accessed with the get() and set() methods.
// Overwriting a non-null pointer with set causes that pointer to be Released.
template <class Target>
class RetainedPointer {
 public:
  RetainedPointer() : target_(nullptr) {}

  explicit RetainedPointer(ReferenceCounted<Target>* t) : target_(t) {
    if (target_ != nullptr) {
      target_->Retain();
    }
  }

  ~RetainedPointer() {
    if (target_ != nullptr) {
      target_->Release();
    }
  }

  void set(ReferenceCounted<Target>* t) {
    if (target_ != nullptr) {
      target_->Release();
    }
    target_ = t;
    if (target_ != nullptr) {
      target_->Retain();
    }
  }

  Target* get() const { return static_cast<Target*>(target_); }

 private:
  ReferenceCounted<Target>* target_;

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(RetainedPointer);
};

}  // namespace bin
}  // namespace dart

#endif  // RUNTIME_BIN_REFERENCE_COUNTING_H_
