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

#include "CSSPropertyId.h"
#include "nsCSSPropertyIDSet.h"
#include "nsTHashSet.h"

namespace mozilla {

class AnimatedPropertyIDSet {
 public:
  using CustomNameSet = nsTHashSet<RefPtr<nsAtom>>;

  AnimatedPropertyIDSet() = default;
  AnimatedPropertyIDSet(AnimatedPropertyIDSet&& aOther) = default;
  AnimatedPropertyIDSet(nsCSSPropertyIDSet&& aIDs, CustomNameSet&& aNames)
      : mIDs(std::move(aIDs)), mCustomNames(std::move(aNames)) {}

  AnimatedPropertyIDSet& operator=(AnimatedPropertyIDSet&& aRhs) {
    MOZ_ASSERT(&aRhs != this);
    this->~AnimatedPropertyIDSet();
    new (this) AnimatedPropertyIDSet(std::move(aRhs));
    return *this;
  }

  void AddProperty(const CSSPropertyId& aProperty) {
    if (aProperty.IsCustom()) {
      mCustomNames.Insert(aProperty.mCustomName);
    } else {
      mIDs.AddProperty(aProperty.mId);
    }
  }

  void RemoveProperty(const CSSPropertyId& aProperty) {
    if (aProperty.IsCustom()) {
      mCustomNames.Remove(aProperty.mCustomName);
    } else {
      mIDs.RemoveProperty(aProperty.mId);
    }
  }

  bool HasProperty(const CSSPropertyId& aProperty) const {
    if (aProperty.IsCustom()) {
      return mCustomNames.Contains(aProperty.mCustomName);
    }
    return mIDs.HasProperty(aProperty.mId);
  }

  bool Intersects(const nsCSSPropertyIDSet& aIDs) const {
    return mIDs.Intersects(aIDs);
  }

  bool IsSubsetOf(const AnimatedPropertyIDSet& aOther) const {
    if (!mIDs.IsSubsetOf(aOther.mIDs) ||
        mCustomNames.Count() > aOther.mCustomNames.Count()) {
      return false;
    }
    for (const auto& name : mCustomNames) {
      if (!aOther.mCustomNames.Contains(name)) {
        return false;
      }
    }
    return true;
  }

  bool IsEmpty() const { return mIDs.IsEmpty() && mCustomNames.IsEmpty(); }

  void AddProperties(const AnimatedPropertyIDSet& aOther) {
    mIDs |= aOther.mIDs;
    for (const auto& name : aOther.mCustomNames) {
      mCustomNames.Insert(name);
    }
  }

  // Returns a new nsCSSPropertyIDSet with all properties that are both in this
  // set and |aOther|.
  nsCSSPropertyIDSet Intersect(const nsCSSPropertyIDSet& aOther) const {
    return mIDs.Intersect(aOther);
  }

  // Returns a new AnimatedPropertyIDSet with all properties that are both in
  // this set and |aOther|.
  AnimatedPropertyIDSet Intersect(const AnimatedPropertyIDSet& aOther) const {
    nsCSSPropertyIDSet ids = mIDs.Intersect(aOther.mIDs);
    CustomNameSet names;
    for (const auto& name : mCustomNames) {
      if (!aOther.mCustomNames.Contains(name)) {
        continue;
      }
      names.Insert(name);
    }
    return AnimatedPropertyIDSet(std::move(ids), std::move(names));
  }

  // Returns a new AnimatedPropertyIDSet with all properties that are in either
  // this set or |aOther| but not both.
  AnimatedPropertyIDSet Xor(const AnimatedPropertyIDSet& aOther) const {
    nsCSSPropertyIDSet ids = mIDs.Xor(aOther.mIDs);
    CustomNameSet names;
    for (const auto& name : mCustomNames) {
      if (aOther.mCustomNames.Contains(name)) {
        continue;
      }
      names.Insert(name);
    }

    for (const auto& name : aOther.mCustomNames) {
      if (mCustomNames.Contains(name)) {
        continue;
      }
      names.Insert(name);
    }
    return AnimatedPropertyIDSet(std::move(ids), std::move(names));
  }

  // Iterator for use in range-based for loops
  class Iterator {
   public:
    Iterator(Iterator&& aOther)
        : mPropertySet(aOther.mPropertySet),
          mIDIterator(std::move(aOther.mIDIterator)),
          mCustomNameIterator(std::move(aOther.mCustomNameIterator)),
          mPropertyId(eCSSProperty_UNKNOWN) {}
    Iterator() = delete;
    Iterator(const Iterator&) = delete;
    Iterator& operator=(const Iterator&) = delete;
    Iterator& operator=(const Iterator&&) = delete;

    static Iterator BeginIterator(const AnimatedPropertyIDSet& aPropertySet) {
      return Iterator(aPropertySet, aPropertySet.mIDs.begin(),
                      aPropertySet.mCustomNames.begin());
    }

    static Iterator EndIterator(const AnimatedPropertyIDSet& aPropertySet) {
      return Iterator(aPropertySet, aPropertySet.mIDs.end(),
                      aPropertySet.mCustomNames.end());
    }

    bool operator!=(const Iterator& aOther) const {
      return mIDIterator != aOther.mIDIterator ||
             mCustomNameIterator != aOther.mCustomNameIterator;
    }

    Iterator& operator++() {
      MOZ_ASSERT(mIDIterator != mPropertySet.mIDs.end() ||
                     mCustomNameIterator != mPropertySet.mCustomNames.end(),
                 "Should not iterate beyond end");
      if (mIDIterator != mPropertySet.mIDs.end()) {
        ++mIDIterator;
      } else {
        ++mCustomNameIterator;
      }
      return *this;
    }

    CSSPropertyId operator*() {
      if (mIDIterator != mPropertySet.mIDs.end()) {
        mPropertyId.mId = *mIDIterator;
        mPropertyId.mCustomName = nullptr;
      } else if (mCustomNameIterator != mPropertySet.mCustomNames.end()) {
        mPropertyId.mId = eCSSPropertyExtra_variable;
        mPropertyId.mCustomName = *mCustomNameIterator;
      } else {
        MOZ_ASSERT_UNREACHABLE("Should not dereference beyond end");
        mPropertyId.mId = eCSSProperty_UNKNOWN;
        mPropertyId.mCustomName = nullptr;
      }
      return mPropertyId;
    }

   private:
    Iterator(const AnimatedPropertyIDSet& aPropertySet,
             nsCSSPropertyIDSet::Iterator aIDIterator,
             CustomNameSet::const_iterator aCustomNameIterator)
        : mPropertySet(aPropertySet),
          mIDIterator(std::move(aIDIterator)),
          mCustomNameIterator(std::move(aCustomNameIterator)),
          mPropertyId(eCSSProperty_UNKNOWN) {}

    const AnimatedPropertyIDSet& mPropertySet;
    nsCSSPropertyIDSet::Iterator mIDIterator;
    CustomNameSet::const_iterator mCustomNameIterator;
    CSSPropertyId mPropertyId;
  };

  Iterator begin() const { return Iterator::BeginIterator(*this); }
  Iterator end() const { return Iterator::EndIterator(*this); }

 private:
  AnimatedPropertyIDSet(const AnimatedPropertyIDSet&) = delete;
  AnimatedPropertyIDSet& operator=(const AnimatedPropertyIDSet&) = delete;

  nsCSSPropertyIDSet mIDs;
  CustomNameSet mCustomNames;
};

// A wrapper to support the inversion of AnimatedPropertyIDSet.
//
// We are using this struct (for convenience) to check if we should skip the
// AnimatedPropertyIDs when composing an animation rule, on either
// CascadeLevel::Animations or CascadeLevel::Tranistions.
struct InvertibleAnimatedPropertyIDSet {
  const AnimatedPropertyIDSet* mSet = nullptr;
  bool mIsInverted = false;

  void Setup(const AnimatedPropertyIDSet* aSet, bool aIsInverted) {
    mSet = aSet;
    mIsInverted = aIsInverted;
  }

  bool HasProperty(const CSSPropertyId& aProperty) const {
    return mSet && mIsInverted != mSet->HasProperty(aProperty);
  }
};

}  // namespace mozilla

#endif  // mozilla_AnimatedPropertyIDSet_h
