// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * Copyright (C) 2015, 2017, 2024 Kernkonzept GmbH.
 * Author(s): Sarah Hoffmann <sarah.hoffmann@kernkonzept.com>
 *            Alexander Warg <alexander.warg@kernkonzept.com>
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */
#pragma once

#include "hlist"

namespace cxx {

/**
 * Generic (base) weak reference to some object.
 *
 * A weak reference is a reference that gets reset to NULL when the object
 * shall be deleted.  All weak references to the same object are kept in a
 * linked list of weak references.
 *
 * For typed weak references see `cxx::Weak_ref`.
 */
class Weak_ref_base : public H_list_item_t<Weak_ref_base>
{
protected:
  Weak_ref_base(void const *ptr = nullptr) : _obj(ptr) {}
  void reset_hard() { _obj = nullptr; }
  void const *_obj;

public:
  /**
   * The list type for keeping all weak references to an object.
   *
   * On destruction of a list, all weak references to the respective object are
   * set to `nullptr`.
   */
  struct List : H_list_t<Weak_ref_base>
  {
    void reset()
    {
      while (!empty())
        pop_front()->reset_hard();
    }

    ~List()
    { reset(); }
  };

  explicit operator bool () const
  { return _obj ? true : false; }
};


/**
 * Typed weak reference to an object of type `T`.
 *
 * \tparam T  The type of the referenced object.
 *
 * A weak reference is a reference that is invalidated when the referenced
 * object is about to be deleted.  All weak references to an object are kept in
 * a linked list (see Weak_ref_base::List) and all the weak references are
 * iterated and reset by the Weak_ref_base::List destructor or
 * Weak_ref_base::List::reset().
 *
 * The type `T` must provide two methods that handle the housekeeping of weak
 * references: `remove_weak_ref(Weak_ref_base *)` and
 * `add_weak_ref(Weak_ref_base *)`. These functions must handle the insertion
 * and removal of the weak reference into the respective Weak_ref_base::List
 * object.  For convenience one can use the cxx::Weak_ref_obj as a base class
 * that handles weak references for you.
 *
 * For example:
 * ```{.cpp}
 * class C : public cxx::Weak_ref_obj {};
 *
 * int main()
 * {
 *   cxx::Weak_ref<C> r;  // r is nullptr
 *   {
 *     C c;
 *     r = &c;  // now r points to c
 *   }  // c is destructed, which implies resetting all weak references to c
 *   // now r is nullptr
 *   return 0;
 * }
 * ```
 *
 * \note Weak references have no effect on the lifetime of the referenced
 *       object. Hence, a referenced object is *not* deleted when all weak
 *       references for it are gone. If automatic deletion is needed, see
 *       cxx::Ref_ptr.
 */
template <typename T>
class Weak_ref : public Weak_ref_base
{
public:
  T *get() const
  { return reinterpret_cast<T*>(const_cast<void *>(_obj)); }

  T *reset(T *n)
  {
    T *r = get();
    if (r)
      r->remove_weak_ref(this);

    _obj = n;
    if (n)
      n->add_weak_ref(this);

    return r;
  }

  Weak_ref(T *s = nullptr) : Weak_ref_base(s)
  {
    if (s)
      s->add_weak_ref(this);
  }

  ~Weak_ref() { reset(0); }

  void operator = (T *n)
  { reset(n); }

  Weak_ref(Weak_ref const &o) : Weak_ref_base(o._obj)
  {
    if (T *x = get())
      x->add_weak_ref(this);
  }

  Weak_ref &operator = (Weak_ref const &o)
  {
    if (&o == this)
      return *this;

    reset(o.get());
    return *this;
  }

  T &operator * () const { return get(); }
  T *operator -> () const { return get(); }
};

class Weak_ref_obj
{
protected:
  template <typename T> friend class Weak_ref;
  mutable Weak_ref_base::List weak_references;

  void add_weak_ref(Weak_ref_base *ref) const
  { weak_references.push_front(ref); }

  void remove_weak_ref(Weak_ref_base *ref) const
  { weak_references.remove(ref); }
};

}
