// Escritura y lectura de bits sobre un arreglo de bytes. Viene hecho, con su orden fijo:
// dentro de cada byte se llena primero el bit más significativo.
#ifndef BIT_IO_H
#define BIT_IO_H

#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <vector>

using Bytes = std::vector<std::uint8_t>;

// Agrega bits al final de `out`. Llamen a finish() una vez, al terminar.
class BitWriter {
public:
    explicit BitWriter(Bytes& out) : out_(out) {}

    void put_bit(bool bit) {
        current_ = static_cast<std::uint8_t>((current_ << 1) | (bit ? 1 : 0));
        used_++;
        if (used_ == 8) {
            out_.push_back(current_);
            current_ = 0;
            used_ = 0;
        }
    }

    void put_byte(std::uint8_t byte) {
        for (int i = 7; i >= 0; i--) {
            put_bit(((byte >> i) & 1) != 0);
        }
    }

    // Completa el último byte con ceros, si quedó a medias.
    void finish() {
        while (used_ != 0) {
            put_bit(false);
        }
    }

private:
    Bytes& out_;
    std::uint8_t current_ = 0;
    int used_ = 0;
};

// Lee bits de `data`, desde el byte `start`. Si se acaban, lanza std::runtime_error.
class BitReader {
public:
    BitReader(const Bytes& data, std::size_t start) : data_(data), position_(start) {}

    bool get_bit() {
        if (position_ >= data_.size()) {
            throw std::runtime_error("archivo truncado");
        }
        bool bit = ((data_[position_] >> (7 - used_)) & 1) != 0;
        used_++;
        if (used_ == 8) {
            used_ = 0;
            position_++;
        }
        return bit;
    }

    std::uint8_t get_byte() {
        int byte = 0;
        for (int i = 0; i < 8; i++) {
            byte = (byte << 1) | (get_bit() ? 1 : 0);
        }
        return static_cast<std::uint8_t>(byte);
    }

private:
    const Bytes& data_;
    std::size_t position_;
    int used_ = 0;
};

#endif
