// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0
//
// 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.

#ifndef HIGHWAY_HWY_BIT_SET_H_
#define HIGHWAY_HWY_BIT_SET_H_

// Various BitSet for 64, up to 4096, or any number of bits.

#include <stddef.h>

#include <atomic>

#include "third_party/highway/hwy/base.h"

namespace hwy {

// 64-bit specialization of `std::bitset`, which lacks `Foreach`.
class BitSet64 {
 public:
  constexpr size_t MaxSize() const { return 64; }

  // No harm if `i` is already set.
  void Set(size_t i) {
    HWY_DASSERT(i < 64);
    bits_ |= (1ULL << i);
    HWY_DASSERT(Get(i));
  }

  // Equivalent to Set(i) for i in [0, 64) where (bits >> i) & 1. This does
  // not clear any existing bits.
  void SetNonzeroBitsFrom64(uint64_t bits) { bits_ |= bits; }

  void Clear(size_t i) {
    HWY_DASSERT(i < 64);
    bits_ &= ~(1ULL << i);
  }

  bool Get(size_t i) const {
    HWY_DASSERT(i < 64);
    return (bits_ & (1ULL << i)) != 0;
  }

  // Returns true if Get(i) would return true for any i in [0, 64).
  bool Any() const { return bits_ != 0; }

  // Returns true if Get(i) would return true for all i in [0, 64).
  bool All() const { return bits_ == ~uint64_t{0}; }

  // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`!
  size_t First() const {
    HWY_DASSERT(Any());
    return Num0BitsBelowLS1Bit_Nonzero64(bits_);
  }

  // Returns lowest i such that `!Get(i)`. Caller must first ensure `!All()`!
  size_t First0() const {
    HWY_DASSERT(!All());
    return Num0BitsBelowLS1Bit_Nonzero64(~bits_);
  }

  // Returns uint64_t(Get(i)) << i for i in [0, 64).
  uint64_t Get64() const { return bits_; }

  // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify
  // the set, but the current Foreach call is unaffected.
  template <class Func>
  void Foreach(const Func& func) const {
    uint64_t remaining_bits = bits_;
    while (remaining_bits != 0) {
      const size_t i = Num0BitsBelowLS1Bit_Nonzero64(remaining_bits);
      remaining_bits &= remaining_bits - 1;  // clear LSB
      func(i);
    }
  }

  size_t Count() const { return PopCount(bits_); }

 private:
  uint64_t bits_ = 0;
};

// Any number of bits, flat array.
template <size_t kMaxSize>
class BitSet {
  static_assert(kMaxSize != 0, "BitSet requires non-zero size");

 public:
  constexpr size_t MaxSize() const { return kMaxSize; }

  // No harm if `i` is already set.
  void Set(size_t i) {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    bits_[idx].Set(mod);
  }

  void Clear(size_t i) {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    bits_[idx].Clear(mod);
    HWY_DASSERT(!Get(i));
  }

  bool Get(size_t i) const {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    return bits_[idx].Get(mod);
  }

  // Returns true if Get(i) would return true for any i in [0, kMaxSize).
  bool Any() const {
    for (const BitSet64& bits : bits_) {
      if (bits.Any()) return true;
    }
    return false;
  }

  // Returns true if Get(i) would return true for all i in [0, kMaxSize).
  bool All() const {
    for (size_t idx = 0; idx < kNum64 - 1; ++idx) {
      if (!bits_[idx].All()) return false;
    }

    constexpr size_t kRemainder = kMaxSize % 64;
    if (kRemainder == 0) {
      return bits_[kNum64 - 1].All();
    }
    return bits_[kNum64 - 1].Count() == kRemainder;
  }

  // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`!
  size_t First() const {
    HWY_DASSERT(Any());
    for (size_t idx = 0;; ++idx) {
      HWY_DASSERT(idx < kNum64);
      if (bits_[idx].Any()) return idx * 64 + bits_[idx].First();
    }
  }

  // Returns lowest i such that `!Get(i)`. Caller must first ensure `All()`!
  size_t First0() const {
    HWY_DASSERT(!All());
    for (size_t idx = 0;; ++idx) {
      HWY_DASSERT(idx < kNum64);
      if (!bits_[idx].All()) {
        const size_t first0 = idx * 64 + bits_[idx].First0();
        HWY_DASSERT(first0 < kMaxSize);
        return first0;
      }
    }
  }

  // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify
  // the set, but the current Foreach call is only affected if changing one of
  // the not yet visited BitSet64.
  template <class Func>
  void Foreach(const Func& func) const {
    for (size_t idx = 0; idx < kNum64; ++idx) {
      bits_[idx].Foreach([idx, &func](size_t mod) { func(idx * 64 + mod); });
    }
  }

  size_t Count() const {
    size_t total = 0;
    for (const BitSet64& bits : bits_) {
      total += bits.Count();
    }
    return total;
  }

 private:
  static constexpr size_t kNum64 = DivCeil(kMaxSize, size_t{64});
  BitSet64 bits_[kNum64];
};

// Any number of bits, flat array, atomic updates to the u64.
template <size_t kMaxSize>
class AtomicBitSet {
  static_assert(kMaxSize != 0, "AtomicBitSet requires non-zero size");

  // Bits may signal something to other threads, hence relaxed is insufficient.
  // Acq/Rel ensures a happens-before relationship.
  static constexpr auto kAcq = std::memory_order_acquire;
  static constexpr auto kRel = std::memory_order_release;

 public:
  constexpr size_t MaxSize() const { return kMaxSize; }

  // No harm if `i` is already set.
  void Set(size_t i) {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    bits_[idx].fetch_or(1ULL << mod, kRel);
  }

  void Clear(size_t i) {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    bits_[idx].fetch_and(~(1ULL << mod), kRel);
    HWY_DASSERT(!Get(i));
  }

  bool Get(size_t i) const {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    return ((bits_[idx].load(kAcq) & (1ULL << mod))) != 0;
  }

  // Returns true if Get(i) would return true for any i in [0, kMaxSize).
  bool Any() const {
    for (const std::atomic<uint64_t>& bits : bits_) {
      if (bits.load(kAcq)) return true;
    }
    return false;
  }

  // Returns true if Get(i) would return true for all i in [0, kMaxSize).
  bool All() const {
    for (size_t idx = 0; idx < kNum64 - 1; ++idx) {
      if (bits_[idx].load(kAcq) != ~uint64_t{0}) return false;
    }

    constexpr size_t kRemainder = kMaxSize % 64;
    const uint64_t last_bits = bits_[kNum64 - 1].load(kAcq);
    if (kRemainder == 0) {
      return last_bits == ~uint64_t{0};
    }
    return PopCount(last_bits) == kRemainder;
  }

  // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`!
  size_t First() const {
    HWY_DASSERT(Any());
    for (size_t idx = 0;; ++idx) {
      HWY_DASSERT(idx < kNum64);
      const uint64_t bits = bits_[idx].load(kAcq);
      if (bits != 0) {
        return idx * 64 + Num0BitsBelowLS1Bit_Nonzero64(bits);
      }
    }
  }

  // Returns lowest i such that `!Get(i)`. Caller must first ensure `!All()`!
  size_t First0() const {
    HWY_DASSERT(!All());
    for (size_t idx = 0;; ++idx) {
      HWY_DASSERT(idx < kNum64);
      const uint64_t inv_bits = ~bits_[idx].load(kAcq);
      if (inv_bits != 0) {
        const size_t first0 =
            idx * 64 + Num0BitsBelowLS1Bit_Nonzero64(inv_bits);
        HWY_DASSERT(first0 < kMaxSize);
        return first0;
      }
    }
  }

  // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify
  // the set, but the current Foreach call is only affected if changing one of
  // the not yet visited uint64_t.
  template <class Func>
  void Foreach(const Func& func) const {
    for (size_t idx = 0; idx < kNum64; ++idx) {
      uint64_t remaining_bits = bits_[idx].load(kAcq);
      while (remaining_bits != 0) {
        const size_t i = Num0BitsBelowLS1Bit_Nonzero64(remaining_bits);
        remaining_bits &= remaining_bits - 1;  // clear LSB
        func(idx * 64 + i);
      }
    }
  }

  size_t Count() const {
    size_t total = 0;
    for (const std::atomic<uint64_t>& bits : bits_) {
      total += PopCount(bits.load(kAcq));
    }
    return total;
  }

 private:
  static constexpr size_t kNum64 = DivCeil(kMaxSize, size_t{64});
  std::atomic<uint64_t> bits_[kNum64] = {};
};

// Two-level bitset for up to `kMaxSize` <= 4096 values. The iterators
// (`Any/First/Foreach/Count`) are more efficient than `BitSet` for sparse sets.
// This comes at the cost of slightly slower mutators (`Set/Clear`).
template <size_t kMaxSize = 4096>
class BitSet4096 {
  static_assert(kMaxSize != 0, "BitSet4096 requires non-zero size");

 public:
  constexpr size_t MaxSize() const { return kMaxSize; }

  // No harm if `i` is already set.
  void Set(size_t i) {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    bits_[idx].Set(mod);
    nonzero_.Set(idx);
    HWY_DASSERT(Get(i));
  }

  // Equivalent to Set(i) for i in [0, 64) where (bits >> i) & 1. This does
  // not clear any existing bits.
  void SetNonzeroBitsFrom64(uint64_t bits) {
    bits_[0].SetNonzeroBitsFrom64(bits);
    if (bits) nonzero_.Set(0);
  }

  void Clear(size_t i) {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    bits_[idx].Clear(mod);
    if (!bits_[idx].Any()) {
      nonzero_.Clear(idx);
    }
    HWY_DASSERT(!Get(i));
  }

  bool Get(size_t i) const {
    HWY_DASSERT(i < kMaxSize);
    const size_t idx = i / 64;
    const size_t mod = i % 64;
    return bits_[idx].Get(mod);
  }

  // Returns true if `Get(i)` would return true for any i in [0, kMaxSize).
  bool Any() const { return nonzero_.Any(); }

  // Returns true if `Get(i)` would return true for all i in [0, kMaxSize).
  bool All() const {
    // Do not check `nonzero_.All()` - that only works if `kMaxSize` is 4096.
    if (nonzero_.Count() != kNum64) return false;
    return Count() == kMaxSize;
  }

  // Returns lowest i such that `Get(i)`. Caller must first ensure `Any()`!
  size_t First() const {
    HWY_DASSERT(Any());
    const size_t idx = nonzero_.First();
    return idx * 64 + bits_[idx].First();
  }

  // Returns lowest i such that `!Get(i)`. Caller must first ensure `!All()`!
  size_t First0() const {
    HWY_DASSERT(!All());
    // It is likely not worthwhile to have a separate `BitSet64` for `not_all_`,
    // hence iterate over all u64.
    for (size_t idx = 0;; ++idx) {
      HWY_DASSERT(idx < kNum64);
      if (!bits_[idx].All()) {
        const size_t first0 = idx * 64 + bits_[idx].First0();
        HWY_DASSERT(first0 < kMaxSize);
        return first0;
      }
    }
  }

  // Returns uint64_t(Get(i)) << i for i in [0, 64).
  uint64_t Get64() const { return bits_[0].Get64(); }

  // Calls `func(i)` for each `i` in the set. It is safe for `func` to modify
  // the set, but the current Foreach call is only affected if changing one of
  // the not yet visited BitSet64 for which Any() is true.
  template <class Func>
  void Foreach(const Func& func) const {
    nonzero_.Foreach([&func, this](size_t idx) {
      bits_[idx].Foreach([idx, &func](size_t mod) { func(idx * 64 + mod); });
    });
  }

  size_t Count() const {
    size_t total = 0;
    nonzero_.Foreach(
        [&total, this](size_t idx) { total += bits_[idx].Count(); });
    return total;
  }

 private:
  static_assert(kMaxSize <= 64 * 64, "One BitSet64 insufficient");
  static constexpr size_t kNum64 = DivCeil(kMaxSize, size_t{64});
  BitSet64 nonzero_;
  BitSet64 bits_[kNum64];
};

}  // namespace hwy

#endif  // HIGHWAY_HWY_BIT_SET_H_
