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

#include <type_traits>

#include "DDLifetime.h"
#include "DDLoggedTypeTraits.h"
#include "nsClassHashtable.h"
#include "nsTArray.h"

namespace mozilla {

// Managed list of lifetimes.
class DDLifetimes {
 public:
  // DDLifetime for a given aObject, that exists at the aIndex time;
  // otherwise nullptr.
  DDLifetime* FindLifetime(const DDLogObject& aObject,
                           const DDMessageIndex& aIndex);

  // DDLifetime for a given aObject, that exists at the aIndex time;
  // otherwise nullptr.
  const DDLifetime* FindLifetime(const DDLogObject& aObject,
                                 const DDMessageIndex& aIndex) const;

  // Create a lifetime with the given object and construction index&time.
  DDLifetime& CreateLifetime(const DDLogObject& aObject, DDMessageIndex aIndex,
                             const DDTimeStamp& aConstructionTimeStamp);

  // Remove an existing lifetime (assumed to just have been found by
  // FindLifetime()).
  void RemoveLifetime(const DDLifetime* aLifetime);

  // Remove all lifetimes associated with the given HTMLMediaElement.
  void RemoveLifetimesFor(const dom::HTMLMediaElement* aMediaElement);

  // Remove all lifetimes.
  void Clear();

  // Visit all lifetimes associated with an HTMLMediaElement and run
  // `aF(const DDLifetime&)` on each one.
  // If aOnlyHTMLMediaElement is true, only run aF once of that element.
  template <typename F>
  void Visit(const dom::HTMLMediaElement* aMediaElement, F&& aF,
             bool aOnlyHTMLMediaElement = false) const {
    for (const auto& lifetimes : mLifetimes.Values()) {
      for (const DDLifetime& lifetime : *lifetimes) {
        if (lifetime.mMediaElement == aMediaElement) {
          if (aOnlyHTMLMediaElement) {
            if (lifetime.mObject.Pointer() == aMediaElement &&
                lifetime.mObject.TypeName() ==
                    DDLoggedTypeTraits<dom::HTMLMediaElement>::Name()) {
              aF(lifetime);
              break;
            }
            continue;
          }
          static_assert(std::is_same_v<decltype(aF(lifetime)), void>, "");
          aF(lifetime);
        }
      }
    }
  }

  // Visit all lifetimes associated with an HTMLMediaElement and run
  // `aF(const DDLifetime&)` on each one.
  // If aF() returns false, the loop continues.
  // If aF() returns true, the loop stops, and true is returned immediately.
  // If all aF() calls have returned false, false is returned at the end.
  template <typename F>
  bool VisitBreakable(const dom::HTMLMediaElement* aMediaElement,
                      F&& aF) const {
    for (const auto& lifetimes : mLifetimes.Values()) {
      for (const DDLifetime& lifetime : *lifetimes) {
        if (lifetime.mMediaElement == aMediaElement) {
          static_assert(std::is_same_v<decltype(aF(lifetime)), bool>, "");
          if (aF(lifetime)) {
            return true;
          }
        }
      }
    }
    return false;
  }

  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;

 private:
  // Hashtable key to use for each DDLogObject.
  class DDLogObjectHashKey : public PLDHashEntryHdr {
   public:
    typedef const DDLogObject& KeyType;
    typedef const DDLogObject* KeyTypePointer;

    explicit DDLogObjectHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
    DDLogObjectHashKey(const DDLogObjectHashKey& aToCopy)
        : mValue(aToCopy.mValue) {}
    ~DDLogObjectHashKey() = default;

    KeyType GetKey() const { return mValue; }
    bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }

    static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
    static PLDHashNumber HashKey(KeyTypePointer aKey) {
      return HashBytes(aKey, sizeof(DDLogObject));
    }
    enum { ALLOW_MEMMOVE = true };

   private:
    const DDLogObject mValue;
  };

  // Array of all DDLifetimes for a given DDLogObject; they should be
  // distinguished by their construction&destruction times.
  using LifetimesForObject = nsTArray<DDLifetime>;

  // For each DDLogObject, we store an array of all objects that have used this
  // pointer and type.
  nsClassHashtable<DDLogObjectHashKey, LifetimesForObject> mLifetimes;
};

}  // namespace mozilla

#endif  // DDLifetimes_h_
