/**
 * 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.
 */

#ifdef __cplusplus
extern "C" {
#endif

#include <string.h>
#include "bitstream.h"

void bitstream_writer_init(struct bitstream_writer_t *self_p,
                           uint8_t *buf_p)
{
    self_p->buf_p = buf_p;
    self_p->byte_offset = 0;
    self_p->bit_offset = 0;
}

int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p)
{
    return (8 * self_p->byte_offset + self_p->bit_offset);
}

int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p)
{
    return (self_p->byte_offset + (self_p->bit_offset + 7) / 8);
}

void bitstream_writer_write_bit(struct bitstream_writer_t *self_p,
                                int value)
{
    if (self_p->bit_offset == 0) {
        self_p->buf_p[self_p->byte_offset] = (value << 7);
        self_p->bit_offset = 1;
    } else {
        self_p->buf_p[self_p->byte_offset] |= (value << (8 - self_p->bit_offset - 1));

        if (self_p->bit_offset == 7) {
            self_p->bit_offset = 0;
            self_p->byte_offset++;
        } else {
            self_p->bit_offset++;
        }
    }
}

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

    dst_p = &self_p->buf_p[self_p->byte_offset];

    if (self_p->bit_offset == 0) {
        memcpy(dst_p, buf_p, sizeof(uint8_t) * length);
    } else {
        for (i = 0; i < length; i++) {
            dst_p[i] |= (buf_p[i] >> self_p->bit_offset);
            dst_p[i + 1] = (uint8_t)(buf_p[i] << (8 - self_p->bit_offset));
        }
    }

    self_p->byte_offset += length;
}

void bitstream_writer_write_u8(struct bitstream_writer_t *self_p,
                               uint8_t value)
{
    if (self_p->bit_offset == 0) {
        self_p->buf_p[self_p->byte_offset] = value;
    } else {
        self_p->buf_p[self_p->byte_offset] |= (value >> self_p->bit_offset);
        self_p->buf_p[self_p->byte_offset + 1] =
            (uint8_t)(value << (8 - self_p->bit_offset));
    }

    self_p->byte_offset++;
}

void bitstream_writer_write_u16(struct bitstream_writer_t *self_p,
                                uint16_t value)
{
    if (self_p->bit_offset == 0) {
        self_p->buf_p[self_p->byte_offset] = (value >> 8);
    } else {
        self_p->buf_p[self_p->byte_offset] |= (value >> (8 + self_p->bit_offset));
        self_p->buf_p[self_p->byte_offset + 2] =
            (uint8_t)(value << (8 - self_p->bit_offset));
        value >>= self_p->bit_offset;
    }

    self_p->buf_p[self_p->byte_offset + 1] = (uint8_t)value;
    self_p->byte_offset += 2;
}

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

    if (self_p->bit_offset == 0) {
        self_p->buf_p[self_p->byte_offset] = (value >> 24);
    } else {
        self_p->buf_p[self_p->byte_offset] |= (value >> (24 + self_p->bit_offset));
        self_p->buf_p[self_p->byte_offset + 4] =
            (uint8_t)(value << (8 - self_p->bit_offset));
        value >>= self_p->bit_offset;
    }

    for (i = 3; i > 0; i--) {
        self_p->buf_p[self_p->byte_offset + i] = value;
        value >>= 8;
    }

    self_p->byte_offset += 4;
}

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


    if (self_p->bit_offset == 0) {
        self_p->buf_p[self_p->byte_offset] = (value >> 56);
    } else {
        self_p->buf_p[self_p->byte_offset] |= (value >> (56 + self_p->bit_offset));
        self_p->buf_p[self_p->byte_offset + 8] =
            (uint8_t)(value << (8 - self_p->bit_offset));
        value >>= self_p->bit_offset;
    }

    for (i = 7; i > 0; i--) {
        self_p->buf_p[self_p->byte_offset + i] = (uint8_t)value;
        value >>= 8;
    }

    self_p->byte_offset += 8;
}

void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p,
                                     uint64_t value,
                                     int number_of_bits)
{
    int i;
    int first_byte_bits;
    int last_byte_bits;
    int full_bytes;

    if (number_of_bits == 0) {
        return;
    }

    /* Align beginning. */
    first_byte_bits = (8 - self_p->bit_offset);

    if (first_byte_bits != 8) {
        if (number_of_bits < first_byte_bits) {
            self_p->buf_p[self_p->byte_offset] |=
                (uint8_t)(value << (first_byte_bits - number_of_bits));
            self_p->bit_offset += number_of_bits;
        } else {
            self_p->buf_p[self_p->byte_offset] |= (value >> (number_of_bits
                                                             - first_byte_bits));
            self_p->byte_offset++;
            self_p->bit_offset = 0;
        }

        number_of_bits -= first_byte_bits;

        if (number_of_bits <= 0) {
            return;
        }
    }

    /* Align end. */
    last_byte_bits = (number_of_bits % 8);
    full_bytes = (number_of_bits / 8);

    if (last_byte_bits != 0) {
        self_p->buf_p[self_p->byte_offset + full_bytes] =
            (uint8_t)(value << (8 - last_byte_bits));
        value >>= last_byte_bits;
        self_p->bit_offset = last_byte_bits;
    }

    /* Copy middle bytes. */
    for (i = full_bytes; i > 0; i--) {
        self_p->buf_p[self_p->byte_offset + i - 1] = (uint8_t)value;
        value >>= 8;
    }

    self_p->byte_offset += full_bytes;
}

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

    if (value != 0) {
        value = 0xff;
    }

    rest = (length % 8);
    bitstream_writer_write_u64_bits(self_p, value & ((1 << rest) - 1), rest);
    bitstream_writer_write_repeated_u8(self_p, value, length / 8);
}

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

    for (i = 0; i < length; i++) {
        bitstream_writer_write_u8(self_p, value);
    }
}

void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p,
                                 int value)
{
    struct bitstream_writer_bounds_t bounds;

    bitstream_writer_bounds_save(&bounds,
                                 self_p,
                                 (8 * self_p->byte_offset) + self_p->bit_offset,
                                 1);
    bitstream_writer_write_bit(self_p, value);
    bitstream_writer_bounds_restore(&bounds);
}

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

    bitstream_writer_bounds_save(&bounds,
                                 self_p,
                                 (8 * self_p->byte_offset) + self_p->bit_offset,
                                 8 * length);
    bitstream_writer_write_bytes(self_p, buf_p, length);
    bitstream_writer_bounds_restore(&bounds);
}

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

    bitstream_writer_bounds_save(&bounds,
                                 self_p,
                                 (8 * self_p->byte_offset) + self_p->bit_offset,
                                 8);
    bitstream_writer_write_u8(self_p, value);
    bitstream_writer_bounds_restore(&bounds);
}

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

    bitstream_writer_bounds_save(&bounds,
                                 self_p,
                                 (8 * self_p->byte_offset) + self_p->bit_offset,
                                 16);
    bitstream_writer_write_u16(self_p, value);
    bitstream_writer_bounds_restore(&bounds);
}

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

    bitstream_writer_bounds_save(&bounds,
                                 self_p,
                                 (8 * self_p->byte_offset) + self_p->bit_offset,
                                 32);
    bitstream_writer_write_u32(self_p, value);
    bitstream_writer_bounds_restore(&bounds);
}

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

    bitstream_writer_bounds_save(&bounds,
                                 self_p,
                                 (8 * self_p->byte_offset) + self_p->bit_offset,
                                 64);
    bitstream_writer_write_u64(self_p, value);
    bitstream_writer_bounds_restore(&bounds);
}

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

    bitstream_writer_bounds_save(&bounds,
                                 self_p,
                                 (8 * self_p->byte_offset) + self_p->bit_offset,
                                 number_of_bits);
    bitstream_writer_write_u64_bits(self_p, value, number_of_bits);
    bitstream_writer_bounds_restore(&bounds);
}

void bitstream_writer_seek(struct bitstream_writer_t *self_p,
                           int offset)
{
    offset += ((8 * self_p->byte_offset) + self_p->bit_offset);
    self_p->byte_offset = (offset / 8);
    self_p->bit_offset = (offset % 8);
}

void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p,
                                  struct bitstream_writer_t *writer_p,
                                  int bit_offset,
                                  int length)
{
    int number_of_bits;

    self_p->writer_p = writer_p;
    number_of_bits = (bit_offset % 8);

    if (number_of_bits == 0) {
        self_p->first_byte_offset = -1;
    } else {
        self_p->first_byte_offset = (bit_offset / 8);
        self_p->first_byte = writer_p->buf_p[self_p->first_byte_offset];
        self_p->first_byte &= (0xff00 >> number_of_bits);
    }

    number_of_bits = ((bit_offset + length) % 8);

    if (number_of_bits == 0) {
        self_p->last_byte_offset = -1;
    } else {
        self_p->last_byte_offset = ((bit_offset + length) / 8);
        self_p->last_byte = writer_p->buf_p[self_p->last_byte_offset];
        self_p->last_byte &= ~(0xff00 >> number_of_bits);
        writer_p->buf_p[self_p->last_byte_offset] = 0;
    }

    if (self_p->first_byte_offset != -1) {
        writer_p->buf_p[self_p->first_byte_offset] = 0;
    }
}

void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p)
{
    if (self_p->first_byte_offset != -1) {
        self_p->writer_p->buf_p[self_p->first_byte_offset] |= self_p->first_byte;
    }

    if (self_p->last_byte_offset != -1) {
        self_p->writer_p->buf_p[self_p->last_byte_offset] |= self_p->last_byte;
    }
}

void bitstream_reader_init(struct bitstream_reader_t *self_p,
                           const uint8_t *buf_p)
{
    self_p->buf_p = buf_p;
    self_p->byte_offset = 0;
    self_p->bit_offset = 0;
}

int bitstream_reader_read_bit(struct bitstream_reader_t *self_p)
{
    int value;

    if (self_p->bit_offset == 0) {
        value = (self_p->buf_p[self_p->byte_offset] >> 7);
        self_p->bit_offset = 1;
    } else {
        value = ((self_p->buf_p[self_p->byte_offset] >> (7 - self_p->bit_offset)) & 0x1);

        if (self_p->bit_offset == 7) {
            self_p->bit_offset = 0;
            self_p->byte_offset++;
        } else {
            self_p->bit_offset++;
        }
    }

    return (value);
}

void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p,
                                 uint8_t *buf_p,
                                 int length)
{
    int i;
    const uint8_t *src_p;

    src_p = &self_p->buf_p[self_p->byte_offset];

    if (self_p->bit_offset == 0) {
        memcpy(buf_p, src_p, sizeof(uint8_t) * length);
    } else {
        for (i = 0; i < length; i++) {
            buf_p[i] = (src_p[i] << self_p->bit_offset);
            buf_p[i] |= (src_p[i + 1] >> (8 - self_p->bit_offset));
        }
    }

    self_p->byte_offset += length;
}

uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p)
{
    uint8_t value;

    value = (self_p->buf_p[self_p->byte_offset] << self_p->bit_offset);
    self_p->byte_offset++;

    if (self_p->bit_offset != 0) {
        value |= (self_p->buf_p[self_p->byte_offset] >> (8 - self_p->bit_offset));
    }

    return (value);
}

uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p)
{
    uint16_t value;
    int i;
    int offset;
    const uint8_t *src_p;

    src_p = &self_p->buf_p[self_p->byte_offset];
    offset = (16 + self_p->bit_offset);
    value = 0;

    for (i = 0; i < 2; i++) {
        offset -= 8;
        value |= ((uint16_t)src_p[i] << offset);
    }

    if (offset != 0) {
        value |= (src_p[2] >> (8 - offset));
    }

    self_p->byte_offset += 2;

    return (value);
}

uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p)
{
    uint32_t value;
    int i;
    int offset;
    const uint8_t *src_p;

    src_p = &self_p->buf_p[self_p->byte_offset];
    offset = (32 + self_p->bit_offset);
    value = 0;

    for (i = 0; i < 4; i++) {
        offset -= 8;
        value |= ((uint32_t)src_p[i] << offset);
    }

    if (offset != 0) {
        value |= (src_p[4] >> (8 - offset));
    }

    self_p->byte_offset += 4;

    return (value);
}

uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p)
{
    uint64_t value;
    int i;
    int offset;
    const uint8_t *src_p;

    src_p = &self_p->buf_p[self_p->byte_offset];
    offset = (64 + self_p->bit_offset);
    value = 0;

    for (i = 0; i < 8; i++) {
        offset -= 8;
        value |= ((uint64_t)src_p[i] << offset);
    }

    if (offset != 0) {
        value |= ((uint64_t)src_p[8] >> (8 - offset));
    }

    self_p->byte_offset += 8;

    return (value);
}

uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p,
                                        int number_of_bits)
{
    uint64_t value;
    int i;
    int first_byte_bits;
    int last_byte_bits;
    int full_bytes;

    if (number_of_bits == 0) {
        return (0);
    }

    /* Align beginning. */
    first_byte_bits = (8 - self_p->bit_offset);

    if (first_byte_bits != 8) {
        if (number_of_bits < first_byte_bits) {
            value = (self_p->buf_p[self_p->byte_offset] >> (first_byte_bits
                                                            - number_of_bits));
            value &= ((1 << number_of_bits) - 1);
            self_p->bit_offset += number_of_bits;
        } else {
            value = self_p->buf_p[self_p->byte_offset];
            value &= ((1 << first_byte_bits) - 1);
            self_p->byte_offset++;
            self_p->bit_offset = 0;
        }

        number_of_bits -= first_byte_bits;

        if (number_of_bits <= 0) {
            return (value);
        }
    } else {
        value = 0;
    }

    /* Copy middle bytes. */
    full_bytes = (number_of_bits / 8);

    for (i = 0; i < full_bytes; i++) {
        value <<= 8;
        value |= self_p->buf_p[self_p->byte_offset + i];
    }

    /* Last byte. */
    last_byte_bits = (number_of_bits % 8);

    if (last_byte_bits != 0) {
        value <<= last_byte_bits;
        value |= (self_p->buf_p[self_p->byte_offset + full_bytes]
                  >> (8 - last_byte_bits));
        self_p->bit_offset = last_byte_bits;
    }

    self_p->byte_offset += full_bytes;

    return (value);
}

void bitstream_reader_seek(struct bitstream_reader_t *self_p,
                           int offset)
{
    offset += ((8 * self_p->byte_offset) + self_p->bit_offset);
    self_p->byte_offset = (offset / 8);
    self_p->bit_offset = (offset % 8);
}

int bitstream_reader_tell(struct bitstream_reader_t *self_p)
{
    return ((8 * self_p->byte_offset) + self_p->bit_offset);
}

#ifdef __cplusplus
}
#endif
