/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 mozilla_dom_MaybeDiscarded_h
#define mozilla_dom_MaybeDiscarded_h

#include "mozilla/RefPtr.h"

namespace mozilla::dom {

// Wrapper type for a WindowContext or BrowsingContext instance which may be
// discarded, and thus unavailable in the current process. This type is used to
// pass WindowContext and BrowsingContext instances over IPC, as they may be
// discarded in the receiving process.
//
// A MaybeDiscarded can generally be implicitly converted to from a
// BrowsingContext* or WindowContext*, but requires an explicit check of
// |IsDiscarded| and call to |get| to read from.
template <typename T>
class MaybeDiscarded {
 public:
  MaybeDiscarded() = default;
  MaybeDiscarded(MaybeDiscarded<T>&&) = default;
  MaybeDiscarded(const MaybeDiscarded<T>&) = default;

  // Construct from raw pointers and |nullptr|.
  MOZ_IMPLICIT MaybeDiscarded(T* aRawPtr)
      : mId(aRawPtr ? aRawPtr->Id() : 0), mPtr(aRawPtr) {}
  MOZ_IMPLICIT MaybeDiscarded(decltype(nullptr)) {}

  // Construct from |RefPtr<I>|
  template <typename I,
            typename = std::enable_if_t<std::is_convertible_v<I*, T*>>>
  MOZ_IMPLICIT MaybeDiscarded(RefPtr<I>&& aPtr)
      : mId(aPtr ? aPtr->Id() : 0), mPtr(std::move(aPtr)) {}
  template <typename I,
            typename = std::enable_if_t<std::is_convertible_v<I*, T*>>>
  MOZ_IMPLICIT MaybeDiscarded(const RefPtr<I>& aPtr)
      : mId(aPtr ? aPtr->Id() : 0), mPtr(aPtr) {}

  // Basic assignment operators.
  MaybeDiscarded<T>& operator=(const MaybeDiscarded<T>&) = default;
  MaybeDiscarded<T>& operator=(MaybeDiscarded<T>&&) = default;
  MaybeDiscarded<T>& operator=(decltype(nullptr)) {
    mId = 0;
    mPtr = nullptr;
    return *this;
  }
  MaybeDiscarded<T>& operator=(T* aRawPtr) {
    mId = aRawPtr ? aRawPtr->Id() : 0;
    mPtr = aRawPtr;
    return *this;
  }
  template <typename I>
  MaybeDiscarded<T>& operator=(const RefPtr<I>& aRhs) {
    mId = aRhs ? aRhs->Id() : 0;
    mPtr = aRhs;
    return *this;
  }
  template <typename I>
  MaybeDiscarded<T>& operator=(RefPtr<I>&& aRhs) {
    mId = aRhs ? aRhs->Id() : 0;
    mPtr = std::move(aRhs);
    return *this;
  }

  // Validate that the value is neither discarded nor null.
  bool IsNullOrDiscarded() const { return !mPtr || mPtr->IsDiscarded(); }
  bool IsDiscarded() const { return IsNullOrDiscarded() && !IsNull(); }
  bool IsNull() const { return mId == 0; }

  explicit operator bool() const { return !IsNullOrDiscarded(); }

  // Extract the wrapped |T|. Must not be called on a discarded |T|.
  T* get() const {
    MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded());
    return mPtr.get();
  }
  already_AddRefed<T> forget() {
    MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded());
    return mPtr.forget();
  }

  T* operator->() const {
    MOZ_ASSERT(!IsNull());
    return get();
  }

  // Like "get", but gets the "Canonical" version of the type. This method may
  // only be called in the parent process.
  auto get_canonical() const -> decltype(get()->Canonical()) {
    if (get()) {
      return get()->Canonical();
    } else {
      return nullptr;
    }
  }

  // The ID for the context wrapped by this MaybeDiscarded. This ID comes from a
  // remote process, and should generally only be used for logging. A
  // BrowsingContext with this ID may not exist in the current process.
  uint64_t ContextId() const { return mId; }

  // Tries to get the wrapped value, disregarding discarded status.
  // This may return |nullptr| for a non-null |MaybeDiscarded|, in the case that
  // the target is no longer available in this process.
  T* GetMaybeDiscarded() const { return mPtr.get(); }

  // Clear the value to a discarded state with the given ID.
  void SetDiscarded(uint64_t aId) {
    mId = aId;
    mPtr = nullptr;
  }

  // Comparison operators required by IPDL
  bool operator==(const MaybeDiscarded<T>& aRhs) const {
    return mId == aRhs.mId && mPtr == aRhs.mPtr;
  }
  bool operator!=(const MaybeDiscarded<T>& aRhs) const {
    return !operator==(aRhs);
  }

 private:
  uint64_t mId = 0;
  RefPtr<T> mPtr;
};

}  // namespace mozilla::dom

#endif  // mozilla_dom_MaybeDiscarded_h
