/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsMaybeWeakPtr_h_
#define nsMaybeWeakPtr_h_

#include "mozilla/Try.h"
#include "mozilla/Variant.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionNoteChild.h"
#include "nsIWeakReferenceUtils.h"
#include "nsTArray.h"
#include "xpcpublic.h"

// nsMaybeWeakPtr is a helper object to hold a strong-or-weak reference
// to the template class.  It's pretty minimal, but sufficient.

template <class T>
class nsMaybeWeakPtr {
 public:
  nsMaybeWeakPtr() : mPtr(mozilla::VariantType<nsCOMPtr<T>>{}, nullptr) {}
  explicit nsMaybeWeakPtr(std::nullptr_t)
      : mPtr(mozilla::VariantType<nsCOMPtr<T>>{}, nullptr) {}
  explicit nsMaybeWeakPtr(T* aRef)
      : mPtr(mozilla::VariantType<nsCOMPtr<T>>{}, aRef) {}
  explicit nsMaybeWeakPtr(const nsWeakPtr& aRef)
      : mPtr(mozilla::VariantType<nsWeakPtr>{}, aRef) {
    MOZ_ASSERT(AsWeak(), "null nsWeakPtr pointer passed to nsMaybeWeakPtr");
  }

  bool IsStrong() const { return mPtr.template is<nsCOMPtr<T>>(); }

  bool IsWeak() const { return mPtr.template is<nsWeakPtr>(); }

  nsMaybeWeakPtr& operator=(std::nullptr_t) {
    mPtr.template emplace<nsCOMPtr<T>>(nullptr);
    return *this;
  }

  nsMaybeWeakPtr<T>& operator=(T* aRef) {
    mPtr.template emplace<nsCOMPtr<T>>(aRef);
    return *this;
  }

  nsMaybeWeakPtr<T>& operator=(const nsWeakPtr& aRef) {
    mPtr.template emplace<nsWeakPtr>(aRef);
    return *this;
  }

  bool operator==(const nsMaybeWeakPtr<T>& aOther) const {
    return mPtr == aOther.mPtr;
  }

  bool operator==(T* const& aStrong) const {
    return IsStrong() && AsStrong() == aStrong;
  }

  bool operator==(const nsWeakPtr& aWeak) const {
    return IsWeak() && AsWeak() == aWeak;
  }

  already_AddRefed<T> GetValue() const {
    return IsWeak() ? GetWeakReferent() : nsCOMPtr<T>{AsStrong()}.forget();
  }

  friend inline void ImplCycleCollectionTraverse(
      nsCycleCollectionTraversalCallback& aCallback,
      const nsMaybeWeakPtr& aField, const char* aName, uint32_t aFlags = 0) {
    if (aField.IsStrong()) {
      CycleCollectionNoteChild(aCallback, aField.AsStrong().get(), aName,
                               aFlags);
    }
  }

 protected:
  const nsCOMPtr<T>& AsStrong() const {
    return mPtr.template as<nsCOMPtr<T>>();
  }

  const nsWeakPtr& AsWeak() const { return mPtr.template as<nsWeakPtr>(); }

 private:
  already_AddRefed<T> GetWeakReferent() const {
    MOZ_ASSERT(AsWeak());
    nsresult rv;
    nsCOMPtr<T> ref = do_QueryReferent(AsWeak(), &rv);
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_ERROR_NULL_POINTER,
                         "QueryReferent failed with non-null pointer");
    return ref.forget();
  }

  mozilla::Variant<nsCOMPtr<T>, nsWeakPtr> mPtr;
};

// nsMaybeWeakPtrArray is an array of MaybeWeakPtr objects, that knows how to
// grab a weak reference to a given object if requested.  It only allows a
// given object to appear in the array once.

template <class T>
class nsMaybeWeakPtrArray : public CopyableTArray<nsMaybeWeakPtr<T>> {
  using MaybeWeakArray = nsTArray<nsMaybeWeakPtr<T>>;

  nsresult SetMaybeWeakPtr(nsMaybeWeakPtr<T>& aRef, T* aElement,
                           bool aOwnsWeak) {
    nsresult rv = NS_OK;

    if (aOwnsWeak) {
      aRef = do_GetWeakReference(aElement, &rv);
    } else {
      aRef = aElement;
    }

    return rv;
  }

 public:
  nsresult AppendWeakElement(T* aElement, bool aOwnsWeak) {
    nsMaybeWeakPtr<T> ref;
    MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak));

#if (defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(MOZ_THUNDERBIRD))
    // Checking for duplicates is expensive, so we enforce callers to avoid
    // this with a diagnostic assertion. See bug 2000788 for Thunderbird.
    if (IsAssertOnDoubleAdd()) {
      if (MaybeWeakArray::Contains(aElement)) {
        xpc_DumpJSStack(true, true, false);
        MOZ_DIAGNOSTIC_ASSERT(false, "Element already in array.");
      }
    }
#endif

    MaybeWeakArray::AppendElement(ref);
    return NS_OK;
  }

  nsresult AppendWeakElementUnlessExists(T* aElement, bool aOwnsWeak) {
    nsMaybeWeakPtr<T> ref;
    MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak));

    if (MaybeWeakArray::Contains(ref)) {
      return NS_ERROR_INVALID_ARG;
    }

    MaybeWeakArray::AppendElement(ref);
    return NS_OK;
  }

  nsresult RemoveWeakElement(T* aElement) {
    if (MaybeWeakArray::RemoveElement(aElement)) {
      return NS_OK;
    }

    // Don't use do_GetWeakReference; it should only be called if we know
    // the object supports weak references.
    nsCOMPtr<nsISupportsWeakReference> supWeakRef = do_QueryInterface(aElement);
    if (!supWeakRef) {
      return NS_ERROR_INVALID_ARG;
    }

    nsCOMPtr<nsIWeakReference> weakRef;
    nsresult rv = supWeakRef->GetWeakReference(getter_AddRefs(weakRef));
    NS_ENSURE_SUCCESS(rv, rv);

    if (MaybeWeakArray::RemoveElement(weakRef)) {
      return NS_OK;
    }

    return NS_ERROR_INVALID_ARG;
  }

  friend inline void ImplCycleCollectionUnlink(nsMaybeWeakPtrArray& aField) {
    aField.Clear();
  }

  friend inline void ImplCycleCollectionTraverse(
      nsCycleCollectionTraversalCallback& aCallback,
      nsMaybeWeakPtrArray& aField, const char* aName, uint32_t aFlags = 0) {
    aFlags |= CycleCollectionEdgeNameArrayFlag;
    for (auto& e : aField) {
      ImplCycleCollectionTraverse(aCallback, e, aName, aFlags);
    }
  }

 private:
#if (defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) && !defined(MOZ_THUNDERBIRD))
  // Until we have bug 2005466, a plain diagnostic assert would only yield us
  // unactionable crash reports from official Nightly builds in case JS was
  // adding an observer (which is common enough). But we want developers using
  // non-DEBUG builds to catch this locally and on CI when testing.
  static inline bool IsAssertOnDoubleAdd() {
#  if defined(DEBUG) || defined(FUZZING)
    return true;
#  else
    return xpc::IsInAutomation();
#  endif
  }
#endif
};

// Call a method on each element in the array, but only if the element is
// non-null.

#define ENUMERATE_WEAKARRAY(array, type, method)                            \
  for (uint32_t array_idx = 0; array_idx < (array).Length(); ++array_idx) { \
    const nsCOMPtr<type>& e = (array).ElementAt(array_idx).GetValue();      \
    if (e) e->method;                                                       \
  }

#endif
