/** @file
 * @brief Classes to encode/decode a bitstream.
 */
/* Copyright (C) 2004,2005,2006,2008,2012,2013,2014,2017,2018 Olly Betts
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see
 * <https://www.gnu.org/licenses/>.
 */

#ifndef XAPIAN_INCLUDED_BITSTREAM_H
#define XAPIAN_INCLUDED_BITSTREAM_H

#include <xapian/types.h>

#include "api/smallvector.h"

#include <string>
#include <vector>

namespace Xapian {

/// Create a stream to which non-byte-aligned values can be written.
class BitWriter {
    std::string buf;
    int n_bits = 0;
    Xapian::termpos acc = 0;

  public:
    /// Construct empty.
    BitWriter() { }

    /// Construct with the contents of seed already in the stream.
    explicit BitWriter(const std::string& seed)
        : buf(seed) { }

    /// Encode value, known to be less than outof.
    void encode(Xapian::termpos value, Xapian::termpos outof);

    /// Finish encoding and return the encoded data as a std::string.
    std::string& freeze() {
        if (n_bits) {
            buf += char(acc);
            n_bits = 0;
            acc = 0;
        }
        return buf;
    }

    /// Perform interpolative encoding of pos elements between j and k.
    void encode_interpolative(const Xapian::VecCOW<Xapian::termpos>& pos,
                              int j, int k);
};

/// Read a stream created by BitWriter.
class BitReader {
    const char* p;

    const char* end;

    int n_bits;

    Xapian::termpos acc;

    Xapian::termpos read_bits(int count);

    struct DIStack {
        int j, k;
        Xapian::termpos pos_k;
    };

    struct DIState : public DIStack {
        Xapian::termpos pos_j;

        void set_j(int j_, Xapian::termpos pos_j_) {
            j = j_;
            pos_j = pos_j_;
        }
        void set_k(int k_, Xapian::termpos pos_k_) {
            k = k_;
            pos_k = pos_k_;
        }
        void uninit() {
            j = 1;
            k = 0;
        }
        DIState() { uninit(); }
        DIState(int j_, int k_,
                Xapian::termpos pos_j_, Xapian::termpos pos_k_) {
            set_j(j_, pos_j_);
            set_k(k_, pos_k_);
        }
        void operator=(const DIStack& o) {
            j = o.j;
            set_k(o.k, o.pos_k);
        }
        bool is_next() const { return j + 1 < k; }
        bool is_initialized() const {
            return j <= k;
        }
        // Given pos[j] = pos_j and pos[k] = pos_k, how many possible position
        // values are there for the value midway between?
        Xapian::termpos outof() const {
            return pos_k - pos_j - Xapian::termpos(k - j) + 1;
        }
    };

    std::vector<DIStack> di_stack;
    DIState di_current;

  public:
    // Construct.
    BitReader() { }

    // Construct and set data.
    BitReader(const char* p_, const char* end_)
        : p(p_), end(end_), n_bits(0), acc(0) { }

    // Initialise with fresh data.
    void init(const char* p_, const char* end_) {
        p = p_;
        end = end_;
        n_bits = 0;
        acc = 0;
        di_stack.clear();
        di_current.uninit();
    }

    // Decode value, known to be less than outof.
    Xapian::termpos decode(Xapian::termpos outof, bool force = false);

    // Check all the data has been read.  Because it'll be zero padded
    // to fill a byte, the best we can actually do is check that
    // there's less than a byte left and that all remaining bits are
    // zero.
    bool check_all_gone() const {
        return (p == end && n_bits <= 7 && acc == 0);
    }

    /// Perform interpolative decoding between elements between j and k.
    void decode_interpolative(int j, int k,
                              Xapian::termpos pos_j, Xapian::termpos pos_k);

    /// Perform on-demand interpolative decoding.
    Xapian::termpos decode_interpolative_next();
};

}

using Xapian::BitWriter;
using Xapian::BitReader;

#endif // XAPIAN_INCLUDED_BITSTREAM_H
