/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Erik Moqvist
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef BITSTREAM_H
#define BITSTREAM_H

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

#include <stdint.h>

#define BITSTREAM_VERSION "0.8.0"

struct bitstream_writer_t {
    uint8_t *buf_p;
    int byte_offset;
    int bit_offset;
};

struct bitstream_writer_bounds_t {
    struct bitstream_writer_t *writer_p;
    int first_byte_offset;
    uint8_t first_byte;
    int last_byte_offset;
    uint8_t last_byte;
};

struct bitstream_reader_t {
    const uint8_t *buf_p;
    int byte_offset;
    int bit_offset;
};

/*
 * The writer.
 */

void bitstream_writer_init(struct bitstream_writer_t *self_p,
                           uint8_t *buf_p);

int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p);

int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p);

/* Write bits to the stream. Clears each byte before bits are
   written. */
void bitstream_writer_write_bit(struct bitstream_writer_t *self_p,
                                int value);

void bitstream_writer_write_bytes(struct bitstream_writer_t *self_p,
                                  const uint8_t *buf_p,
                                  int length);

void bitstream_writer_write_u8(struct bitstream_writer_t *self_p,
                               uint8_t value);

void bitstream_writer_write_u16(struct bitstream_writer_t *self_p,
                                uint16_t value);

void bitstream_writer_write_u32(struct bitstream_writer_t *self_p,
                                uint32_t value);

void bitstream_writer_write_u64(struct bitstream_writer_t *self_p,
                                uint64_t value);

/* Upper unused bits must be zero. */
void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p,
                                     uint64_t value,
                                     int number_of_bits);

void bitstream_writer_write_repeated_bit(struct bitstream_writer_t *self_p,
                                         int value,
                                         int length);

void bitstream_writer_write_repeated_u8(struct bitstream_writer_t *self_p,
                                        uint8_t value,
                                        int length);

/* Insert bits into the stream. Leaves all other bits unmodified. */
void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p,
                                 int value);

void bitstream_writer_insert_bytes(struct bitstream_writer_t *self_p,
                                   const uint8_t *buf_p,
                                   int length);

void bitstream_writer_insert_u8(struct bitstream_writer_t *self_p,
                                uint8_t value);

void bitstream_writer_insert_u16(struct bitstream_writer_t *self_p,
                                 uint16_t value);

void bitstream_writer_insert_u32(struct bitstream_writer_t *self_p,
                                 uint32_t value);

void bitstream_writer_insert_u64(struct bitstream_writer_t *self_p,
                                 uint64_t value);

void bitstream_writer_insert_u64_bits(struct bitstream_writer_t *self_p,
                                      uint64_t value,
                                      int number_of_bits);

/* Move write position. Seeking backwards makes the written size
   smaller. Use write with care after seek, as seek does not clear
   bytes. */
void bitstream_writer_seek(struct bitstream_writer_t *self_p,
                           int offset);

/* Save-restore first and last bytes in given range, so write can be
   used in given range. */
void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p,
                                  struct bitstream_writer_t *writer_p,
                                  int bit_offset,
                                  int length);

void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p);

/*
 * The reader.
 */

void bitstream_reader_init(struct bitstream_reader_t *self_p,
                           const uint8_t *buf_p);

/* Read bits from the stream. */
int bitstream_reader_read_bit(struct bitstream_reader_t *self_p);

void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p,
                                 uint8_t *buf_p,
                                 int length);

uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p);

uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p);

uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p);

uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p);

uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p,
                                        int number_of_bits);

/* Move read position. */
void bitstream_reader_seek(struct bitstream_reader_t *self_p,
                           int offset);

/* Get read position. */
int bitstream_reader_tell(struct bitstream_reader_t *self_p);

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // BITSTREAM_H
