// 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 sorted_multi_stack_support.hpp
 * \brief sorted_multi_stack_support.hpp contains a data structure for a stack which contains
 * elements from [0..n] in sorted order. Duplicates are possible.
 * \author Simon Gog
 */
#ifndef INCLUDED_SDSL_SORTED_MULTI_STACK_SUPPORT
#define INCLUDED_SDSL_SORTED_MULTI_STACK_SUPPORT

#include <sdsl/int_vector.hpp>

namespace sdsl
{

//! Stack which contains elements from [0..n] in sorted order. Duplicates are possible.
/*!\par Space complexity
 *  \f$2n\f$ bits
 */
class sorted_multi_stack_support
{
  public:
    typedef int_vector<64>::size_type size_type;

  private:
    size_type m_n;                      // Size of the supported vector.
    size_type m_cnt;                    // Counter for the indices on the stack.
    size_type m_top;                    // Topmost index of the stack.
    int_vector<64> m_stack;             // Memory for the stack.
    int_vector<64> m_duplication_stack; // Memory for the duplications

    inline size_type block_nr(size_type x) { return x / 63; };  // maybe we can speed this up with bit hacks
    inline size_type block_pos(size_type x) { return x % 63; }; // maybe we can speed this up with bit hacks
  public:
    //! Constructor
    /*!\param n Maximum that can be pushed onto the stack
     */
    sorted_multi_stack_support(size_type n);
    sorted_multi_stack_support(const sorted_multi_stack_support &) = default;
    sorted_multi_stack_support(sorted_multi_stack_support &&) = default;
    sorted_multi_stack_support & operator=(const sorted_multi_stack_support &) = default;
    sorted_multi_stack_support & operator=(sorted_multi_stack_support &&) = default;

    /*! Returns if the stack is empty.
     */
    bool empty() const { return 0 == m_cnt; };

    /*! Returns the topmost index on the stack.
     * \pre empty()==false
     */
    size_type top() const;

    /*! Pop the topmost index of the stack.
     *  \return True if there the value of the top element after the execution of pop()
     *          is not equal to the value of the top element before the execution of pop(). False otherwise.
     */
    bool pop();

    /*! Push the index x of vector vec onto the stack.
     * \par x value which should be pushed onto the stack.
     * \return True if the value on the top of the stack is smaller than x. False if the value is equal.
     * \pre top() <= x
     */
    bool push(size_type x);

    /*! Returns the number of element is the stack.
     */
    size_type size() const { return m_cnt; };

    size_type serialize(std::ostream & out, structure_tree_node * v = nullptr, std::string name = "") const;
    void load(std::istream & in);
    template <typename archive_t>
    void CEREAL_SAVE_FUNCTION_NAME(archive_t & ar) const;
    template <typename archive_t>
    void CEREAL_LOAD_FUNCTION_NAME(archive_t & ar);
};

inline sorted_multi_stack_support::sorted_multi_stack_support(size_type n)
  : m_n(n)
  , m_cnt(0)
  , m_top(0)
  , m_stack()
  , m_duplication_stack()
{
    m_stack = int_vector<64>(block_nr(m_n + 1) + 1, 0);
    m_stack[0] = 1;
    m_duplication_stack = int_vector<64>((m_n >> 6) + 1, 0);
}

inline sorted_multi_stack_support::size_type sorted_multi_stack_support::top() const
{
    return m_top - 1;
}

inline bool sorted_multi_stack_support::push(size_type x)
{
    x += 1;
    size_type bn = block_nr(x);
    if (0 == ((m_stack[bn] >> block_pos(x)) & 1))
    { // check if x is not already on the stack
        m_stack[bn] ^= (1ULL << block_pos(x));
        if (bn > 0 and m_stack[bn - 1] == 0) { m_stack[bn - 1] = 0x8000000000000000ULL | m_top; }
        m_top = x;
        // write a 0 to the duplication stack
        // do nothing as stack is initialized with zeros
        ++m_cnt; //< increment counter
        return true;
    }
    else
    { // if the element is already on the stack
        // write a 1 to the duplication stack
        m_duplication_stack[m_cnt >> 6] ^= (1ULL << (m_cnt & 0x3F));
        ++m_cnt; //< increment counter
        return false;
    }
}

inline bool sorted_multi_stack_support::pop()
{
    if (m_cnt)
    {
        --m_cnt; //< decrement counter
        if ((m_duplication_stack[m_cnt >> 6] >> (m_cnt & 0x3F)) & 1)
        {                                                                // if it's a duplication
            m_duplication_stack[m_cnt >> 6] ^= (1ULL << (m_cnt & 0x3F)); // delete 1
            return false;
        }
        else
        {
            size_type bn = block_nr(m_top);
            uint64_t w = m_stack[bn];
            assert((w >> 63) == 0); // highest bit is not set, as the block contains no pointer
            w ^= (1ULL << block_pos(m_top));
            m_stack[bn] = w;
            if (w > 0) { m_top = bn * 63 + bits::hi(w); }
            else
            { // w==0 and cnt>0
                assert(bn > 0);
                w = m_stack[bn - 1];
                if ((w >> 63) == 0)
                { // highest bit is not set => the block contains no pointer
                    assert(w > 0);
                    m_top = (bn - 1) * 63 + bits::hi(w);
                }
                else
                { // block contains pointers
                    m_stack[bn - 1] = 0;
                    m_top = w & 0x7FFFFFFFFFFFFFFFULL;
                }
            }
            return true;
        }
    }
    return false;
}

inline sorted_multi_stack_support::size_type sorted_multi_stack_support::serialize(std::ostream & out,
                                                                                   structure_tree_node * v,
                                                                                   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_n, out);
    written_bytes += write_member(m_top, out);
    written_bytes += write_member(m_cnt, out);
    written_bytes += m_stack.serialize(out);
    written_bytes += m_duplication_stack.serialize(out);
    structure_tree::add_size(child, written_bytes);
    return written_bytes;
}

inline void sorted_multi_stack_support::load(std::istream & in)
{
    read_member(m_n, in);
    read_member(m_top, in);
    read_member(m_cnt, in);
    m_stack.load(in);
    m_duplication_stack.load(in);
}

template <typename archive_t>
void sorted_multi_stack_support::CEREAL_SAVE_FUNCTION_NAME(archive_t & ar) const
{
    ar(CEREAL_NVP(m_n));
    ar(CEREAL_NVP(m_cnt));
    ar(CEREAL_NVP(m_top));
    ar(CEREAL_NVP(m_stack));
    ar(CEREAL_NVP(m_duplication_stack));
}

template <typename archive_t>
void sorted_multi_stack_support::CEREAL_LOAD_FUNCTION_NAME(archive_t & ar)
{
    ar(CEREAL_NVP(m_n));
    ar(CEREAL_NVP(m_cnt));
    ar(CEREAL_NVP(m_top));
    ar(CEREAL_NVP(m_stack));
    ar(CEREAL_NVP(m_duplication_stack));
}

} // end namespace sdsl

#endif // end file
