// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2008-2014 Alexander Warg <warg@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * License: see LICENSE.spdx (in this directory or the directories above)
 */

#pragma once

namespace cxx {

/**
 * Basic bitmap abstraction.
 *
 * This abstraction keeps a pointer to a memory area that is used as bitmap.
 */
class Bitmap_base
{
protected:
  /**
   * Data type for each element of the bit buffer.
   */
  typedef unsigned long word_type;

  enum
  {
    W_bits = sizeof(word_type) * 8, ///< number of bits in word_type
    C_bits = 8,                     ///< number of bits in char
  };

  /**
   * Pointer to the buffer storing the bits.
   */
  word_type *_bits;

  /**
   * Get the word index for the given bit.
   *
   * \param bit  The index of the bit in question.
   *
   * \return the index in Bitmap_base::_bits for the given bit (bit / W_bits).
   */
  static unsigned word_index(unsigned bit) { return bit / W_bits; }

  /**
   * Get the bit index within word_type for the given bit.
   *
   * \param bit  The bit index in the bitmap.
   *
   * \return the bit index within word_type (bit % W_bits).
   */
  static unsigned bit_index(unsigned bit) { return bit % W_bits; }

  /**
   * A writable bit in a bitmap.
   */
  class Bit
  {
    Bitmap_base *_bm;
    long _bit;

  public:
    Bit(Bitmap_base *bm, long bit) : _bm(bm), _bit(bit) {}
    Bit &operator = (bool val) { _bm->bit(_bit, val); return *this; }
    operator bool () const { return _bm->bit(_bit); }
  };

public:
  explicit Bitmap_base(void *bits) noexcept : _bits(reinterpret_cast<word_type *>(bits)) {}

  /** Get the number of `Words` that are used for the bitmap. */
  static long words(long bits) noexcept { return (bits + W_bits -1) / W_bits; }
  static long bit_buffer_bytes(long bits) noexcept
  { return words(bits) * W_bits / 8; }

  /** Helper abstraction for a word contained in the bitmap. */
  template< long BITS >
  class Word
  {
  public:
    typedef unsigned long Type;
    enum
    {
      Size = (BITS + W_bits - 1) / W_bits
    };
  };

  /** Get the number of chars that are used for the bitmap. */
  static long chars(long bits) noexcept
  { return (bits + C_bits -1) / C_bits; }

  /** Helper abstraction for a byte contained in the bitmap. */
  template< long BITS >
  class Char
  {
  public:
    typedef unsigned char Type;
    enum
    {
      Size = (BITS + C_bits - 1) / C_bits
    };
  };

  /**
   * Set the value of bit `bit` to `on`.
   *
   * \param bit  The number of the bit.
   * \param on   The boolean value that shall be assigned to the bit.
   */
  void bit(long bit, bool on) noexcept;

  /**
   * Clear bit `bit`.
   *
   * \param bit  The number of the bit to clear.
   */
  void clear_bit(long bit) noexcept;

  /**
   * Clear bit `bit` atomically.
   *
   * Use this function for multi-threaded access to the bitmap.
   *
   * \param bit  The number of the bit to clear.
   */
  void atomic_clear_bit(long bit) noexcept;

  /**
   * Clear bit `bit` atomically and return old state.
   *
   * Use this function for multi-threaded access to the bitmap.
   *
   * \param bit  The number of the bit to clear.
   */
  word_type atomic_get_and_clear(long bit) noexcept;

  /**
   * Set bit `bit`.
   *
   * \param bit  The number of the bit to set.
   */
  void set_bit(long bit) noexcept;

  /**
   * Set bit `bit` atomically.
   *
   * Use this function for multi-threaded access to the bitmap.
   *
   * \param bit  The number of the bit to set.
   */
  void atomic_set_bit(long bit) noexcept;

  /**
   * Set bit `bit` atomically and return old state.
   *
   * Use this function for multi-threaded access to the bitmap.
   *
   * \param bit  The number of the bit to set.
   */
  word_type atomic_get_and_set(long bit) noexcept;

  /**
   * Get the truth value of a bit.
   *
   * \param bit  The number of the bit to read.
   *
   * \retval 0     Bit is not set.
   * \retval != 0  Bit is set.
   */
  word_type bit(long bit) const noexcept;

  /**
   * Get the bit at index `bit`.
   *
   * \param bit  The number of the bit to read.
   *
   * \retval 0     Bit is not set.
   * \retval != 0  Bit is set.
   */
  word_type operator [] (long bit) const noexcept
  { return this->bit(bit); }

  /**
   * Get the lvalue for the bit at index `bit`.
   *
   * \param bit  The number.
   *
   * \return lvalue for `bit`
   */
  Bit operator [] (long bit) noexcept
  { return Bit(this, bit); }

  /**
   * Scan for the first zero bit.
   *
   * \param max_bit    Upper bound (exclusive) for the scanning operation.
   * \param start_bit  Hint at the number of the first bit to look at.
   *                   Zero bits below `start_bit` may or may not be
   *                   taken into account by the implementation.
   *
   * \retval >= 0  Number of first zero bit found.
   * \retval -1    All bits between `start_bit` and `max_bit` are set.
   */
  long scan_zero(long max_bit, long start_bit = 0) const noexcept;

  void *bit_buffer() const noexcept { return _bits; }

protected:
  static int _bzl(unsigned long w) noexcept;
};


/**
 * A static bitmap.
 *
 * \tparam BITS  The number of bits that shall be in the bitmap.
 */
template<int BITS>
class Bitmap : public Bitmap_base
{
private:
  char _bits[Bitmap_base::Char<BITS>::Size];

public:
  /** Create a bitmap with `BITS` bits. */
  Bitmap() noexcept : Bitmap_base(_bits) {}
  Bitmap(Bitmap<BITS> const &o) noexcept : Bitmap_base(_bits)
  { __builtin_memcpy(_bits, o._bits, sizeof(_bits)); }
  /**
   * Scan for the first zero bit.
   *
   * \param start_bit  Hint at the number of the first bit to look at.
   *                   Zero bits below `start_bit` may or may not be
   *                   taken into account by the implementation.
   *
   * \retval >= 0  Number of first zero bit found.
   * \retval -1    All bits at `start_bit` or higher are set.
   *
   * Compared to Bitmap_base::scan_zero(), the upper bound is set to BITS.
   */
  long scan_zero(long start_bit = 0) const noexcept;

  void clear_all()
  { __builtin_memset(_bits, 0, sizeof(_bits)); }
};


inline
void
Bitmap_base::bit(long bit, bool on) noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  _bits[idx] = (_bits[idx] & ~(1UL << b)) | (static_cast<unsigned long>(on) << b);
}

inline
void
Bitmap_base::clear_bit(long bit) noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  _bits[idx] &= ~(1UL << b);
}

inline
void
Bitmap_base::atomic_clear_bit(long bit) noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  word_type mask = 1UL << b;
  __atomic_and_fetch(&_bits[idx], ~mask, __ATOMIC_RELAXED);
}

inline
Bitmap_base::word_type
Bitmap_base::atomic_get_and_clear(long bit) noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  word_type mask = 1UL << b;
  return __atomic_fetch_and(&_bits[idx], ~mask, __ATOMIC_RELAXED) & mask;
}

inline
void
Bitmap_base::set_bit(long bit) noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  _bits[idx] |= (1UL << b);
}

inline
void
Bitmap_base::atomic_set_bit(long bit) noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  word_type mask = 1UL << b;
  __atomic_or_fetch(&_bits[idx], mask, __ATOMIC_RELAXED);
}

inline
Bitmap_base::word_type
Bitmap_base::atomic_get_and_set(long bit) noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  word_type mask = 1UL << b;
  return __atomic_fetch_or(&_bits[idx], mask, __ATOMIC_RELAXED) & mask;
}

inline
Bitmap_base::word_type
Bitmap_base::bit(long bit) const noexcept
{
  long idx = word_index(bit);
  long b   = bit_index(bit);
  return _bits[idx] & (1UL << b);
}

inline
int
Bitmap_base::_bzl(unsigned long w) noexcept
{
  for (int i = 0; i < W_bits; ++i, w >>= 1)
    {
      if ((w & 1) == 0)
        return i;
    }
  return -1;
}

inline
long
Bitmap_base::scan_zero(long max_bit, long start_bit) const noexcept
{
  if (!(operator [] (start_bit)))
    return start_bit;

  long idx = word_index(start_bit);

  max_bit -= start_bit & ~(W_bits - 1);

  for (; max_bit > 0; max_bit -= W_bits, ++idx)
    {
      if (_bits[idx] == 0)
        return idx * W_bits;

      if (_bits[idx] != ~0UL)
        {
          long zbit = _bzl(_bits[idx]);
          return zbit < max_bit ? idx * W_bits + zbit : -1;
        }
    }

  return -1;
}

template<int BITS> inline
long
Bitmap<BITS>::scan_zero(long start_bit) const noexcept
{
  return Bitmap_base::scan_zero(BITS, start_bit);
}

};
