/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <bitset>
#include <vector>

#include <glog/logging.h>
#include <folly/ConstructorCallbackList.h>
#include <folly/Function.h>
#include <folly/Optional.h>
#include <folly/ScopeGuard.h>
#include <folly/io/async/DestructorCheck.h>
#include <folly/small_vector.h>

/**
 * Tooling that makes it easier to design observable objects and observers.
 */

namespace folly {

/**
 * Interface for store of pointers to observers.
 */
template <typename Observer>
class ObserverContainerStoreBase {
 public:
  using observer_type = Observer;

  // ObserverContainerStore stores shared_ptr<Observer> objects.
  //
  // To support observer objects that are NOT managed by a shared_ptr, the
  // encapsulating ObserverContainer wraps unmanaged pointers inside of
  // shared_ptrs, but sets an empty deleter for the shared_ptr, so that the
  // pointer remains unmanaged.
  //
  // As a result, it is possible for the shared_ptrs maintained by the store to
  // be "unmanaged". The type alias `MaybeManagedObserverPointer` is used to
  // ensure that this detail is apparent in other parts of the container code.
  using MaybeManagedObserverPointer = std::shared_ptr<Observer>;

  virtual ~ObserverContainerStoreBase() = default;

  /**
   * Add an observer pointer to the store.
   *
   * @param observer     Observer to add.
   * @return             Whether observer was added (not already present).
   */
  virtual bool add(MaybeManagedObserverPointer observer) = 0;

  /**
   * Remove an observer pointer from the store.
   *
   * @param observer     Observer to remove.
   * @return             Whether observer found and removed from store.
   */
  virtual bool remove(MaybeManagedObserverPointer observer) = 0;

  /**
   * Get number of observers in store.
   *
   * If called while the store is being iterated, the returned value may not
   * reflect changes that occurred (e.g., observers added or removed) during
   * iteration.
   *
   * @return             Number of observers in store.
   */
  virtual size_t size() const = 0;

  /**
   * Policy that determines how invokeForEachObserver handles mutations.
   */
  enum class InvokeWhileIteratingPolicy {
    InvokeAdded, // if observer added, invoke fn for it
    DoNotInvokeAdded, // if observer added, do not invoke fn for it
    CheckNoChange, // observers must not be added or removed during iteration
    CheckNoAdded // observers must not be added during iteration
  };

  /**
   * Invoke function for each observer in the store.
   *
   * @param fn           Function to call for each observer in store.
   * @param policy       InvokeWhileIteratingPolicy policy.
   */
  virtual void invokeForEachObserver(
      folly::Function<void(MaybeManagedObserverPointer&)>&& fn,
      const InvokeWhileIteratingPolicy policy) = 0;

  /**
   * Invoke function for each observer in the store.
   *
   * @param fn           Function to call for each observer in store.
   * @param policy       InvokeWhileIteratingPolicy policy.
   */
  virtual void invokeForEachObserver(
      folly::Function<void(Observer*)>&& fn,
      const InvokeWhileIteratingPolicy policy) {
    invokeForEachObserver(
        [fnL = std::move(fn)](MaybeManagedObserverPointer& observer) mutable {
          fnL(observer.get());
        },
        policy);
  }
};

/**
 * Policy for ObserverContainerStore.
 *
 * Defines the udnerlying container type and the default size.
 */
template <unsigned int ReserveElements = 2>
struct ObserverContainerStorePolicyDefault {
  template <typename Observer>
  using container = std::conditional_t<
      !kIsMobile,
      folly::small_vector<Observer, ReserveElements>,
      std::vector<Observer>>;
  const static unsigned int reserve_elements = ReserveElements;
};

/**
 * Policy-based implementation of ObserverContainerStoreBase.
 */
template <
    typename Observer,
    typename Policy = ObserverContainerStorePolicyDefault<>>
class ObserverContainerStore : public ObserverContainerStoreBase<Observer> {
 public:
  using Base = ObserverContainerStoreBase<Observer>;
  using InvokeWhileIteratingPolicy = typename Base::InvokeWhileIteratingPolicy;

  /**
   * Construct a new store, reserving as configured.
   */
  ObserverContainerStore() { observers_.reserve(Policy::reserve_elements); }

  /**
   * Add an observer pointer to the store.
   *
   * @param observer     Observer to add.
   * @return             Whether observer was added (not already present).
   */
  bool add(std::shared_ptr<Observer> observer) override {
    // attempts to add the same observer multiple times are rejected
    if (std::find(observers_.begin(), observers_.end(), observer) !=
        observers_.end()) {
      return false;
    }

    if (iterating_) {
      CHECK(maybeCurrentIterationPolicy_.has_value());
      const auto& policy = maybeCurrentIterationPolicy_.value();
      switch (policy) {
        case InvokeWhileIteratingPolicy::InvokeAdded:
        case InvokeWhileIteratingPolicy::DoNotInvokeAdded:
          break;
        case InvokeWhileIteratingPolicy::CheckNoChange:
          folly::terminate_with<std::runtime_error>(
              "Cannot add observers while iterating "
              "per current iteration policy (CheckNoChange)");
          break;
        case InvokeWhileIteratingPolicy::CheckNoAdded:
          folly::terminate_with<std::runtime_error>(
              "Cannot add observers while iterating "
              "per current iteration policy (CheckNoAdded)");
          break;
      }
    }
    observers_.insert(observers_.end(), observer);
    return true;
  }

  /**
   * Remove an observer pointer from the store.
   *
   * @param observer     Observer to remove.
   * @return             Whether observer found and removed from store.
   */
  bool remove(std::shared_ptr<Observer> observer) override {
    const auto it = std::find(observers_.begin(), observers_.end(), observer);
    if (it == observers_.end()) {
      return false;
    }

    // if store is currently being iterated, set this element to nullptr and it
    // will be cleaned up after iteration is completed, else erase immediately.
    if (iterating_) {
      CHECK(maybeCurrentIterationPolicy_.has_value());
      const auto& policy = maybeCurrentIterationPolicy_.value();
      switch (policy) {
        case InvokeWhileIteratingPolicy::InvokeAdded:
        case InvokeWhileIteratingPolicy::DoNotInvokeAdded:
          break;
        case InvokeWhileIteratingPolicy::CheckNoChange:
          folly::terminate_with<std::runtime_error>(
              "Cannot remove observers while iterating "
              "per current iteration policy (CheckNoChange)");
          break;
        case InvokeWhileIteratingPolicy::CheckNoAdded:
          break;
      }

      *it = nullptr;
      removalDuringIteration_ = true;
    } else {
      observers_.erase(it);
    }

    return true;
  }

  /**
   * Get number of observers in store.
   *
   * If called while the store is being iterated, the returned value may not
   * reflect changes that occurred (e.g., observers added or removed) during
   * iteration.
   *
   * @return             Number of observers in store.
   */
  size_t size() const override { return observers_.size(); }

  /**
   * Invoke function for each observer in the store.
   *
   * @param fn           Function to call for each observer in store.
   * @param policy       InvokeWhileIteratingPolicy policy.
   */
  void invokeForEachObserver(
      folly::Function<void(typename Base::MaybeManagedObserverPointer&)>&& fn,
      const typename Base::InvokeWhileIteratingPolicy policy) noexcept
      override {
    CHECK(!iterating_)
        << "Nested iteration of ObserverContainer is prohibited.";
    CHECK(!maybeCurrentIterationPolicy_.has_value())
        << "Nested iteration of ObserverContainer is prohibited.";
    iterating_ = true;
    maybeCurrentIterationPolicy_ = policy;
    SCOPE_EXIT {
      if (removalDuringIteration_) {
        // observers removed while we were iterating through container;
        // remove elements for which the element value is null
        observers_.erase(
            std::remove_if(
                observers_.begin(),
                observers_.end(),
                [](const auto& elem) { return elem == nullptr; }),
            observers_.end());
      }
      iterating_ = false;
      maybeCurrentIterationPolicy_ = folly::none;
      removalDuringIteration_ = false;
    };

    const auto numObserversAtStart = observers_.size();

    // iterate through the list using indexes, not iterators, so that the list
    // can mutate during iteration...
    for (typename container_type::size_type idx = 0;
         // observers_.size() cannot decrease during iteration, so it should be
         // insignificantly faster to check the single size in the common case.
         idx < numObserversAtStart ||
         (idx < observers_.size() &&
          policy == InvokeWhileIteratingPolicy::InvokeAdded);
         idx++) {
      auto& observer = observers_.at(idx);
      if (!observer) { // empty space in list caused by incomplete removal
        continue;
      }

      fn(observer);
    }
  }

  using Base::invokeForEachObserver;

 private:
  using container_type =
      typename Policy::template container<std::shared_ptr<Observer>>;

  // The actual list of observers.
  container_type observers_;

  // Whether we are actively iterating through the list of observers.
  bool iterating_{false};

  // If we are actively iterating, the corresponding InvokeWhileIteratingPolicy.
  folly::Optional<InvokeWhileIteratingPolicy> maybeCurrentIterationPolicy_;

  // Whether a removal or addition occurred while we iterating through the list.
  bool removalDuringIteration_{false};
};

/**
 * Policy for ObserverContainerBase.
 *
 * @tparam EventEnum    Enum of events that observers can subscribe to.
 *                      Each event must have a unique integer value greater
 *                      than zero.
 *
 * @tparam BitsetSize   Size of bitset, must be greater than or equal to the
 *                      number of events in EventEnum.
 */
template <typename EventEnum, size_t BitsetSize>
struct ObserverContainerBasePolicyDefault {
  static constexpr size_t bitset_size() { return BitsetSize; }
  using event_enum = EventEnum;
};

/**
 * Base ObserverContainer and definition of Observers.
 */
template <
    typename ObserverInterface,
    typename Observed,
    typename ContainerPolicy>
class ObserverContainerBase {
 public:
  using interface_type = ObserverInterface;
  using observed_type = Observed;
  using policy_type = ContainerPolicy;
  using EventEnum = typename ContainerPolicy::event_enum;
  using EventEnumIntT = std::underlying_type_t<EventEnum>;

  virtual ~ObserverContainerBase() = default;

  /**
   * EventSet is used to keep track of the observer events that are enabled.
   */
  class ObserverEventSet {
   public:
    ObserverEventSet() : bitset_(0) {}

    /**
     * Enables all events.
     */
    void enableAllEvents() { bitset_.set(); }

    /**
     * Enables the events passed in the initializer list.
     *
     * @param eventsEnums  Events to enable.

     */
    template <typename... EventEnums>
    void enable(EventEnums... eventEnums) {
      for (auto&& event : {eventEnums...}) {
        const auto eventAsInt = static_cast<EventEnumIntT>(event);
        bitset_.set(eventAsInt);
      }
    }

    /**
     * Returns whether the event passed in is enabled.
     *
     * @param event        Event to check.
     * @return             Whether the passed event is enabled.
     */
    bool isEnabled(const EventEnum event) const {
      const auto eventAsInt = static_cast<EventEnumIntT>(event);
      return bitset_.test(eventAsInt);
    }

    /**
     * Builder that makes it easier to pass EventSet to Observer constructor.
     */
    class Builder {
     public:
      explicit Builder() = default;

      /**
       * Enables all events.
       */
      Builder&& enableAllEvents() {
        set_.enableAllEvents();
        return std::move(*this);
      }

      /**
       * Enables the events passed in the intiailizer list.
       *
       * @param events       Events to enable.
       */
      template <typename... EventEnums>
      Builder&& enable(EventEnums... eventEnums) {
        set_.enable(eventEnums...);
        return std::move(*this);
      }

      /**
       * Returns the EventSet that has been built.
       */
      ObserverEventSet build() && { return set_; }

     private:
      ObserverEventSet set_;
    };

   private:
    std::bitset<ContainerPolicy::bitset_size()> bitset_{0};
  };

  /**
   * Observer base interface.
   *
   * This interface includes the events exposed by the subject's observer
   * interface and the set of events that are provided by the ObserverContainer
   * (attached/detached/moved/destoyed). It also defines how observers subscribe
   * to specific events made available by the subject.
   */
  class ObserverBase : public ObserverInterface, public DestructorCheck {
   public:
    using observed_type = Observed;
    using interface_type = ObserverInterface;

    using EventSet = ObserverEventSet;
    using EventSetBuilder = typename ObserverEventSet::Builder;

    ~ObserverBase() override = default;

    /**
     * Construct a new observer with no event subscriptions.
     */
    ObserverBase() {}

    /**
     * Construct a new observer subscribed to events in the passed EventSet.
     */
    explicit ObserverBase(EventSet eventSet) : eventSet_(eventSet) {}

    /**
     * Base class that can be used to pass context about move operation.
     */
    class MoveContext {};

    /**
     * Base class that can be used to pass context about why object destroyed.
     */
    class DestroyContext {};

    /**
     * Invoked when this observer is attached to an object.
     *
     * @param obj   Object that observer is now attached to.
     */
    virtual void attached(Observed* /* obj */) noexcept {}

    /**
     * Invoked if this observer is detached from an object.
     *
     * @param obj   Object that observer is no longer attached to.
     */
    virtual void detached(Observed* /* obj */) noexcept {}

    /**
     * Invoked when an observed object's destructor is invoked.
     *
     * Destruction of the observed object implicitly implies detached, and thus
     * detached will not be called if an object is destroyed.
     *
     * @param obj           Object being destroyed.
     * @param ctx           Additional info about what triggered destruction.
     *                      Not available unless provided by the implementation;
     *                      if not supported it is a nullptr.
     */
    virtual void destroyed(
        Observed* /* obj */, DestroyContext* /* ctx */) noexcept {}

    /**
     * Invoked when object being observed changes due to move construction.
     *
     * @param oldObj        Object previously being observed.
     * @param newObj        Object now being observed.
     * @param ctx           Additional info about what triggered the move.
     *                      Not available unless provided by the implementation;
     *                      if not supported it is a nullptr.
     */
    virtual void moved(
        Observed* /* oldObj */,
        Observed* /* newObj */,
        MoveContext* /* ctx */) noexcept {}

    /**
     * Proxy function used to invoke a method defined in the observer interface.
     *
     * Can be overridden to enable composition of observers, including event bus
     * architectures in which multiple handlers act on an event.
     *
     * Implementations can remove themselves and add/remove other observers from
     * the container when handling this call. If new observers are added to the
     * container, invokeInterfaceMethod will be called on those new observers
     * as well. If you want to avoid this in your observer implementation, delay
     * mutation of the container until postInvokeInterfaceMethod is called.
     *
     * @param obj           Object associated with observer event.
     * @param fn            Function that will invoke the method associated with
     *                      an observer event, passing any event context.
     * @param maybeEvent    The event enum associated with the invocation.
     */
    virtual void invokeInterfaceMethod(
        Observed* obj,
        folly::Function<void(ObserverBase*, Observed*)>& fn,
        folly::Optional<EventEnum> /* maybeEvent */) noexcept {
      fn(this, obj);
    }

    /**
     * Invoked after invokeInterfaceMethod has completed for all observers.
     *
     * Can be used to delay mutation of the container after processing of an
     * event has completed. Implementations can remove themselves and add/remove
     * other observers from the container when handling this call. However, this
     * function will only be called for the set of observers in the container
     * when the preceding call to invokeInterfaceMethod finished.
     *
     * @param obj           Object associated with observer event.
     */
    virtual void postInvokeInterfaceMethod(Observed* /* obj */) noexcept {}

    /**
     * Returns the EventSet containing the events the observer wants.
     */
    const EventSet& getEventSet() const noexcept { return eventSet_; }

   private:
    const EventSet eventSet_;
  };

  /**
   * Observer interface.
   *
   * The interface between an observer container and observers in the container.
   *
   * This interface includes methods that are called upon relevant changes to
   * the observer's status in a container (added/removedFromObserverContainer).
   *
   * An observer must not be destroyed while it is in a container. This can be
   * accomplished by removing the observer from the container on its destruction
   * or delaying destruction.
   *
   * Typical use cases should not attempt to implement this interface and should
   * instead use a specialization such as ManagedObserver.
   */
  class Observer : public ObserverBase {
   public:
    using observed_type = Observed;
    using interface_type = ObserverInterface;

    using EventSet = ObserverEventSet;
    using EventSetBuilder = typename ObserverEventSet::Builder;

    ~Observer() override = default;

    /**
     * Construct a new observer with no event subscriptions.
     */
    Observer() : ObserverBase() {}

    /**
     * Construct a new observer subscribed to events in the passed EventSet.
     */
    explicit Observer(EventSet eventSet) : ObserverBase(eventSet) {}

    /**
     * Invoked when this observer has been added to an observer container.
     *
     * For the typical observer container implementation a call to `attached`
     * will proceed a call to this method.
     *
     * The observer implementation must ensure that it remains alive as long as
     * it is in this container.
     *
     * @param ctr           Container observer has been added to.
     */
    virtual void addedToObserverContainer(
        ObserverContainerBase* ctr) noexcept = 0;

    /**
     * Invoked when this observer has been removed from an observer container.
     *
     * For the typical observer container implementation a call to `detached`
     * will have occurred before this method is called.
     *
     * @param ctr           Container observer has been removed from.
     */
    virtual void removedFromObserverContainer(
        ObserverContainerBase* ctr) noexcept = 0;

    /**
     * Invoked when this observer is moved from one container to another.
     *
     * Occurs in the case of move construction of a new object during which the
     * observers in the observer container are shifted from the old object to
     * the new object.
     *
     * @param oldCtr        Container observer has been removed from.
     * @param newCtr        Container observer has been added to.
     */
    virtual void movedToObserverContainer(
        ObserverContainerBase* oldCtr,
        ObserverContainerBase* newCtr) noexcept = 0;
  };

  /**
   * Returns the object associated with the container (e.g., observed object).
   *
   * @return             Return object associated with container or nullptr.
   */
  virtual Observed* getObject() const = 0;

  /**
   * Adds an observer to the container.
   *
   * If the observer is already in the container, this is a no-op.
   *
   * @param observer     Observer to add.
   */
  virtual void addObserver(std::shared_ptr<Observer> observer) = 0;

  /**
   * Adds an observer to the container.
   *
   * If the observer is already in the container, this is a no-op.
   *
   * @param observer     Observer to add.
   */
  virtual void addObserver(Observer* observer) {
    // create a shared_ptr holding an unmanaged ptr
    // this does not trigger control block allocation
    return addObserver(
        std::shared_ptr<Observer>(std::shared_ptr<void>(), observer));
  }

  /**
   * Removes an observer from the container.
   *
   * @param observer     Observer to remove.
   * @return             Whether the observer was found and removed.
   */
  virtual bool removeObserver(std::shared_ptr<Observer> observer) = 0;

  /**
   * Removes an observer from the container.
   *
   * @param observer     Observer to remove.
   * @return             Whether the observer was found and removed.
   */
  virtual bool removeObserver(Observer* observer) {
    // create a shared_ptr holding an unmanaged ptr
    // this does not trigger control block allocation
    return removeObserver(
        std::shared_ptr<Observer>(std::shared_ptr<void>(), observer));
  }

  /**
   * Get number of observers in container.
   *
   * @return             Number of observers in container.
   */
  size_t numObservers() const { return getStoreConst().size(); }

  /**
   * Get a list of observers in the container of type T.
   *
   * @tparam T           Type of observer to find.
   * @return             List of observers in the container of type T.
   */
  template <typename T = Observer>
  std::vector<T*> findObservers() {
    static_assert(
        std::is_base_of<Observer, T>::value,
        "T must derive from ObserverContainer::Observer");

    std::vector<T*> matchingObservers;
    using InvokeWhileIteratingPolicy = typename ObserverContainerStoreBase<
        Observer>::InvokeWhileIteratingPolicy;
    getStore().invokeForEachObserver(
        [&matchingObservers](Observer* observer) {
          auto castPtr = dynamic_cast<T*>(observer);
          if (castPtr) {
            matchingObservers.emplace_back(castPtr);
          }
        },
        InvokeWhileIteratingPolicy::CheckNoChange);

    return matchingObservers;
  }

  /**
   * Get all observers.
   *
   * @return             List of observers in the container.
   */
  std::vector<Observer*> getObservers() { return findObservers<Observer>(); }

  /**
   * Returns if any observer in the container is subscribed to a given event.
   *
   * TODO(bschlinker): The current implementation scans the entire container to
   * search for an observer subscribed to the requested event; we should cache
   * this information instead and update the cache on observer add / remove.
   *
   * @tparam event       Event in EventEnum.
   * @return             If there are observers subscribed to the given event.
   */
  template <EventEnum event>
  bool hasObserversForEvent() {
    bool foundObserverWithEvent = false;
    using InvokeWhileIteratingPolicy = typename ObserverContainerStoreBase<
        Observer>::InvokeWhileIteratingPolicy;
    getStore().invokeForEachObserver(
        [&foundObserverWithEvent](Observer* observer) {
          foundObserverWithEvent |= observer->getEventSet().isEnabled(event);
        },
        InvokeWhileIteratingPolicy::CheckNoChange);
    return foundObserverWithEvent;
  }

  /**
   * Helper class for observers that attach to single object / container.
   *
   * Does not have any thread safety, and thus can only be used if the observer
   * is driven exclusively by the same thread as the thread that controls the
   * object being observed.
   *
   * Tracks the container the observer is in (if any). If the observer's
   * destructor is triggered while it is in an container, it will be removed
   * from the container during the destruction process.
   */
  class ManagedObserver : public Observer {
   public:
    using Observer::Observer;
    using EventSet = typename Observer::EventSet;
    using EventSetBuilder = typename Observer::EventSetBuilder;

    ~ManagedObserver() override { detach(); }

    /**
     * Detach the observer (if currently attached).
     *
     * If the observer is not currently attached, this is a no-op.
     *
     * @return       If successfully detached.
     */
    bool detach() {
      if (!ctr_) {
        return false;
      }
      return ctr_->removeObserver(this);
    }

    /**
     * Return if the observer is observing an object.
     *
     * @return       If observer is observing an object.
     */
    bool isObserving() const {
      return ctr_ != nullptr && ctr_->getObject() != nullptr;
    }

    /**
     * Get the object that is being observed or nullptr.
     *
     * @return       Object being observed.
     */
    Observed* getObservedObject() const {
      if (!ctr_) {
        return nullptr;
      }
      return ctr_->getObject();
    }

   private:
    void addedToObserverContainer(
        ObserverContainerBase* ctr) noexcept override {
      CHECK(!ctr_);
      ctr_ = ctr;
    }

    void removedFromObserverContainer(
        ObserverContainerBase* ctr) noexcept override {
      CHECK_EQ(ctr_, ctr);
      ctr_ = nullptr;
    }

    void movedToObserverContainer(
        ObserverContainerBase* oldCtr,
        ObserverContainerBase* newCtr) noexcept override {
      CHECK_EQ(ctr_, oldCtr);
      CHECK_NE(ctr_, newCtr);
      ctr_ = newCtr;
    }

    // Container the observer is in (or nullptr).
    ObserverContainerBase* ctr_{nullptr};
  };

 protected:
  virtual ObserverContainerStoreBase<Observer>& getStore() = 0;

  virtual const ObserverContainerStoreBase<Observer>& getStoreConst() const = 0;

  void invokeInterfaceMethodImpl(
      Observed* observed,
      folly::Function<void(ObserverBase*, Observed*)>&& fn,
      const folly::Optional<EventEnum> maybeEvent = folly::none) noexcept {
    using InvokeWhileIteratingPolicy = typename ObserverContainerStoreBase<
        Observer>::InvokeWhileIteratingPolicy;
    getStore().invokeForEachObserver(
        [observed, maybeEvent, &fn](Observer* observer) {
          if (!maybeEvent.has_value() ||
              observer->getEventSet().isEnabled(maybeEvent.value())) {
            observer->invokeInterfaceMethod(observed, fn, maybeEvent);
          }
        },
        InvokeWhileIteratingPolicy::InvokeAdded);
    getStore().invokeForEachObserver(
        [observed, maybeEvent](ObserverBase* observer) {
          if (!maybeEvent.has_value() ||
              observer->getEventSet().isEnabled(maybeEvent.value())) {
            observer->postInvokeInterfaceMethod(observed);
          }
        },
        InvokeWhileIteratingPolicy::DoNotInvokeAdded);
  }
};

/**
 * Policy-based implementation of ObserverContainerBase.
 */
template <
    typename ObserverInterface,
    typename Observed,
    typename ContainerPolicy,
    typename StorePolicy = ObserverContainerStorePolicyDefault<>,
    std::size_t MaxConstructorCallbacks = 4>
class ObserverContainer : public ObserverContainerBase<
                              ObserverInterface,
                              Observed,
                              ContainerPolicy> {
 public:
  using ContainerBase =
      ObserverContainerBase<ObserverInterface, Observed, ContainerPolicy>;
  using Observer = typename ContainerBase::Observer;
  using EventEnum = typename ContainerBase::EventEnum;
  using StoreBase = ObserverContainerStoreBase<Observer>;
  using ContainerConstructorCallbackList =
      ConstructorCallbackList<ObserverContainer, MaxConstructorCallbacks>;

  explicit ObserverContainer(Observed* obj)
      : obj_(CHECK_NOTNULL(obj)), constructorCallbackList_(this) {}

  ObserverContainer(Observed* obj, ObserverContainer&& observerContainer)
      : obj_(CHECK_NOTNULL(obj)), constructorCallbackList_(this) {
    using InvokeWhileIteratingPolicy =
        typename StoreBase::InvokeWhileIteratingPolicy;
    observerContainer.getStore().invokeForEachObserver(
        [this, &observerContainer](
            typename StoreBase::MaybeManagedObserverPointer& observer) {
          // observer may be a managed pointer (e.g., a shared_ptr), and
          // invokeForEachObserver passes a reference, so we need to copy the
          // observer object before calling remove so that it will not be
          // destroyed upon removal from the old ObserverContainer
          auto observerCopy = observer;
          CHECK_NOTNULL(observerCopy.get());

          // remove
          const bool removed = observerContainer.getStore().remove(observer);
          CHECK(removed);

          // add to new, operating solely on observerCopy
          const bool added = getStore().add(observerCopy);
          CHECK(added);
          observerCopy->movedToObserverContainer(&observerContainer, this);
          observerCopy->moved(
              observerContainer.getObject(), obj_, nullptr /* ctx */);
        },
        InvokeWhileIteratingPolicy::CheckNoAdded);
  }

  ~ObserverContainer() override {
    using InvokeWhileIteratingPolicy =
        typename StoreBase::InvokeWhileIteratingPolicy;
    getStore().invokeForEachObserver(
        [this](Observer* observer) {
          DestructorCheck::Safety dc(*observer);
          observer->destroyed(obj_, nullptr /* ctx */);
          if (!dc.destroyed()) {
            observer->removedFromObserverContainer(this);
          }
        },
        InvokeWhileIteratingPolicy::CheckNoAdded);
  }

  /**
   * Returns the object associated with the container (e.g., observed object).
   *
   * @return             Return object associated with container or nullptr.
   */
  Observed* getObject() const override { return obj_; }

  using ContainerBase::addObserver;
  using ContainerBase::removeObserver;

  /**
   * Adds an observer to the container.
   *
   * If the observer is already in the container, this is a no-op.
   *
   * @param observer     Observer to add.
   */
  void addObserver(std::shared_ptr<Observer> observer) override {
    CHECK_NOTNULL(observer.get());
    if (getStore().add(observer)) {
      DestructorCheck::Safety dc(*observer);
      observer->addedToObserverContainer(this);
      if (!dc.destroyed()) {
        observer->attached(obj_);
      }
    }
  }

  /**
   * Removes an observer from the container.
   *
   * @param observer     Observer to remove.
   * @return             Whether the observer was found and removed.
   */
  bool removeObserver(std::shared_ptr<Observer> observer) override {
    CHECK_NOTNULL(observer.get());
    if (getStore().remove(observer)) {
      DestructorCheck::Safety dc(*observer);
      observer->detached(obj_);
      if (!dc.destroyed()) {
        observer->removedFromObserverContainer(this);
      }
      return true;
    }
    return false;
  }

  /**
   * Add a callback fired each time obj of the observed type is constructed.
   *
   * Uses ConstructorCallbackList. Can be used to attach observers to all
   * objects of a given type.
   *
   * @throw std::length_error() if installing callback would exceed max allowed
   */
  static void addConstructorCallback(
      typename ContainerConstructorCallbackList::Callback cb) {
    ContainerConstructorCallbackList::addCallback(std::move(cb));
  }

  /**
   * Invokes an observer interface method on observers subscribed to an event.
   *
   * See instead `invokeInterfaceMethodAllObservers` to invoke an interface
   * method on all observers without filtering based on observer event
   * subscription.
   *
   * @tparam event       Associated event in EventEnum. The passed function will
   *                     only be called for observers subscribed to this event.
   * @param  fn          Function to call for each observer that takes a pointer
   *                     to the observer and invokes the interface method.
   */
  template <EventEnum event>
  void invokeInterfaceMethod(
      folly::Function<void(ObserverInterface*, Observed*)>&& fn) noexcept {
    this->invokeInterfaceMethodImpl(obj_, std::move(fn), event);
  }

  /**
   * Invokes an observer interface method on all observers.
   *
   * @param  fn          Function to call for each observer that takes a pointer
   *                     to the observer and invokes the interface method.
   */
  void invokeInterfaceMethodAllObservers(
      folly::Function<void(ObserverInterface*, Observed*)>&& fn) noexcept {
    this->invokeInterfaceMethodImpl(obj_, std::move(fn), folly::none);
  }

  ObserverContainer(const ObserverContainer&) = delete;
  ObserverContainer(ObserverContainer&&) = delete;
  ObserverContainer& operator=(const ObserverContainer&) = delete;
  ObserverContainer& operator=(ObserverContainer&& rhs) = delete;

 private:
  StoreBase& getStore() override { return store_; }
  const StoreBase& getStoreConst() const override { return store_; }

  // Object being observed.
  Observed* obj_{nullptr};

  // Store that contains the observers in the container.
  ObserverContainerStore<Observer, StorePolicy> store_;

  // Enables objects to register constructor callbacks.
  //
  // This can be used to enable observers to be attached to all objects of a
  // given type immediately upon object construction.
  //
  // Initialized last and in ObserverContainer instead of ObserverContainerBase
  // to ensure that the container has completed initialization and is ready to
  // add observers when any constructor callbacks are called.
  ContainerConstructorCallbackList constructorCallbackList_{this};
};

} // namespace folly
