//===-- include/flang/Common/reference-counted.h ----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_COMMON_REFERENCE_COUNTED_H_
#define FORTRAN_COMMON_REFERENCE_COUNTED_H_

// A class template of smart pointers to objects with their own
// reference counting object lifetimes that's lighter weight
// than std::shared_ptr<>.  Not thread-safe.

namespace Fortran::common {

// A base class for reference-counted objects.  Must be public.
template <typename A> class ReferenceCounted {
public:
  ReferenceCounted() {}
  void TakeReference() { ++references_; }
  void DropReference() {
    if (--references_ == 0) {
      delete static_cast<A *>(this);
    }
  }

private:
  int references_{0};
};

// A reference to a reference-counted object.
template <typename A> class CountedReference {
public:
  using type = A;
  CountedReference() {}
  CountedReference(type *m) : p_{m} { Take(); }
  CountedReference(const CountedReference &c) : p_{c.p_} { Take(); }
  CountedReference(CountedReference &&c) : p_{c.p_} { c.p_ = nullptr; }
  CountedReference &operator=(const CountedReference &c) {
    c.Take();
    Drop();
    p_ = c.p_;
    return *this;
  }
  CountedReference &operator=(CountedReference &&c) {
    A *p{c.p_};
    c.p_ = nullptr;
    Drop();
    p_ = p;
    return *this;
  }
  ~CountedReference() { Drop(); }
  operator bool() const { return p_ != nullptr; }
  type *get() const { return p_; }
  type &operator*() const { return *p_; }
  type *operator->() const { return p_; }

private:
  void Take() const {
    if (p_) {
      p_->TakeReference();
    }
  }
  void Drop() {
    if (p_) {
      p_->DropReference();
      p_ = nullptr;
    }
  }

  type *p_{nullptr};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_REFERENCE_COUNTED_H_
