//===-- include/flang/Common/constexpr-bitset.h -----------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_COMMON_CONSTEXPR_BITSET_H_
#define FORTRAN_COMMON_CONSTEXPR_BITSET_H_

// Implements a replacement for std::bitset<> that is suitable for use
// in constexpr expressions.  Limited to elements in [0..63].

#include "bit-population-count.h"
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <optional>
#include <type_traits>

namespace Fortran::common {

template <int BITS> class BitSet {
  static_assert(BITS > 0 && BITS <= 64);
  static constexpr bool partialWord{BITS != 32 && BITS != 64};
  using Word = std::conditional_t<(BITS > 32), std::uint64_t, std::uint32_t>;
  static constexpr Word allBits{
      partialWord ? (static_cast<Word>(1) << BITS) - 1 : ~static_cast<Word>(0)};

  constexpr BitSet(Word b) : bits_{b} {}

public:
  constexpr BitSet() {}
  constexpr BitSet(const std::initializer_list<int> &xs) {
    for (auto x : xs) {
      set(x);
    }
  }
  constexpr BitSet(const BitSet &) = default;
  constexpr BitSet(BitSet &&) = default;
  constexpr BitSet &operator=(const BitSet &) = default;
  constexpr BitSet &operator=(BitSet &&) = default;

  constexpr BitSet &operator&=(const BitSet &that) {
    bits_ &= that.bits_;
    return *this;
  }
  constexpr BitSet &operator&=(BitSet &&that) {
    bits_ &= that.bits_;
    return *this;
  }
  constexpr BitSet &operator^=(const BitSet &that) {
    bits_ ^= that.bits_;
    return *this;
  }
  constexpr BitSet &operator^=(BitSet &&that) {
    bits_ ^= that.bits_;
    return *this;
  }
  constexpr BitSet &operator|=(const BitSet &that) {
    bits_ |= that.bits_;
    return *this;
  }
  constexpr BitSet &operator|=(BitSet &&that) {
    bits_ |= that.bits_;
    return *this;
  }

  constexpr BitSet operator~() const { return ~bits_; }
  constexpr BitSet operator&(const BitSet &that) const {
    return bits_ & that.bits_;
  }
  constexpr BitSet operator&(BitSet &&that) const { return bits_ & that.bits_; }
  constexpr BitSet operator^(const BitSet &that) const {
    return bits_ ^ that.bits_;
  }
  constexpr BitSet operator^(BitSet &&that) const { return bits_ & that.bits_; }
  constexpr BitSet operator|(const BitSet &that) const {
    return bits_ | that.bits_;
  }
  constexpr BitSet operator|(BitSet &&that) const { return bits_ | that.bits_; }

  constexpr bool operator==(const BitSet &that) const {
    return bits_ == that.bits_;
  }
  constexpr bool operator==(BitSet &&that) const { return bits_ == that.bits_; }
  constexpr bool operator!=(const BitSet &that) const {
    return bits_ != that.bits_;
  }
  constexpr bool operator!=(BitSet &&that) const { return bits_ != that.bits_; }

  static constexpr std::size_t size() { return BITS; }
  constexpr bool test(std::size_t x) const {
    return x < BITS && ((bits_ >> x) & 1) != 0;
  }

  constexpr bool all() const { return bits_ == allBits; }
  constexpr bool any() const { return bits_ != 0; }
  constexpr bool none() const { return bits_ == 0; }

  constexpr std::size_t count() const { return BitPopulationCount(bits_); }

  constexpr BitSet &set() {
    bits_ = allBits;
    return *this;
  }
  constexpr BitSet set(std::size_t x, bool value = true) {
    if (!value) {
      return reset(x);
    } else {
      bits_ |= static_cast<Word>(1) << x;
      return *this;
    }
  }
  constexpr BitSet &reset() {
    bits_ = 0;
    return *this;
  }
  constexpr BitSet &reset(std::size_t x) {
    bits_ &= ~(static_cast<Word>(1) << x);
    return *this;
  }
  constexpr BitSet &flip() {
    bits_ ^= allBits;
    return *this;
  }
  constexpr BitSet &flip(std::size_t x) {
    bits_ ^= static_cast<Word>(1) << x;
    return *this;
  }

  constexpr std::optional<std::size_t> LeastElement() const {
    if (bits_ == 0) {
      return std::nullopt;
    } else {
      return {TrailingZeroBitCount(bits_)};
    }
  }

  Word bits() const { return bits_; }

private:
  Word bits_{0};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_CONSTEXPR_BITSET_H_
