/* 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 XPCOM_THREADS_STATEWATCHING_H_
#define XPCOM_THREADS_STATEWATCHING_H_

#include <cstddef>
#include <new>
#include <utility>
#include "mozilla/AbstractThread.h"
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/RefPtr.h"
#include "nsCycleCollectionNoteChild.h"
#include "nsISupports.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"

/*
 * The state-watching machinery automates the process of responding to changes
 * in various pieces of state.
 *
 * A standard programming pattern is as follows:
 *
 * mFoo = ...;
 * NotifyStuffChanged();
 * ...
 * mBar = ...;
 * NotifyStuffChanged();
 *
 * This pattern is error-prone and difficult to audit because it requires the
 * programmer to manually trigger the update routine. This can be especially
 * problematic when the update routine depends on numerous pieces of state, and
 * when that state is modified across a variety of helper methods. In these
 * cases the responsibility for invoking the routine is often unclear, causing
 * developers to scatter calls to it like pixie dust. This can result in
 * duplicate invocations (which is wasteful) and missing invocations in corner-
 * cases (which is a source of bugs).
 *
 * This file provides a set of primitives that automatically handle updates and
 * allow the programmers to explicitly construct a graph of state dependencies.
 * When used correctly, it eliminates the guess-work and wasted cycles described
 * above.
 *
 * There are two basic pieces:
 *   (1) Objects that can be watched for updates. These inherit WatchTarget.
 *   (2) Objects that receive objects and trigger processing. These inherit
 *       AbstractWatcher. In the current machinery, these exist only internally
 *       within the WatchManager, though that could change.
 *
 * Note that none of this machinery is thread-safe - it must all happen on the
 * same owning thread. To solve multi-threaded use-cases, use state mirroring
 * and watch the mirrored value.
 *
 * Given that semantics may change and comments tend to go out of date, we
 * deliberately don't provide usage examples here. Grep around to find them.
 */

namespace mozilla {

extern LazyLogModule gStateWatchingLog;

#define WATCH_LOG(x, ...) \
  MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__))

/*
 * AbstractWatcher is a superclass from which all watchers must inherit.
 */
class AbstractWatcher {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
  AbstractWatcher() : mDestroyed(false) {}
  bool IsDestroyed() { return mDestroyed; }
  virtual void Notify() = 0;

 protected:
  virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
  bool mDestroyed;
};

/*
 * WatchTarget is a superclass from which all watchable things must inherit.
 * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
 * needs only to invoke NotifyWatchers when something changes.
 *
 * The functionality that this class provides is not threadsafe, and should only
 * be used on the thread that owns that WatchTarget.
 */
class WatchTarget {
 public:
  explicit WatchTarget(const char* aName) : mName(aName) {}

  void AddWatcher(AbstractWatcher* aWatcher) {
    MOZ_ASSERT(!mWatchers.Contains(aWatcher));
    mWatchers.AppendElement(aWatcher);
  }

  void RemoveWatcher(AbstractWatcher* aWatcher) {
    MOZ_ASSERT(mWatchers.Contains(aWatcher));
    mWatchers.RemoveElement(aWatcher);
  }

 protected:
  void NotifyWatchers() {
    WATCH_LOG("%s[%p] notifying watchers\n", mName, this);
    PruneWatchers();
    for (size_t i = 0; i < mWatchers.Length(); ++i) {
      mWatchers[i]->Notify();
    }
  }

 private:
  // We don't have Watchers explicitly unregister themselves when they die,
  // because then they'd need back-references to all the WatchTargets they're
  // subscribed to, and WatchTargets aren't reference-counted. So instead we
  // just prune dead ones at appropriate times, which works just fine.
  void PruneWatchers() {
    mWatchers.RemoveElementsBy(
        [](const auto& watcher) { return watcher->IsDestroyed(); });
  }

  nsTArray<RefPtr<AbstractWatcher>> mWatchers;

 protected:
  const char* mName;
};

/*
 * Watchable is a wrapper class that turns any primitive into a WatchTarget.
 */
template <typename T>
class Watchable : public WatchTarget {
 public:
  Watchable(const T& aInitialValue, const char* aName)
      : WatchTarget(aName), mValue(aInitialValue) {}

  const T& Ref() const { return mValue; }
  operator const T&() const { return Ref(); }
  template <typename U>
  Watchable& operator=(U&& aNewValue) {
    if (aNewValue != mValue) {
      mValue = std::forward<U>(aNewValue);
      NotifyWatchers();
    }

    return *this;
  }

 private:
  Watchable(const Watchable& aOther) = delete;
  Watchable& operator=(const Watchable& aOther) = delete;

  T mValue;
};

template <typename T>
inline void ImplCycleCollectionUnlink(Watchable<T>& aField) {
  ImplCycleCollectionUnlink<T>(aField);
}

template <typename T>
inline void ImplCycleCollectionTraverse(
    nsCycleCollectionTraversalCallback& aCallback, const Watchable<T>& aField,
    const char* aName, uint32_t aFlags = 0) {
  ImplCycleCollectionTraverse(aCallback, aField.Ref(), aName, aFlags);
}

// Manager class for state-watching. Declare one of these in any class for which
// you want to invoke method callbacks.
//
// Internally, WatchManager maintains one AbstractWatcher per callback method.
// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
// This causes an AbstractWatcher for |Callback| to be instantiated if it
// doesn't already exist, and registers it with |WatchTarget|.
//
// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
// watch callbacks no more than once per task, once all other operations for
// that task have been completed.
//
// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType|
// objects. Given that, it and its owned objects can't hold permanent strong
// refs to the owner, since that would keep the owner alive indefinitely.
// Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire.
// This ensures that everything is kept alive just long enough.
template <typename OwnerType>
class WatchManager {
 public:
  typedef void (OwnerType::*CallbackMethod)();
  explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
      : mOwner(aOwner), mOwnerThread(aOwnerThread) {}

  ~WatchManager() {
    if (!IsShutdown()) {
      Shutdown();
    }
  }

  bool IsShutdown() const { return !mOwner; }

  // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
  // destroyed on a different thread, Shutdown() must be called manually.
  void Shutdown() {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    for (auto& watcher : mWatchers) {
      watcher->Destroy();
    }
    mWatchers.Clear();
    mOwner = nullptr;
  }

  void Watch(WatchTarget& aTarget, CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    aTarget.AddWatcher(&EnsureWatcher(aMethod));
  }

  void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    MOZ_ASSERT(watcher);
    aTarget.RemoveWatcher(watcher);
  }

  void ManualNotify(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    MOZ_ASSERT(watcher);
    watcher->Notify();
  }

 private:
  class PerCallbackWatcher : public AbstractWatcher {
   public:
    PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread,
                       CallbackMethod aMethod)
        : mOwner(aOwner),
          mOwnerThread(aOwnerThread),
          mCallbackMethod(aMethod) {}

    void Destroy() {
      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
      mDestroyed = true;
      mOwner = nullptr;
    }

    void Notify() override {
      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
      MOZ_DIAGNOSTIC_ASSERT(mOwner,
                            "mOwner is only null after destruction, "
                            "at which point we shouldn't be notified");
      if (mNotificationPending) {
        // We've already got a notification job in the pipe.
        return;
      }
      mNotificationPending = true;

      // Queue up our notification jobs to run in a stable state.
      AbstractThread::DispatchDirectTask(
          NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify",
                                 [self = RefPtr<PerCallbackWatcher>(this),
                                  owner = RefPtr<OwnerType>(mOwner)]() {
                                   if (!self->mDestroyed) {
                                     ((*owner).*(self->mCallbackMethod))();
                                   }
                                   self->mNotificationPending = false;
                                 }));
    }

    bool CallbackMethodIs(CallbackMethod aMethod) const {
      return mCallbackMethod == aMethod;
    }

   private:
    ~PerCallbackWatcher() = default;

    OwnerType* mOwner;  // Never null.
    bool mNotificationPending = false;
    RefPtr<AbstractThread> mOwnerThread;
    CallbackMethod mCallbackMethod;
  };

  PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    for (auto& watcher : mWatchers) {
      if (watcher->CallbackMethodIs(aMethod)) {
        return watcher;
      }
    }
    return nullptr;
  }

  PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) {
    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
    PerCallbackWatcher* watcher = GetWatcher(aMethod);
    if (watcher) {
      return *watcher;
    }
    watcher = mWatchers
                  .AppendElement(MakeAndAddRef<PerCallbackWatcher>(
                      mOwner, mOwnerThread, aMethod))
                  ->get();
    return *watcher;
  }

  nsTArray<RefPtr<PerCallbackWatcher>> mWatchers;
  OwnerType* mOwner;
  RefPtr<AbstractThread> mOwnerThread;
};

#undef WATCH_LOG

}  // namespace mozilla

#endif
