// 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 bp_support_sada.hpp
 * \brief bp_support_sada.hpp contains an implementation of a balanced
 * parentheses support structure proposed by Kunihiko Sadakane.
 * \author Simon Gog
 */
#ifndef INCLUDED_SDSL_BP_SUPPORT_SADA
#define INCLUDED_SDSL_BP_SUPPORT_SADA

#include <assert.h>
#include <stdint.h>

#include <sdsl/bp_support_algorithm.hpp>
#include <sdsl/int_vector.hpp>
#include <sdsl/rank_support_v.hpp>
#include <sdsl/rank_support_v5.hpp>
#include <sdsl/select_support_mcl.hpp>
#ifndef NDEBUG
#    include <algorithm>
#endif
#include <iostream>
#include <string>

#include <sdsl/bits.hpp>
#include <sdsl/cereal.hpp>
#include <sdsl/io.hpp>
#include <sdsl/structure_tree.hpp>
#include <sdsl/util.hpp>

namespace sdsl
{

//! A class that provides support for bit_vectors that represent a BP sequence.
/*! This data structure supports the following operations:
 *   - find_open
 *   - find_close
 *   - enclose
 *   - double_enclose
 *   - rank
 *   - select
 *   - excess
 *   - rr_enclose
 *  An opening parenthesis in the balanced parentheses sequence is represented by a 1 in the bit_vector
 *  and a closing parenthesis by a 0.
 *
 *  \tparam t_sml_blk The size of the small blocks. Denoted as `s` in Sadakane's paper.
 *  \tparam t_med_deg Number of small blocks that a medium block contains. Denoted as `l` in Sadakane's paper.
 *  \tparam t_rank    Type of rank support used for the underlying bitvector.
 *  \tparam t_select  Type of select support used for the underlying bitvector.
 *
 *  \par References
 *      - Kunihiko Sadakane:
 *        The Ultimate Balanced Parentheses
 *        Technical Report 2008.
 *      - Kunihiko Sadakane, Gonzalo Navarro:
 *        Fully-Functional Succinct Trees.
 *        SODA 2010: 134-149
 *
 *  @ingroup bps
 */
template <uint32_t t_sml_blk = 256,
          uint32_t t_med_deg = 32,
          class t_rank = rank_support_v5<>,
          class t_select = select_support_mcl<>>
class bp_support_sada
{
public:
    typedef bit_vector::size_type size_type;
    typedef bit_vector::difference_type difference_type;
    typedef int_vector<> sml_block_array_type;
    typedef int_vector<> med_block_array_type;
    typedef t_rank rank_type;
    typedef t_select select_type;

private:
    static_assert(0 < t_sml_blk, "bp_support_sada: t_sml_blk should be greater than 0!");
    bit_vector const * m_bp = nullptr; // the supported balanced parentheses sequence as bit_vector
    rank_type m_bp_rank;               // RS for the BP sequence => see excess() and rank()
    select_type m_bp_select;           // SS for the BP sequence => see select()

    sml_block_array_type m_sml_block_min_max;
    med_block_array_type m_med_block_min_max;

    size_type m_size = 0;             // number of supported parentheses
    size_type m_sml_blocks = 0;       // number of small sized blocks
    size_type m_med_blocks = 0;       // number of medium sized blocks
    size_type m_med_inner_blocks = 0; // number of inner nodes in the min max tree of the medium sized blocks
//#define USE_CACHE
#ifdef USE_CACHE
    mutable fast_cache find_close_cache;
    mutable fast_cache find_open_cache;
    mutable fast_cache select_cache;
#endif

    static inline size_type sml_block_idx(size_type i)
    {
        return i / t_sml_blk;
    }

    static inline size_type med_block_idx(size_type i)
    {
        return i / (t_sml_blk * t_med_deg);
    }

    static inline bool is_root(size_type v)
    {
        return v == 0;
    }

    static inline bool is_left_child(size_type v)
    {
        assert(!is_root(v));
        return v % 2;
    }

    static inline bool is_right_child(size_type v)
    {
        assert(!is_root(v));
        return !(v % 2);
    }

    static inline size_type parent(size_type v)
    {
        assert(!is_root(v));
        return (v - 1) / 2;
    }

    static inline size_type left_child(size_type v)
    {
        return 2 * v + 1;
    }

    static inline size_type right_child(size_type v)
    {
        return 2 * v + 2;
    }

    inline bool node_exists(size_type v) const
    {
        return v < (m_med_inner_blocks + m_med_blocks);
    }

    static inline size_type right_sibling(size_type v)
    {
        return ++v;
    }

    static inline size_type left_sibling(size_type v)
    {
        return --v;
    }

    inline bool is_leaf(size_type v) const
    {
        return v >= m_med_inner_blocks;
    }

    inline difference_type min_value(size_type v) const
    {
        return m_size - ((difference_type)m_med_block_min_max[2 * v]);
    }

    inline difference_type max_value(size_type v) const
    {
        return m_med_block_min_max[2 * v + 1] - m_size;
    }

    inline difference_type sml_min_value(size_type sml_block) const
    {
        return (1 - ((difference_type)m_sml_block_min_max[sml_block << 1]));
    }

    inline difference_type sml_max_value(size_type sml_block) const
    {
        return (difference_type)m_sml_block_min_max[(sml_block << 1) + 1] - 1;
    }

    void print_node(size_type v) const
    {
        std::cout << "v = " << v << "  (" << min_value(v) << ", " << max_value(v) << ")";
        if (is_leaf(v))
        {
            std::cout << " range: [" << (v - m_med_inner_blocks) * t_med_deg * t_sml_blk << ","
                      << (v - m_med_inner_blocks + 1) * t_med_deg * t_sml_blk - 1 << "]";
        }
        std::cout << std::endl;
    }

    //! Calculate the min parenthesis \f$j>i\f$ with \f$excess(j)=excess(i)+rel\f$
    /*!\param i   The index of a parenthesis in the supported sequence.
     *  \param rel The excess difference to the excess value of parentheses \f$i\f$.
     *  \return    If there exists a parenthesis \f$ j>i\f$ with
     *            \f$ excess(j) = excess(i)+rel \f$, \f$j\f$ is returned
     *                otherwise size().
     */
    size_type fwd_excess(size_type i, difference_type rel) const
    {
        size_type j;
        // (1) search the small block for the answer
        if ((j = near_fwd_excess(*m_bp, i + 1, rel, t_sml_blk)) > i)
        {
            return j;
        }
        difference_type desired_excess = excess(i) + rel;
        // (2) scan the small blocks of the current median block for an answer
        if ((j = fwd_excess_in_med_block(sml_block_idx(i) + 1, desired_excess)) != size())
        {
            return j;
        }
        // (3) search the min-max tree of the medium blocks for the right med block
        if (med_block_idx(i) == m_med_blocks) // if we are already in the last medium block => we are done
            return size();
        size_type v = m_med_inner_blocks + med_block_idx(i);
        // (3 a) go up the tree
        while (!is_root(v))
        {
            if (is_left_child(v))
            {                                                                          // if the node is a left child
                v = right_sibling(v);                                                  // choose right sibling
                if (min_value(v) <= desired_excess and desired_excess <= max_value(v)) // found solution
                    break;
            }
            v = parent(v); // choose parent
        }
        // (3 b) go down the tree
        if (!is_root(v))
        { // found solution for the query
            while (!is_leaf(v))
            {
                v = left_child(v); // choose left child
                if (!(min_value(v) <= desired_excess and desired_excess <= max_value(v)))
                {
                    v = right_sibling(v); // choose right child == right sibling of the left child
                    assert((min_value(v) <= desired_excess and desired_excess <= max_value(v)));
                }
            }
            return fwd_excess_in_med_block((v - m_med_inner_blocks) * t_med_deg, desired_excess);
        }
        // no solution found
        return size();
    }

    //! Calculate the maximal parenthesis \f$ j<i \f$ with \f$ excess(j) = excess(i)+rel \f$
    /*!\param i     The index of a parenthesis in the supported sequence.
     *  \param rel  The excess difference to the excess value of parenthesis \f$i\f$.
     *  \return     If there exists a parenthesis \f$i<j\f$ with \f$ excess(j) = excess(i)+rel\f$, \f$j\f$ is returned
     *                otherwise size().
     */
    size_type bwd_excess(size_type i, difference_type rel) const
    {
        size_type j;
        if (i == 0)
        {
            return rel == 0 ? -1 : size();
        }
        // (1) search the small block for the answer
        if ((j = near_bwd_excess(*m_bp, i - 1, rel, t_sml_blk)) < i or j == (size_type)-1)
        {
            return j;
        }
        difference_type desired_excess = excess(i) + rel;
        // (2) scan the small blocks of the current median block for an answer
        if ((j = bwd_excess_in_med_block(sml_block_idx(i) - 1, desired_excess)) != size())
        {
            return j;
        }
        // (3) search the min-max tree of the medium blocks for the right med block
        if (med_block_idx(i) == 0)
        { // if we are already in the first medium block => we are done
            if (desired_excess == 0)
                return -1;
            return size();
        }
        size_type v = m_med_inner_blocks + med_block_idx(i);
        // (3 a) go up the tree
        while (!is_root(v))
        {
            if (is_right_child(v))
            {                                                                          // if the node is a right child
                v = left_sibling(v);                                                   // choose left sibling
                if (min_value(v) <= desired_excess and desired_excess <= max_value(v)) // found solution
                    break;
            }
            v = parent(v); // choose parent
        }
        // (3 b) go down the tree
        if (!is_root(v))
        { // found solution for the query
            while (!is_leaf(v))
            {
                v = right_child(v); // choose  child
                if (!(min_value(v) <= desired_excess and desired_excess <= max_value(v)))
                {
                    v = left_sibling(v); // choose left child == left sibling of the right child
                    assert((min_value(v) <= desired_excess and desired_excess <= max_value(v)));
                }
            }
            return bwd_excess_in_med_block((v - m_med_inner_blocks) * t_med_deg + (t_med_deg - 1), desired_excess);
        }
        else if (desired_excess == 0)
        {
            return -1;
        }
        // no solution found
        return size();
    }

    //! Calculate the maximal parentheses \f$ j \leq sml_block_idx\cdot t_sml_blk+(t_sml_blk-1) \f$ with \f$
    //! excess(j)=desired\_excess \f$
    size_type bwd_excess_in_med_block(size_type sml_block_idx, difference_type desired_excess) const
    {
        // get the first small block in the medium block right to the current med block
        size_type first_sml_block_in_med_block = (med_block_idx(sml_block_idx * t_sml_blk)) * t_med_deg;

        while ((sml_block_idx + 1) and sml_block_idx >= first_sml_block_in_med_block)
        {
            difference_type ex = (sml_block_idx == 0) ? 0 : excess(sml_block_idx * t_sml_blk - 1);
            difference_type min_ex = ex + (1 - ((difference_type)m_sml_block_min_max[2 * sml_block_idx]));
            difference_type max_ex = ex + (m_sml_block_min_max[2 * sml_block_idx + 1] - 1);

            if (min_ex <= desired_excess and desired_excess <= max_ex)
            {
                size_type j = near_bwd_excess(*m_bp,
                                              (sml_block_idx + 1) * t_sml_blk - 1,
                                              desired_excess - excess((sml_block_idx + 1) * t_sml_blk),
                                              t_sml_blk);
                return j;
            }
            --sml_block_idx;
        }
        if (sml_block_idx == 0 and desired_excess == 0)
            return -1;
        return size();
    }

    //! Calculate the minimal parentheses \f$ j \geq sml_block_idx\cdot t_sml_blk \f$ with \f$ excess(j)=desired\_excess
    //! \f$
    size_type fwd_excess_in_med_block(size_type sml_block_idx, difference_type desired_excess) const
    {
        // get the first small block in the medium block right to the current med block
        size_type first_sml_block_nr_in_next_med_block = (med_block_idx(sml_block_idx * t_sml_blk) + 1) * t_med_deg;
        if (first_sml_block_nr_in_next_med_block > m_sml_blocks)
            first_sml_block_nr_in_next_med_block = m_sml_blocks;

        assert(sml_block_idx > 0);
        while (sml_block_idx < first_sml_block_nr_in_next_med_block)
        {
            difference_type ex = excess(sml_block_idx * t_sml_blk - 1);
            difference_type min_ex = ex + (1 - ((difference_type)m_sml_block_min_max[2 * sml_block_idx]));
            difference_type max_ex = ex + m_sml_block_min_max[2 * sml_block_idx + 1] - 1;
            if (min_ex <= desired_excess and desired_excess <= max_ex)
            {
                size_type j = near_fwd_excess(*m_bp, sml_block_idx * t_sml_blk, desired_excess - ex, t_sml_blk);
                return j;
            }
            ++sml_block_idx;
        }
        return size();
    }

public:
    rank_type const & bp_rank = m_bp_rank;                                //!< RS for the underlying BP sequence.
    select_type const & bp_select = m_bp_select;                          //!< SS for the underlying BP sequence.
    sml_block_array_type const & sml_block_min_max = m_sml_block_min_max; //!< Small blocks array. Rel. min/max for the
                                                                          //!< small blocks.
    med_block_array_type const & med_block_min_max = m_med_block_min_max; //!< Array containing the min max tree of the
                                                                          //!< medium blocks.

    bp_support_sada()
    {}

    //! Copy constructor
    bp_support_sada(bp_support_sada const & v) :
        m_bp(v.m_bp),
        m_bp_rank(v.m_bp_rank),
        m_bp_select(v.m_bp_select),
        m_sml_block_min_max(v.m_sml_block_min_max),
        m_med_block_min_max(v.m_med_block_min_max),
        m_size(v.m_size),
        m_sml_blocks(v.m_sml_blocks),
        m_med_blocks(v.m_med_blocks),
        m_med_inner_blocks(v.m_med_inner_blocks)
    {
        m_bp_rank.set_vector(m_bp);
        m_bp_select.set_vector(m_bp);
    }

    //! Move constructor
    bp_support_sada(bp_support_sada && bp_support)
    {
        *this = std::move(bp_support);
    }

    //! Assignment operator
    bp_support_sada & operator=(bp_support_sada && bp_support)
    {
        if (this != &bp_support)
        {
            m_bp = std::move(bp_support.m_bp);
            m_bp_rank = std::move(bp_support.m_bp_rank);
            m_bp_rank.set_vector(m_bp);
            m_bp_select = std::move(bp_support.m_bp_select);
            m_bp_select.set_vector(m_bp);

            m_sml_block_min_max = std::move(bp_support.m_sml_block_min_max);
            m_med_block_min_max = std::move(bp_support.m_med_block_min_max);

            m_size = std::move(bp_support.m_size);
            m_sml_blocks = std::move(bp_support.m_sml_blocks);
            m_med_blocks = std::move(bp_support.m_med_blocks);
            m_med_inner_blocks = std::move(bp_support.m_med_inner_blocks);
        }
        return *this;
    }

    //! Assignment operator
    bp_support_sada & operator=(bp_support_sada const & v)
    {
        if (this != &v)
        {
            bp_support_sada tmp(v);
            *this = std::move(tmp);
        }
        return *this;
    }

    //! Constructor
    explicit bp_support_sada(bit_vector const * bp) :
        m_bp(bp),
        m_size(bp == nullptr ? 0 : bp->size()),
        m_sml_blocks((m_size + t_sml_blk - 1) / t_sml_blk),
        m_med_blocks((m_size + t_sml_blk * t_med_deg - 1) / (t_sml_blk * t_med_deg)),
        m_med_inner_blocks(0)
    {
        if (bp == nullptr or bp->size() == 0)
            return;
        // initialize rank and select
        util::init_support(m_bp_rank, bp);
        util::init_support(m_bp_select, bp);

        m_med_inner_blocks = 1;
        // m_med_inner_blocks = (next power of 2 greater than or equal to m_med_blocks)-1
        while (m_med_inner_blocks < m_med_blocks)
        {
            m_med_inner_blocks <<= 1;
            assert(m_med_inner_blocks != 0);
        }
        --m_med_inner_blocks;
        assert((m_med_inner_blocks == 0) or (m_med_inner_blocks % 2 == 1));

        m_sml_block_min_max = int_vector<>(2 * m_sml_blocks, 0, bits::hi(t_sml_blk + 2) + 1);
        m_med_block_min_max = int_vector<>(2 * (m_med_blocks + m_med_inner_blocks), 0, bits::hi(2 * m_size + 2) + 1);

        // calculate min/max excess values of the small blocks and medium blocks
        difference_type min_ex = 1, max_ex = -1, curr_rel_ex = 0, curr_abs_ex = 0;
        for (size_type i = 0; i < m_size; ++i)
        {
            if ((*bp)[i])
                ++curr_rel_ex;
            else
                --curr_rel_ex;
            if (curr_rel_ex > max_ex)
                max_ex = curr_rel_ex;
            if (curr_rel_ex < min_ex)
                min_ex = curr_rel_ex;
            if ((i + 1) % t_sml_blk == 0 or i + 1 == m_size)
            {
                size_type sidx = i / t_sml_blk;
                m_sml_block_min_max[2 * sidx] = -(min_ex - 1);
                m_sml_block_min_max[2 * sidx + 1] = max_ex + 1;

                size_type v = m_med_inner_blocks + sidx / t_med_deg;

                if ((difference_type)(-(curr_abs_ex + min_ex) + m_size) > ((difference_type)m_med_block_min_max[2 * v]))
                {
                    assert(curr_abs_ex + min_ex <= min_value(v));
                    m_med_block_min_max[2 * v] = -(curr_abs_ex + min_ex) + m_size;
                }

                if ((difference_type)(curr_abs_ex + max_ex + m_size) > (difference_type)m_med_block_min_max[2 * v + 1])
                    m_med_block_min_max[2 * v + 1] = curr_abs_ex + max_ex + m_size;

                curr_abs_ex += curr_rel_ex;
                min_ex = 1;
                max_ex = -1;
                curr_rel_ex = 0;
            }
        }

        for (size_type v = m_med_block_min_max.size() / 2 - 1; !is_root(v); --v)
        {
            size_type p = parent(v);
            if (min_value(v) < min_value(p)) // update minimum
                m_med_block_min_max[2 * p] = m_med_block_min_max[2 * v];
            if (max_value(v) > max_value(p)) // update maximum
                m_med_block_min_max[2 * p + 1] = m_med_block_min_max[2 * v + 1];
        }
    }

    void set_vector(bit_vector const * bp)
    {
        m_bp = bp;
        m_bp_rank.set_vector(bp);
        m_bp_select.set_vector(bp);
    }

    /*! Calculates the excess value at index i.
     * \param i The index of which the excess value should be calculated.
     */
    inline difference_type excess(size_type i) const
    {
        return (m_bp_rank(i + 1) << 1) - i - 1;
    }

    /*! Returns the number of opening parentheses up to and including index i.
     * \pre{ \f$ 0\leq i < size() \f$ }
     */
    size_type rank(size_type i) const
    {
        return m_bp_rank(i + 1);
    }

    /*! Returns the index of the i-th opening parenthesis.
     * \param i Number of the parenthesis to select.
     * \pre{ \f$1\leq i < rank(size())\f$ }
     * \post{ \f$ 0\leq select(i) < size() \f$ }
     */
    size_type select(size_type i) const
    {
#ifdef USE_CACHE
        size_type a = 0;
        if (select_cache.exists(i, a))
        {
            return a;
        }
        else
        {
            a = m_bp_select(i);
            select_cache.write(i, a);
            return a;
        }
#endif
        return m_bp_select(i);
    }

    /*! Calculate the index of the matching closing parenthesis to the parenthesis at index i.
     * \param i Index of an parenthesis. 0 <= i < size().
     * \return * i, if the parenthesis at index i is closing,
     *         * the position j of the matching closing parenthesis, if a matching parenthesis exists,
     *         * size() if no matching closing parenthesis exists.
     */
    size_type find_close(size_type i) const
    {
        assert(i < m_size);
        if (!(*m_bp)[i])
        { // if there is a closing parenthesis at index i return i
            return i;
        }
#ifdef USE_CACHE
        size_type a = 0;
        if (find_close_cache.exists(i, a))
        {
            return a;
        }
        else
        {
            a = fwd_excess(i, -1);
            find_close_cache.write(i, a);
            return a;
        }
#endif
        return fwd_excess(i, -1);
    }

    //! Calculate the matching opening parenthesis to the closing parenthesis at position i
    /*!\param i Index of a closing parenthesis.
     * \return * i, if the parenthesis at index i is closing,
     *         * the position j of the matching opening parenthesis, if a matching parenthesis exists,
     *         * size() if no matching closing parenthesis exists.
     */
    size_type find_open(size_type i) const
    {
        assert(i < m_size);
        if ((*m_bp)[i])
        { // if there is a opening parenthesis at index i return i
            return i;
        }
#ifdef USE_CACHE
        size_type a = 0;
        if (find_open_cache.exists(i, a))
        {
            return a;
        }
        else
        {
            size_type bwd_ex = bwd_excess(i, 0);
            if (bwd_ex == size())
                a = size();
            else
                a = bwd_ex + 1;
            find_open_cache.write(i, a);
            return a;
        }
#endif
        size_type bwd_ex = bwd_excess(i, 0);
        if (bwd_ex == size())
            return size();
        else
            return bwd_ex + 1;
    }

    //! Calculate the index of the opening parenthesis corresponding to the closest matching parenthesis pair enclosing
    //! i.
    /*!\param i Index of an opening parenthesis.
     *  \return The index of the opening parenthesis corresponding to the closest matching parenthesis pair enclosing i,
     *          or size() if no such pair exists.
     */
    size_type enclose(size_type i) const
    {
        assert(i < m_size);
        if (!(*m_bp)[i])
        { // if there is closing parenthesis at position i
            return find_open(i);
        }
        size_type bwd_ex = bwd_excess(i, -2);
        if (bwd_ex == size())
            return size();
        else
            return bwd_ex + 1;
    }

    //! The range restricted enclose operation for parentheses pairs \f$(i,\mu(i))\f$ and \f$(j,\mu(j))\f$.
    /*!\param i First opening parenthesis.
     *  \param j Second opening parenthesis \f$ i<j \wedge findclose(i) < j \f$.
     *  \return The smallest index, say k, of an opening parenthesis such that findclose(i) < k < j and
     *  findclose(j) < findclose(k). If such a k does not exists, restricted_enclose(i,j) returns size().
     * \par Time complexity
     *     \f$ \Order{block\_size} \f$
     */
    size_type rr_enclose(const size_type i, const size_type j) const
    {
        assert(j < m_size);
        assert((*m_bp)[i] == 1 and (*m_bp)[j] == 1);
        const size_type mip1 = find_close(i) + 1;
        if (mip1 >= j)
            return size();
        return rmq_open(mip1, j);
    }

    /*! Search the interval [l,r-1] for an opening parenthesis, say i, such that find_close(i) >= r.
     * \param l The left end (inclusive) of the interval to search for the result.
     * \param r The right end (exclusive) of the interval to search for the result.
     * \return The minimal opening parenthesis i with \f$ \ell \leq i < r \f$ and \f$ find_close(i) \geq r \f$;
     *         if no such i exists size() is returned.
     * \par Time complexity
     *     \f$ \Order{block\_size} \f$
     */
    size_type rmq_open(const size_type l, const size_type r) const
    {
        assert(r < m_bp->size());
        if (l >= r)
            return size();
        size_type res = rmq(l, r - 1);
        assert(res >= l and res <= r - 1);
        if ((*m_bp)[res] == 1)
        { // The parenthesis with minimal excess is opening
            assert(find_close(res) >= r);
            return res;
        }
        else
        {
            res = res + 1; // go to the next parenthesis to the right
            if (res < r)
            { // The parenthesis with minimal excess if closing and the next opening parenthesis is less than r
                assert((*m_bp)[res] == 1);
                size_type ec = enclose(res);
                if (ec < l or ec == size())
                {
                    assert(find_close(res) >= r);
                    return res;
                }
                else
                {
                    assert(find_close(ec) >= r);
                    return ec;
                }
            }
            else if (res == r)
            {
                size_type ec = enclose(res); // if m_bp[res]==0 => find_open(res), if m_bp[res]==1 => enclose(res)
                if (ec >= l)
                {
                    assert(ec == size() or excess(ec) == excess(res - 1));
                    return ec;
                }
            }
        }
        return size();
    }

    size_type median_block_rmq(size_type l_sblock, size_type r_sblock, bit_vector::difference_type & min_ex) const
    {
        assert(l_sblock <= r_sblock + 1);
        size_type pos_min_block = (size_type)-1;
        difference_type e = 0;
        if (l_sblock == 0)
        {
            if (sml_min_value(0) <= min_ex)
            {
                pos_min_block = 0;
                min_ex = sml_min_value(0);
            }
            l_sblock = 1;
        }
        for (size_type i = l_sblock; i <= r_sblock; ++i)
        {
            if ((e = (excess(i * t_sml_blk - 1) + sml_min_value(i))) <= min_ex)
            {
                pos_min_block = i;
                min_ex = e;
            }
        }
        return pos_min_block;
    }

    //! The range minimum query (rmq) returns the index of the parenthesis with minimal excess in the range \f$[l..r]\f$
    /*!\param l The left border of the interval \f$[l..r]\f$ (\f$l\leq r\f$).
     *  \param r The right border of the interval \f$[l..r]\f$ (\f$l \leq r\f$).
     */
    size_type rmq(size_type l, size_type r) const
    {
        assert(l <= r);
        size_type sbl = sml_block_idx(l);
        size_type sbr = sml_block_idx(r);
        difference_type min_rel_ex = 0;
        if (sbl == sbr)
        { // if l and r are in the same small block
            return near_rmq(*m_bp, l, r, min_rel_ex);
        }
        else
        {
            difference_type min_ex = 0; // current minimal excess value
            size_type min_pos = 0;      // current min pos
            enum min_pos_type
            {
                POS,
                SMALL_BLOCK_POS,
                MEDIUM_BLOCK_POS
            };
            enum min_pos_type pos_type = POS;                                    // current
            min_pos = near_rmq(*m_bp, l, (sbl + 1) * t_sml_blk - 1, min_rel_ex); // scan the leftmost small block of l
            assert(min_pos >= l);
            min_ex = excess(l) + min_rel_ex;

            size_type mbl = med_block_idx(l);
            size_type mbr = med_block_idx(r);
            assert(mbl <= mbr);

            size_type temp = median_block_rmq(sbl + 1,
                                              std::min((mbl + 1) * t_med_deg - 1, sbr - 1),
                                              min_ex); // scan the medium block of l
            if (temp != (size_type)-1)
            {
                assert(temp * t_sml_blk >= l and temp * t_sml_blk <= r);
                min_pos = temp;
                assert(min_pos < m_sml_blocks);
                pos_type = SMALL_BLOCK_POS;
            }
#if 0
                // sequential scan the medium blocks
                for (size_type v=mbl+1+m_med_inner_blocks; v < mbr + m_med_inner_blocks; ++v) {
                    assert(is_leaf(v));
                    if (min_value(v) <= min_ex) {
                        min_ex = min_value(v);
                        min_pos = v;
                        assert(min_pos-m_med_inner_blocks >= 0 and min_pos < m_med_blocks-m_med_inner_blocks);
                        pos_type = MEDIUM_BLOCK_POS;
                    }
                }
#else
            // tree search in the min max tree of the medium blocks
            if (mbr - mbl > 1)
            {
                size_type v = mbl + 1 + m_med_inner_blocks;
                size_type rcb = mbl + 1; // rightmost covered block
                size_type h = 0;         // subtree height
                while (rcb < mbr - 1)
                { // go up the tree until the rightmost covered block >= mbr-1
                    if (min_value(v) <= min_ex)
                    {
                        min_ex = min_value(v);
                        min_pos = v;
                        pos_type = MEDIUM_BLOCK_POS;
                    }
                    if (is_right_child(v))
                    { // v is a right child
                        h += 1;
                        rcb += (1ULL << h);
                        v = right_sibling(parent(v));
                    }
                    else
                    { //  it is a left child
                        rcb += (1ULL << h);
                        h += 1;
                        v = parent(v);
                    }
                }
                if (rcb <= mbr - 1 and min_value(v) <= min_ex)
                {
                    min_ex = min_value(v);
                    min_pos = v;
                    pos_type = MEDIUM_BLOCK_POS;
                }
                assert(node_exists(v));
                assert(rcb >= mbr - 1);

                while (rcb != mbr - 1)
                { // go down the tree until the rightmost covered block = mbr-1
                    assert(h != (size_type)-1);
                    if (rcb > mbr - 1)
                    {
                        h = h - 1;
                        rcb = rcb - (1ULL << h);
                        v = left_child(v);
                    }
                    else
                    { // rcb < mbr-1
                        h = h - 1;
                        rcb = rcb + (1ULL << h);
                        v = right_sibling(right_child(v));
                    }
                    if (rcb <= mbr - 1 and min_value(v) <= min_ex)
                    {
                        min_ex = min_value(v);
                        min_pos = v;
                        pos_type = MEDIUM_BLOCK_POS;
                    }
                }
                if (pos_type == MEDIUM_BLOCK_POS)
                {
                    while (!is_leaf(min_pos))
                    {
                        min_pos = right_child(min_pos);
                        if (!node_exists(min_pos) or min_value(min_pos) > min_ex)
                            min_pos = left_sibling(min_pos);
                    }
                }
            }
#endif

            // search in the medium block of r
            temp = median_block_rmq(std::max(mbr * t_med_deg, sbl + 1), sbr - 1, min_ex); // scan the medium block of r
            if (temp != (size_type)-1)
            {
                assert(temp * t_sml_blk >= l and temp * t_sml_blk <= r);
                min_pos = temp;
                pos_type = SMALL_BLOCK_POS;
            }
            // search in the small block of r
            temp = near_rmq(*m_bp, sbr * t_sml_blk, r, min_rel_ex); // scan the small block of r
            if ((excess(sbr * t_sml_blk) + min_rel_ex) <= min_ex)
            { // if it contains the minimum return its position
                assert(temp >= l and temp <= r);
                return temp;
            }
            // if the found minimum lies in a medium block => find its small block
            if (pos_type == MEDIUM_BLOCK_POS)
            {
                min_pos = min_pos - m_med_inner_blocks;
                temp = median_block_rmq(min_pos * t_med_deg, (min_pos + 1) * t_med_deg - 1, min_ex);
                assert(temp != (size_type)-1); // assert that we find a solution
                assert(temp * t_sml_blk >= l and temp * t_sml_blk <= r);
                min_pos = temp;
                pos_type = SMALL_BLOCK_POS;
            }
            if (pos_type == SMALL_BLOCK_POS)
            {
                min_pos = near_rmq(*m_bp, min_pos * t_sml_blk, (min_pos + 1) * t_sml_blk - 1, min_rel_ex);
                assert(min_pos >= l and min_pos <= r);
            }
            return min_pos;
        }
    }

    //! The range restricted enclose operation
    /*!\param i Index of an opening parenthesis.
     *  \param j Index of an opening parenthesis \f$ i<j \wedge findclose(i) < j \f$.
     *  \return The minimal opening parenthesis, say k, such that \f$ findclose(i) < k < j\f$ and
     *  findclose(j) < findclose(k). If such a k does not exists, restricted_enclose(i,j) returns size().
     *  \par Time complexity
     *        \f$ \Order{size()}\f$ in the worst case.
     */
    size_type rr_enclose_naive(size_type i, size_type j) const
    {
        assert(j > i and j < m_size);
        assert((*m_bp)[i] == 1 and (*m_bp)[j] == 1);
        size_type mi = find_close(i); // matching parenthesis to i
        assert(mi > i and mi < j);
        assert(find_close(j) > j);
        size_type k = enclose(j);
        if (k == m_size or k < i) // there exists no opening parenthesis at position mi<k<j.
            return m_size;
        size_type kk;
        do
        {
            kk = k;
            k = enclose(k);
        }
        while (k != m_size and k > mi);
        return kk;
    }

    //! The double enclose operation
    /*!\param i Index of an opening parenthesis.
     *  \param j Index of an opening parenthesis \f$ i<j \wedge findclose(i) < j \f$.
     *  \return The maximal opening parenthesis, say k, such that \f$ k<j \wedge k>findclose(j) \f$.
     *          If such a k does not exists, double_enclose(i,j) returns size().
     */
    size_type double_enclose(size_type i, size_type j) const
    {
        assert(j > i);
        assert((*m_bp)[i] == 1 and (*m_bp)[j] == 1);
        size_type k = rr_enclose(i, j);
        if (k == size())
            return enclose(j);
        else
            return enclose(k);
    }

    //! Return the number of zeros which proceed position i in the balanced parentheses sequence.
    /*!\param i Index of an parenthesis.
     */
    size_type preceding_closing_parentheses(size_type i) const
    {
        assert(i < m_size);
        if (!i)
            return 0;
        size_type ones = m_bp_rank(i);
        if (ones)
        { // ones > 0
            assert(m_bp_select(ones) < i);
            return i - m_bp_select(ones) - 1;
        }
        else
        {
            return i;
        }
    }

    //! Returns the level ancestor of the node i.
    /*!\param i The index of a parenthesis (i.e., a node).
     *  \param d The level, i.e., which node to select on the path from the node i up to the root.
     *           The level d = 0 will return the node itself, d = 1 will return its parent, and so on.
     */
    size_type level_anc(size_type i, size_type d) const
    {
        assert(i < m_size);
        size_type bwd_ex = bwd_excess(i, -d - 1);
        if (bwd_ex == size())
            return size();
        else
            return bwd_ex + 1;
    }

    /*! The size of the supported balanced parentheses sequence.
     * \return the size of the supported balanced parentheses sequence.
     */
    size_type size() const
    {
        return m_size;
    }

    //! Serializes the bp_support_sada to a stream.
    /*!
     * \param out The outstream to which the data structure is written.
     * \return The number of bytes written to out.
     */
    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_sml_blocks, out, child, "sml_block_cnt");
        written_bytes += write_member(m_med_blocks, out, child, "med_block_cnt");
        written_bytes += write_member(m_med_inner_blocks, out, child, "med_inner_blocks");

        written_bytes += m_bp_rank.serialize(out, child, "bp_rank");
        written_bytes += m_bp_select.serialize(out, child, "bp_select");

        written_bytes += m_sml_block_min_max.serialize(out, child, "sml_blocks");
        written_bytes += m_med_block_min_max.serialize(out, child, "med_blocks");

        structure_tree::add_size(child, written_bytes);
        return written_bytes;
    }

    //! Load the bp_support_sada for a bit_vector v.
    /*!
     * \param in The instream from which the data strucutre is read.
     * \param bp Bit vector representing a balanced parentheses sequence that is supported by this data structure.
     */
    void load(std::istream & in, bit_vector const * bp)
    {
        m_bp = bp;
        read_member(m_size, in);
        assert(m_size == bp->size());
        read_member(m_sml_blocks, in);
        read_member(m_med_blocks, in);
        read_member(m_med_inner_blocks, in);

        m_bp_rank.load(in, m_bp);
        m_bp_select.load(in, m_bp);

        m_sml_block_min_max.load(in);
        m_med_block_min_max.load(in);
    }

    template <typename archive_t>
    void CEREAL_SAVE_FUNCTION_NAME(archive_t & ar) const
    {
        ar(CEREAL_NVP(m_size));
        ar(CEREAL_NVP(m_sml_blocks));
        ar(CEREAL_NVP(m_med_blocks));
        ar(CEREAL_NVP(m_med_inner_blocks));
        ar(CEREAL_NVP(m_bp_rank));
        ar(CEREAL_NVP(m_bp_select));
        ar(CEREAL_NVP(m_sml_block_min_max));
        ar(CEREAL_NVP(m_med_block_min_max));
    }

    template <typename archive_t>
    void CEREAL_LOAD_FUNCTION_NAME(archive_t & ar)
    {
        ar(CEREAL_NVP(m_size));
        ar(CEREAL_NVP(m_sml_blocks));
        ar(CEREAL_NVP(m_med_blocks));
        ar(CEREAL_NVP(m_med_inner_blocks));
        ar(CEREAL_NVP(m_bp_rank));
        ar(CEREAL_NVP(m_bp_select));
        ar(CEREAL_NVP(m_sml_block_min_max));
        ar(CEREAL_NVP(m_med_block_min_max));
    }

    //! Equality operator.
    bool operator==(bp_support_sada const & other) const noexcept
    {
        return (m_bp_rank == other.m_bp_rank) && (m_bp_select == other.m_bp_select)
            && (m_sml_block_min_max == other.m_sml_block_min_max) && (m_med_block_min_max == other.m_med_block_min_max)
            && (m_size == other.m_size) && (m_sml_blocks == other.m_sml_blocks) && (m_med_blocks == other.m_med_blocks)
            && (m_med_inner_blocks == other.m_med_inner_blocks);
    }

    //! Inequality operator.
    bool operator!=(bp_support_sada const & other) const noexcept
    {
        return !(*this == other);
    }
};

} // namespace sdsl

#endif
