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

#include "PLDHashTable.h"
#include "mozilla/Span.h"

class nsIFrame;

namespace mozilla {

struct DisplayItemKey {
  bool operator==(const DisplayItemKey&) const = default;

  nsIFrame* mFrame;
  uint32_t mPerFrameKey;
};

class DisplayItemHashEntry : public PLDHashEntryHdr {
 public:
  typedef DisplayItemKey KeyType;
  typedef const DisplayItemKey* KeyTypePointer;

  explicit DisplayItemHashEntry(KeyTypePointer aKey) : mKey(*aKey) {}
  DisplayItemHashEntry(DisplayItemHashEntry&&) = default;

  ~DisplayItemHashEntry() = default;

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

  static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
  static PLDHashNumber HashKey(KeyTypePointer aKey) {
    if (!aKey) {
      return 0;
    }

    return mozilla::HashGeneric(aKey->mFrame, aKey->mPerFrameKey);
  }
  enum { ALLOW_MEMMOVE = true };

  DisplayItemKey mKey;
};

template <typename T>
bool SpanContains(mozilla::Span<const T>& aSpan, T aItem) {
  for (const T& i : aSpan) {
    if (i == aItem) {
      return true;
    }
  }
  return false;
}

class OldListUnits {};
class MergedListUnits {};

template <typename Units>
struct Index {
  Index() = default;
  explicit Index(size_t aVal) : val(aVal) {
    MOZ_RELEASE_ASSERT(aVal < std::numeric_limits<uint32_t>::max(),
                       "List index overflowed");
  }

  bool operator==(const Index<Units>&) const = default;

  uint32_t val = 0;
};
typedef Index<OldListUnits> OldListIndex;
typedef Index<MergedListUnits> MergedListIndex;

template <typename T>
class DirectedAcyclicGraph {
 public:
  DirectedAcyclicGraph() = default;
  DirectedAcyclicGraph(DirectedAcyclicGraph&& aOther)
      : mNodesInfo(std::move(aOther.mNodesInfo)),
        mDirectPredecessorList(std::move(aOther.mDirectPredecessorList)) {}

  DirectedAcyclicGraph& operator=(DirectedAcyclicGraph&& aOther) {
    mNodesInfo = std::move(aOther.mNodesInfo);
    mDirectPredecessorList = std::move(aOther.mDirectPredecessorList);
    return *this;
  }

  Index<T> AddNode(
      mozilla::Span<const Index<T>> aDirectPredecessors,
      const mozilla::Maybe<Index<T>>& aExtraPredecessor = mozilla::Nothing()) {
    size_t index = mNodesInfo.Length();
    mNodesInfo.AppendElement(NodeInfo(mDirectPredecessorList.Length(),
                                      aDirectPredecessors.Length()));
    if (aExtraPredecessor &&
        !SpanContains(aDirectPredecessors, aExtraPredecessor.value())) {
      mNodesInfo.LastElement().mDirectPredecessorCount++;
      mDirectPredecessorList.SetCapacity(mDirectPredecessorList.Length() +
                                         aDirectPredecessors.Length() + 1);
      mDirectPredecessorList.AppendElements(aDirectPredecessors);
      mDirectPredecessorList.AppendElement(aExtraPredecessor.value());
    } else {
      mDirectPredecessorList.AppendElements(aDirectPredecessors);
    }
    return Index<T>(index);
  }

  size_t Length() { return mNodesInfo.Length(); }

  mozilla::Span<Index<T>> GetDirectPredecessors(Index<T> aNodeIndex) {
    NodeInfo& node = mNodesInfo[aNodeIndex.val];
    const auto span = mozilla::Span{mDirectPredecessorList};
    return span.Subspan(node.mIndexInDirectPredecessorList,
                        node.mDirectPredecessorCount);
  }

  template <typename OtherUnits>
  void EnsureCapacityFor(const DirectedAcyclicGraph<OtherUnits>& aOther) {
    mNodesInfo.SetCapacity(aOther.mNodesInfo.Length());
    mDirectPredecessorList.SetCapacity(aOther.mDirectPredecessorList.Length());
  }

  void Clear() {
    mNodesInfo.Clear();
    mDirectPredecessorList.Clear();
  }

  struct NodeInfo {
    NodeInfo(size_t aIndexInDirectPredecessorList,
             size_t aDirectPredecessorCount)
        : mIndexInDirectPredecessorList(aIndexInDirectPredecessorList),
          mDirectPredecessorCount(aDirectPredecessorCount) {}
    size_t mIndexInDirectPredecessorList;
    size_t mDirectPredecessorCount;
  };

  nsTArray<NodeInfo> mNodesInfo;
  nsTArray<Index<T>> mDirectPredecessorList;
};

class RetainedDisplayListBuilder;
class nsDisplayItem;

struct OldItemInfo {
  explicit OldItemInfo(nsDisplayItem* aItem);

  void AddedToMergedList(MergedListIndex aIndex) {
    MOZ_ASSERT(!IsUsed());
    mUsed = true;
    mIndex = aIndex;
    mItem = nullptr;
  }

  void AddedMatchToMergedList(RetainedDisplayListBuilder* aBuilder,
                              MergedListIndex aIndex);
  void Discard(RetainedDisplayListBuilder* aBuilder,
               nsTArray<MergedListIndex>&& aDirectPredecessors);
  bool IsUsed() { return mUsed; }

  bool IsDiscarded() {
    MOZ_ASSERT(IsUsed());
    return mDiscarded;
  }

  bool IsChanged();

  nsDisplayItem* mItem;
  nsTArray<MergedListIndex> mDirectPredecessors;
  MergedListIndex mIndex;
  bool mUsed;
  bool mDiscarded;
  bool mOwnsItem;
};

bool AnyContentAncestorModified(nsIFrame* aFrame,
                                nsIFrame* aStopAtFrame = nullptr);

}  // namespace mozilla

#endif  // RETAINEDDISPLAYLISTHELPERS_H_
