/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */

/* A set abstraction for enumeration values. */

#ifndef mozilla_EnumSet_h
#define mozilla_EnumSet_h

#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"

#include <initializer_list>

#include <stdint.h>

namespace mozilla {

/**
 * EnumSet<T> is a set of values defined by an enumeration. It is implemented
 * using a 32 bit mask for each value so it will only work for enums with an int
 * representation less than 32. It works both for enum and enum class types.
 */
template<typename T>
class EnumSet
{
public:
  EnumSet()
    : mBitField(0)
  {
    initVersion();
  }

  MOZ_IMPLICIT EnumSet(T aEnum)
    : mBitField(bitFor(aEnum))
  { }

  EnumSet(T aEnum1, T aEnum2)
    : mBitField(bitFor(aEnum1) |
                bitFor(aEnum2))
  {
    initVersion();
  }

  EnumSet(T aEnum1, T aEnum2, T aEnum3)
    : mBitField(bitFor(aEnum1) |
                bitFor(aEnum2) |
                bitFor(aEnum3))
  {
    initVersion();
  }

  EnumSet(T aEnum1, T aEnum2, T aEnum3, T aEnum4)
    : mBitField(bitFor(aEnum1) |
                bitFor(aEnum2) |
                bitFor(aEnum3) |
                bitFor(aEnum4))
  {
    initVersion();
  }

  MOZ_IMPLICIT EnumSet(std::initializer_list<T> list)
    : mBitField(0)
  {
    for (auto value : list) {
      (*this) += value;
    }
    initVersion();
  }

  EnumSet(const EnumSet& aEnumSet)
    : mBitField(aEnumSet.mBitField)
  {
    initVersion();
  }

  /**
   * Add an element
   */
  void operator+=(T aEnum)
  {
    incVersion();
    mBitField |= bitFor(aEnum);
  }

  /**
   * Add an element
   */
  EnumSet<T> operator+(T aEnum) const
  {
    EnumSet<T> result(*this);
    result += aEnum;
    return result;
  }

  /**
   * Union
   */
  void operator+=(const EnumSet<T> aEnumSet)
  {
    incVersion();
    mBitField |= aEnumSet.mBitField;
  }

  /**
   * Union
   */
  EnumSet<T> operator+(const EnumSet<T> aEnumSet) const
  {
    EnumSet<T> result(*this);
    result += aEnumSet;
    return result;
  }

  /**
   * Remove an element
   */
  void operator-=(T aEnum)
  {
    incVersion();
    mBitField &= ~(bitFor(aEnum));
  }

  /**
   * Remove an element
   */
  EnumSet<T> operator-(T aEnum) const
  {
    EnumSet<T> result(*this);
    result -= aEnum;
    return result;
  }

  /**
   * Remove a set of elements
   */
  void operator-=(const EnumSet<T> aEnumSet)
  {
    incVersion();
    mBitField &= ~(aEnumSet.mBitField);
  }

  /**
   * Remove a set of elements
   */
  EnumSet<T> operator-(const EnumSet<T> aEnumSet) const
  {
    EnumSet<T> result(*this);
    result -= aEnumSet;
    return result;
  }

  /**
   * Clear
   */
  void clear()
  {
    incVersion();
    mBitField = 0;
  }

  /**
   * Intersection
   */
  void operator&=(const EnumSet<T> aEnumSet)
  {
    incVersion();
    mBitField &= aEnumSet.mBitField;
  }

  /**
   * Intersection
   */
  EnumSet<T> operator&(const EnumSet<T> aEnumSet) const
  {
    EnumSet<T> result(*this);
    result &= aEnumSet;
    return result;
  }

  /**
   * Equality
   */
  bool operator==(const EnumSet<T> aEnumSet) const
  {
    return mBitField == aEnumSet.mBitField;
  }

  /**
   * Test is an element is contained in the set.
   */
  bool contains(T aEnum) const
  {
    return mBitField & bitFor(aEnum);
  }

  /**
   * Return the number of elements in the set.
   */
  uint8_t size() const
  {
    uint8_t count = 0;
    for (uint32_t bitField = mBitField; bitField; bitField >>= 1) {
      if (bitField & 1) {
        count++;
      }
    }
    return count;
  }

  bool isEmpty() const
  {
    return mBitField == 0;
  }

  uint32_t serialize() const
  {
    return mBitField;
  }

  void deserialize(uint32_t aValue)
  {
    incVersion();
    mBitField = aValue;
  }

  class ConstIterator
  {
    const EnumSet<T>* mSet;
    uint32_t mPos;
#ifdef DEBUG
    uint64_t mVersion;
#endif

    void checkVersion() {
      // Check that the set has not been modified while being iterated.
      MOZ_ASSERT_IF(mSet, mSet->mVersion == mVersion);
    }

   public:
    ConstIterator(const EnumSet<T>& aSet, uint32_t aPos)
     : mSet(&aSet), mPos(aPos)
    {
#ifdef DEBUG
      mVersion = mSet->mVersion;
#endif
      MOZ_ASSERT(aPos <= kMaxBits);
      if (aPos != kMaxBits && !mSet->contains(T(mPos)))
        ++*this;
    }

    ConstIterator(const ConstIterator& aOther)
     : mSet(aOther.mSet), mPos(aOther.mPos)
    {
#ifdef DEBUG
      mVersion = aOther.mVersion;
      checkVersion();
#endif
    }

    ConstIterator(ConstIterator&& aOther)
     : mSet(aOther.mSet), mPos(aOther.mPos)
    {
#ifdef DEBUG
      mVersion = aOther.mVersion;
      checkVersion();
#endif
      aOther.mSet = nullptr;
    }

    ~ConstIterator() {
      checkVersion();
    }

    bool operator==(const ConstIterator& other) {
      MOZ_ASSERT(mSet == other.mSet);
      checkVersion();
      return mPos == other.mPos;
    }

    bool operator!=(const ConstIterator& other) {
      return !(*this == other);
    }

    T operator*() {
      MOZ_ASSERT(mSet);
      MOZ_ASSERT(mPos < kMaxBits);
      MOZ_ASSERT(mSet->contains(T(mPos)));
      checkVersion();
      return T(mPos);
    }

    ConstIterator& operator++() {
      MOZ_ASSERT(mSet);
      MOZ_ASSERT(mPos < kMaxBits);
      checkVersion();
      do {
        mPos++;
      } while (mPos < kMaxBits && !mSet->contains(T(mPos)));
      return *this;
    }
  };

  ConstIterator begin() const {
    return ConstIterator(*this, 0);
  }

  ConstIterator end() const {
    return ConstIterator(*this, kMaxBits);
  }

private:
  static uint32_t bitFor(T aEnum)
  {
    uint32_t bitNumber = (uint32_t)aEnum;
    MOZ_ASSERT(bitNumber < kMaxBits);
    return 1U << bitNumber;
  }

  void initVersion() {
#ifdef DEBUG
    mVersion = 0;
#endif
  }

  void incVersion() {
#ifdef DEBUG
    mVersion++;
#endif
  }

  static const size_t kMaxBits = 32;
  uint32_t mBitField;

#ifdef DEBUG
  uint64_t mVersion = 0;
#endif
};

} // namespace mozilla

#endif /* mozilla_EnumSet_h_*/