//===-- include/flang/Common/indirection.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_INDIRECTION_H_
#define FORTRAN_COMMON_INDIRECTION_H_

// Define a smart pointer class template that is rather like
// non-nullable std::unique_ptr<>.  Indirection<> is, like a C++ reference
// type, restricted to be non-null when constructed or assigned.
// Indirection<> optionally supports copy construction and copy assignment.
//
// To use Indirection<> with forward-referenced types, add
//    extern template class Fortran::common::Indirection<FORWARD_TYPE>;
// outside any namespace in a header before use, and
//    template class Fortran::common::Indirection<FORWARD_TYPE>;
// in one C++ source file later where a definition of the type is visible.

#include "idioms.h"
#include <memory>
#include <type_traits>
#include <utility>

namespace Fortran::common {

// The default case does not support (deep) copy construction or assignment.
template <typename A, bool COPY = false> class Indirection {
public:
  using element_type = A;
  Indirection() = delete;
  Indirection(A *&&p) : p_{p} {
    CHECK(p_ && "assigning null pointer to Indirection");
    p = nullptr;
  }
  Indirection(A &&x) : p_{new A(std::move(x))} {}
  Indirection(Indirection &&that) : p_{that.p_} {
    CHECK(p_ && "move construction of Indirection from null Indirection");
    that.p_ = nullptr;
  }
  ~Indirection() {
    delete p_;
    p_ = nullptr;
  }
  Indirection &operator=(Indirection &&that) {
    CHECK(that.p_ && "move assignment of null Indirection to Indirection");
    auto tmp{p_};
    p_ = that.p_;
    that.p_ = tmp;
    return *this;
  }

  A &value() { return *p_; }
  const A &value() const { return *p_; }

  bool operator==(const A &that) const { return *p_ == that; }
  bool operator==(const Indirection &that) const { return *p_ == *that.p_; }

  template <typename... ARGS>
  static common::IfNoLvalue<Indirection, ARGS...> Make(ARGS &&...args) {
    return {new A(std::move(args)...)};
  }

private:
  A *p_{nullptr};
};

// Variant with copy construction and assignment
template <typename A> class Indirection<A, true> {
public:
  using element_type = A;

  Indirection() = delete;
  Indirection(A *&&p) : p_{p} {
    CHECK(p_ && "assigning null pointer to Indirection");
    p = nullptr;
  }
  Indirection(const A &x) : p_{new A(x)} {}
  Indirection(A &&x) : p_{new A(std::move(x))} {}
  Indirection(const Indirection &that) {
    CHECK(that.p_ && "copy construction of Indirection from null Indirection");
    p_ = new A(*that.p_);
  }
  Indirection(Indirection &&that) : p_{that.p_} {
    CHECK(p_ && "move construction of Indirection from null Indirection");
    that.p_ = nullptr;
  }
  ~Indirection() {
    delete p_;
    p_ = nullptr;
  }
  Indirection &operator=(const Indirection &that) {
    CHECK(that.p_ && "copy assignment of Indirection from null Indirection");
    *p_ = *that.p_;
    return *this;
  }
  Indirection &operator=(Indirection &&that) {
    CHECK(that.p_ && "move assignment of null Indirection to Indirection");
    auto tmp{p_};
    p_ = that.p_;
    that.p_ = tmp;
    return *this;
  }

  A &value() { return *p_; }
  const A &value() const { return *p_; }

  bool operator==(const A &that) const { return *p_ == that; }
  bool operator==(const Indirection &that) const { return *p_ == *that.p_; }

  template <typename... ARGS>
  static common::IfNoLvalue<Indirection, ARGS...> Make(ARGS &&...args) {
    return {new A(std::move(args)...)};
  }

private:
  A *p_{nullptr};
};

template <typename A> using CopyableIndirection = Indirection<A, true>;

// A variation of std::unique_ptr<> with a reified deletion routine.
// Used to avoid dependence cycles between shared libraries.
template <typename A> class ForwardOwningPointer {
public:
  ForwardOwningPointer() {}
  ForwardOwningPointer(A *p, void (*del)(A *)) : p_{p}, deleter_{del} {}
  ForwardOwningPointer(ForwardOwningPointer &&that)
      : p_{that.p_}, deleter_{that.deleter_} {
    that.p_ = nullptr;
  }
  ForwardOwningPointer &operator=(ForwardOwningPointer &&that) {
    p_ = that.p_;
    that.p_ = nullptr;
    deleter_ = that.deleter_;
    return *this;
  }
  ~ForwardOwningPointer() {
    if (p_) {
      deleter_(p_);
    }
  }

  A &operator*() const { return *p_; }
  A *operator->() const { return p_; }
  operator bool() const { return p_ != nullptr; }
  A *get() { return p_; }
  A *release() {
    A *result{p_};
    p_ = nullptr;
    return result;
  }

  void Reset(A *p, void (*del)(A *)) {
    if (p_) {
      deleter_(p_);
    }
    p_ = p;
    deleter_ = del;
  }

private:
  A *p_{nullptr};
  void (*deleter_)(A *){nullptr};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_INDIRECTION_H_
