// Copyright (c) 2016, the SDSL Project Authors.  All rights reserved.
// Please see the AUTHORS file for details.  Use of this source code is governed
// by a BSD license that can be found in the LICENSE file.
/*!\file bit_vector_il.hpp
 * \brief bit_vector_il.hpp contains the sdsl::bit_vector_il class, and
 * classes which support rank and select for bit_vector_il.
 * \author Matthias Petri, Simon Gog
 */
#ifndef SDSL_BIT_VECTOR_IL
#define SDSL_BIT_VECTOR_IL

#include <queue>

#include <sdsl/int_vector.hpp>
#include <sdsl/iterators.hpp>
#include <sdsl/util.hpp>

//! Namespace for the succinct data structure library
namespace sdsl
{

template <uint8_t t_b = 1, uint32_t t_bs = 512> // forward declaration needed for friend declaration
class rank_support_il;                          // in bit_vector_il

template <uint8_t t_b = 1, uint32_t t_bs = 512> // forward declaration needed for friend declaration
class select_support_il;                        // in bit_vector_il

template <class T>
constexpr bool power_of_two(T x)
{
    return std::is_integral<T>::value and x > 1 and !(x & (x - 1));
}

//! A bit vector which interleaves the original bit_vector with rank information.
/*!
 * This class is a uncompressed bit vector representation. It copies the original
 * bit_vector and interleaves the data every t_bs bits with a cumulative
 * sum of set bits before the current position. Each cumulative sum is stored
 * in a 64 bit word.
 *
 * \tparam t_bs Block size in bits. t_bs has to be a power of 2 and t_bs >= 64.
 */
template <uint32_t t_bs = 512>
class bit_vector_il
{
    static_assert(t_bs >= 64, "bit_vector_il: blocksize must be be at least 64 bits.");
    static_assert(power_of_two(t_bs), "bit_vector_il: blocksize must be a power of two.");

  public:
    typedef bit_vector::size_type size_type;
    typedef size_type value_type;
    typedef bit_vector::difference_type difference_type;
    typedef random_access_const_iterator<bit_vector_il> iterator;
    typedef iterator const_iterator;
    typedef bv_tag index_category;

    friend class rank_support_il<1, t_bs>;
    friend class rank_support_il<0, t_bs>;
    friend class select_support_il<1, t_bs>;
    friend class select_support_il<0, t_bs>;

    typedef rank_support_il<1, t_bs> rank_1_type;
    typedef rank_support_il<0, t_bs> rank_0_type;
    typedef select_support_il<1, t_bs> select_1_type;
    typedef select_support_il<0, t_bs> select_0_type;

  private:
    size_type m_size = 0;        //!< Size of the original bitvector
    size_type m_block_num = 0;   //!< Total size of m_data in uint64_t ss
    size_type m_superblocks = 0; //!< Number of superblocks
    size_type m_block_shift = 0;
    int_vector<64> m_data;         //!< Data container
    int_vector<64> m_rank_samples; //!< Space for additional rank samples

    // precondition: m_rank_samples.size() <= m_superblocks
    void init_rank_samples()
    {
        uint32_t blockSize_U64 = bits::hi(t_bs >> 6);
        size_type idx = 0;
        std::queue<size_type> lbs, rbs;
        lbs.push(0);
        rbs.push(m_superblocks);
        while (!lbs.empty())
        {
            size_type lb = lbs.front();
            lbs.pop();
            size_type rb = rbs.front();
            rbs.pop();
            if (/*lb < rb and*/ idx < m_rank_samples.size())
            {
                size_type mid = lb + (rb - lb) / 2; // select mid \in [lb..rb)
                size_type pos = (mid << blockSize_U64) + mid;
                m_rank_samples[idx++] = m_data[pos];
                lbs.push(lb);
                rbs.push(mid);
                lbs.push(mid + 1);
                rbs.push(rb);
            }
        }
    }

  public:
    bit_vector_il() {}
    bit_vector_il(const bit_vector_il &) = default;
    bit_vector_il(bit_vector_il &&) = default;
    bit_vector_il & operator=(const bit_vector_il &) = default;
    bit_vector_il & operator=(bit_vector_il &&) = default;

    bit_vector_il(const bit_vector & bv)
    {
        m_size = bv.size();
        /* calculate the number of superblocks */
        //          each block of size > 0 gets suberblock in which we store the cumulative sum up to this block
        m_superblocks = (m_size + t_bs) / t_bs;
        m_block_shift = bits::hi(t_bs);
        /* allocate new data */
        size_type blocks = (m_size + 64) / 64;
        size_type mem = blocks + m_superblocks + 1;
        //                          ^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^   ^
        //                          bit vector data | cum. sum data | sum after last block
        m_data = int_vector<64>(mem);
        m_block_num = mem;

        /* assign data and calculate super block values */
        const uint64_t * bvp = bv.data();

        size_type j = 0; // 64-bit word counter in the m_data
        size_type cum_sum = 0;
        size_type sample_rate = t_bs / 64;
        for (size_type i = 0, sample_cnt = sample_rate; i < blocks; ++i, ++sample_cnt)
        {
            if (sample_cnt == sample_rate)
            {
                m_data[j] = cum_sum;
                sample_cnt = 0;
                j++;
            }
            m_data[j] = bvp[i];
            cum_sum += bits::cnt(m_data[j]);
            j++;
        }
        m_data[j] = cum_sum; /* last superblock so we can always
                                    get num_ones fast */
        if (m_block_num > 1024 * 64)
        {
            // we store at most m_superblocks+1 rank_samples:
            // we do a cache efficient binary search for the select on X=1024
            // or X=the smallest power of two smaller than m_superblock
            m_rank_samples.resize(std::min(1024ULL, 1ULL << bits::hi(m_superblocks)));
        }
        init_rank_samples();
    }

    //! Accessing the i-th element of the original bit_vector
    /*!\param i An index i with \f$ 0 \leq i < size()  \f$.
     *  \return The i-th bit of the original bit_vector
     *  \par Time complexity
     *     \f$ \Order{1} \f$
     */
    value_type operator[](size_type i) const
    {
        assert(i < m_size);
        size_type bs = i >> m_block_shift;
        size_type block = bs + (i >> 6) + 1;
        return ((m_data[block] >> (i & 63)) & 1ULL);
    }

    //! Get the integer value of the binary string of length len starting at position idx.
    /*!\param idx Starting index of the binary representation of the integer.
     *  \param len Length of the binary representation of the integer. Default value is 64.
     *   \returns The integer value of the binary string of length len starting at position idx.
     *
     *  \pre idx+len-1 in [0..size()-1]
     *  \pre len in [1..64]
     */
    uint64_t get_int(size_type idx, uint8_t len = 64) const
    {
        assert(idx + len - 1 < m_size);
        size_type bs = idx >> m_block_shift;
        size_type b_block = bs + (idx >> 6) + 1;
        bs = (idx + len - 1) >> m_block_shift;
        size_type e_block = bs + ((idx + len - 1) >> 6) + 1;
        if (b_block == e_block)
        { // spans on block
            return (m_data[b_block] >> (idx & 63)) & bits::lo_set[len];
        }
        else
        { // spans two blocks
            uint8_t b_len = 64 - (idx & 63);
            return (m_data[b_block] >> (idx & 63)) | (m_data[e_block] & bits::lo_set[len - b_len]) << b_len;
        }
    }

    //! Returns the size of the original bit vector.
    size_type size() const { return m_size; }

    //! Serializes the data structure into the given ostream
    size_type serialize(std::ostream & out, structure_tree_node * v = nullptr, std::string name = "") const
    {
        structure_tree_node * child = structure_tree::add_child(v, name, util::class_name(*this));
        size_type written_bytes = 0;
        written_bytes += write_member(m_size, out, child, "size");
        written_bytes += write_member(m_block_num, out, child, "block_num");
        written_bytes += write_member(m_superblocks, out, child, "superblocks");
        written_bytes += write_member(m_block_shift, out, child, "block_shift");
        written_bytes += m_data.serialize(out, child, "data");
        written_bytes += m_rank_samples.serialize(out, child, "rank_samples");
        structure_tree::add_size(child, written_bytes);
        return written_bytes;
    }

    //! Loads the data structure from the given istream.
    void load(std::istream & in)
    {
        read_member(m_size, in);
        read_member(m_block_num, in);
        read_member(m_superblocks, in);
        read_member(m_block_shift, in);
        m_data.load(in);
        m_rank_samples.load(in);
    }

    template <typename archive_t>
    void CEREAL_SAVE_FUNCTION_NAME(archive_t & ar) const
    {
        ar(CEREAL_NVP(m_size));
        ar(CEREAL_NVP(m_block_num));
        ar(CEREAL_NVP(m_superblocks));
        ar(CEREAL_NVP(m_block_shift));
        ar(CEREAL_NVP(m_data));
        ar(CEREAL_NVP(m_rank_samples));
    }

    template <typename archive_t>
    void CEREAL_LOAD_FUNCTION_NAME(archive_t & ar)
    {
        ar(CEREAL_NVP(m_size));
        ar(CEREAL_NVP(m_block_num));
        ar(CEREAL_NVP(m_superblocks));
        ar(CEREAL_NVP(m_block_shift));
        ar(CEREAL_NVP(m_data));
        ar(CEREAL_NVP(m_rank_samples));
    }

    iterator begin() const { return iterator(this, 0); }

    iterator end() const { return iterator(this, size()); }

    bool operator==(const bit_vector_il & v) const { return m_size == v.m_size && m_data == v.m_data; }

    bool operator!=(const bit_vector_il & v) const { return !(*this == v); }
};

template <uint8_t t_b, uint32_t t_bs>
class rank_support_il
{
    static_assert(t_b == 1 or t_b == 0, "rank_support_il only supports bitpatterns 0 or 1.");

  public:
    typedef bit_vector::size_type size_type;
    typedef bit_vector_il<t_bs> bit_vector_type;
    enum
    {
        bit_pat = t_b
    };
    enum
    {
        bit_pat_len = (uint8_t)1
    };

  private:
    const bit_vector_type * m_v;
    size_type m_block_shift;
    size_type m_block_mask;
    size_type m_block_size_U64; //! Size of superblocks in 64-bit words

    inline size_type rank1(size_type i) const
    {
        size_type SBlockNum = i >> m_block_shift;
        size_type SBlockPos = (SBlockNum << m_block_size_U64) + SBlockNum;
        uint64_t resp = m_v->m_data[SBlockPos];
        const uint64_t * B = (m_v->m_data.data() + (SBlockPos + 1));
        uint64_t rem = i & 63;
        uint64_t bits = (i & m_block_mask) - rem;
        while (bits)
        {
            resp += bits::cnt(*B++);
            bits -= 64;
        }
        resp += bits::cnt(*B & bits::lo_set[rem]);
        return resp;
    }

    inline size_type rank0(size_type i) const
    {
        size_type SBlockNum = i >> m_block_shift;
        size_type SBlockPos = (SBlockNum << m_block_size_U64) + SBlockNum;
        uint64_t resp = (SBlockNum << m_block_shift) - m_v->m_data[SBlockPos];
        const uint64_t * B = (m_v->m_data.data() + (SBlockPos + 1));
        uint64_t rem = i & 63;
        uint64_t bits = (i & m_block_mask) - rem;
        while (bits)
        {
            resp += bits::cnt(~(*B));
            B++;
            bits -= 64;
        }
        resp += bits::cnt((~(*B)) & bits::lo_set[rem]);
        return resp;
    }

  public:
    rank_support_il(const bit_vector_type * v = nullptr)
    {
        set_vector(v);
        m_block_shift = bits::hi(t_bs);
        m_block_mask = t_bs - 1;
        m_block_size_U64 = bits::hi(t_bs >> 6);
    }

    //! Returns the position of the i-th occurrence in the bit vector.
    size_type rank(size_type i) const
    {
        if (t_b) return rank1(i);
        return rank0(i);
    }

    size_type operator()(size_type i) const { return rank(i); }

    size_type size() const { return m_v->size(); }

    void set_vector(const bit_vector_type * v = nullptr) { m_v = v; }

    rank_support_il & operator=(const rank_support_il & rs)
    {
        if (this != &rs) { set_vector(rs.m_v); }
        return *this;
    }

    void load(std::istream &, const bit_vector_type * v = nullptr) { set_vector(v); }

    size_type serialize(std::ostream & out, structure_tree_node * v = nullptr, std::string name = "") const
    {
        return serialize_empty_object(out, v, name, this);
    }

    template <typename archive_t>
    void CEREAL_SAVE_FUNCTION_NAME(archive_t &) const
    {}

    template <typename archive_t>
    void CEREAL_LOAD_FUNCTION_NAME(archive_t &)
    {}

    bool operator==(const rank_support_il & other) const noexcept { return (*m_v == *other.m_v); }

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

template <uint8_t t_b, uint32_t t_bs>
class select_support_il
{
    static_assert(t_b == 1 or t_b == 0, "select_support_il only supports bitpatterns 0 or 1.");

  public:
    typedef bit_vector::size_type size_type;
    typedef bit_vector_il<t_bs> bit_vector_type;
    enum
    {
        bit_pat = t_b
    };
    enum
    {
        bit_pat_len = (uint8_t)1
    };

  private:
    const bit_vector_type * m_v;
    size_type m_superblocks;
    size_type m_block_shift;
    size_type m_block_size_U64;

    //! Returns the position of the i-th occurrence in the bit vector.
    size_type select1(size_type i) const
    {
        size_type lb = 0, rb = m_v->m_superblocks; // search interval [lb..rb)
        size_type res = 0;
        size_type idx = 0; // index in m_rank_samples
        /* binary search over super blocks */
        // invariant: lb==0 or m_data[ pos(lb-1) ] < i
        //            m_data[ pos(rb) ] >= i, initial since i < rank(size())
        while (lb < rb)
        {
            size_type mid = (lb + rb) / 2; // select mid \in [lb..rb)
#ifndef NOSELCACHE
            if (idx < m_v->m_rank_samples.size())
            {
                if (m_v->m_rank_samples[idx] >= i)
                {
                    idx = (idx << 1) + 1;
                    rb = mid;
                }
                else
                {
                    idx = (idx << 1) + 2;
                    lb = mid + 1;
                }
            }
            else
            {
#endif
                size_type pos = (mid << m_block_size_U64) + mid;
                //                                  ^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^
                //                                    data blocks to jump      superblock position
                if (m_v->m_data[pos] >= i) { rb = mid; }
                else
                {
                    lb = mid + 1;
                }
#ifndef NOSELCACHE
            }
#endif
        }
        res = (rb - 1) << m_block_shift;
        /* iterate in 64 bit steps */
        const uint64_t * w = m_v->m_data.data() + ((rb - 1) << m_block_size_U64) + (rb - 1);
        i -= *w; // subtract the cumulative sum before the superblock
        ++w;     /* step into the data */
        size_type ones = bits::cnt(*w);
        while (ones < i)
        {
            i -= ones;
            ++w;
            ones = bits::cnt(*w);
            res += 64;
        }
        /* handle last word */
        res += bits::sel(*w, i);
        return res;
    }

    //! Returns the position of the i-th occurrence in the bit vector.
    size_type select0(size_type i) const
    {
        size_type lb = 0, rb = m_v->m_superblocks; // search interval [lb..rb)
        size_type res = 0;
        size_type idx = 0; // index in m_rank_samples
        /* binary search over super blocks */
        // invariant: lb==0 or m_data[ pos(lb-1) ] < i
        //            m_data[ pos(rb) ] >= i, initial since i < rank(size())
        while (lb < rb)
        {
            size_type mid = (lb + rb) / 2; // select mid \in [lb..rb)
#ifndef NOSELCACHE
            if (idx < m_v->m_rank_samples.size())
            {
                if (((mid << m_block_shift) - m_v->m_rank_samples[idx]) >= i)
                {
                    idx = (idx << 1) + 1;
                    rb = mid;
                }
                else
                {
                    idx = (idx << 1) + 2;
                    lb = mid + 1;
                }
            }
            else
            {
#endif
                size_type pos = (mid << m_block_size_U64) + mid;
                //                                  ^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^
                //                                    data blocks to jump      superblock position
                if (((mid << m_block_shift) - m_v->m_data[pos]) >= i) { rb = mid; }
                else
                {
                    lb = mid + 1;
                }
#ifndef NOSELCACHE
            }
#endif
        }
        res = (rb - 1) << m_block_shift;

        /* iterate in 64 bit steps */
        const uint64_t * w = m_v->m_data.data() + ((rb - 1) << m_block_size_U64) + (rb - 1);
        i = i - (res - *w); // substract the cumulative sum before the superblock
        ++w;                /* step into the data */
        size_type zeros = bits::cnt(~*w);
        while (zeros < i)
        {
            i -= zeros;
            ++w;
            zeros = bits::cnt(~*w);
            res += 64;
        }
        /* handle last word */
        res += bits::sel(~*w, i);
        return res;
    }

  public:
    select_support_il(const bit_vector_type * v = nullptr)
    {
        set_vector(v);
        m_block_shift = bits::hi(t_bs);
        m_block_size_U64 = bits::hi(t_bs >> 6);
    }

    //! Returns the position of the i-th occurrence in the bit vector.
    size_type select(size_type i) const
    {
        if (t_b) return select1(i);
        return select0(i);
    }

    size_type operator()(size_type i) const { return select(i); }

    size_type size() const { return m_v->size(); }

    void set_vector(const bit_vector_type * v = nullptr) { m_v = v; }

    select_support_il & operator=(const select_support_il & rs)
    {
        if (this != &rs) { set_vector(rs.m_v); }
        return *this;
    }

    void load(std::istream &, const bit_vector_type * v = nullptr) { set_vector(v); }

    size_type serialize(std::ostream & out, structure_tree_node * v = nullptr, std::string name = "") const
    {
        return serialize_empty_object(out, v, name, this);
    }

    template <typename archive_t>
    void CEREAL_SAVE_FUNCTION_NAME(archive_t &) const
    {}

    template <typename archive_t>
    void CEREAL_LOAD_FUNCTION_NAME(archive_t &)
    {}

    bool operator==(const select_support_il & other) const noexcept { return (*m_v == *other.m_v); }

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

} // end namespace sdsl
#endif
