#ifndef _mpeg4_bits_h_
#define _mpeg4_bits_h_

#include <stdint.h>
#include <stddef.h>
#include <assert.h>

#ifdef __cplusplus
extern "C" {
#endif

struct mpeg4_bits_t
{
	uint8_t* data;
	size_t size;
	size_t bits; // offset bit
	int error;
};

#define mpeg4_bits_read_uint8(bits, n)		(uint8_t)mpeg4_bits_read_n(bits, n)
#define mpeg4_bits_read_uint16(bits, n)		(uint16_t)mpeg4_bits_read_n(bits, n)
#define mpeg4_bits_read_uint32(bits, n)		(uint32_t)mpeg4_bits_read_n(bits, n)
#define mpeg4_bits_read_uint64(bits, n)		(uint64_t)mpeg4_bits_read_n(bits, n)
#define mpeg4_bits_write_uint8(bits, v, n)	mpeg4_bits_write_n(bits, (uint64_t)v, n)
#define mpeg4_bits_write_uint16(bits, v, n) mpeg4_bits_write_n(bits, (uint64_t)v, n)
#define mpeg4_bits_write_uint32(bits, v, n) mpeg4_bits_write_n(bits, (uint64_t)v, n)
#define mpeg4_bits_write_uint64(bits, v, n) mpeg4_bits_write_n(bits, (uint64_t)v, n)

static inline void mpeg4_bits_init(struct mpeg4_bits_t* bits, void* data, size_t size)
{
	bits->data = (uint8_t*)data;
	bits->size = size;
	bits->bits = 0;
	bits->error = 0;
}

/// @return 1-error, 0-no error
static inline int mpeg4_bits_error(struct mpeg4_bits_t* bits)
{
	//return bits->bits >= bits->size * 8 ? 1 : 0;
	return bits->error;
}

static inline void mpeg4_bits_aligment(struct mpeg4_bits_t* bits, int n)
{
	bits->bits = (bits->bits + n - 1) / n * n;
}

static inline size_t mpeg4_bits_remain(struct mpeg4_bits_t* bits)
{
	return bits->error ? 0 : (bits->size * 8 - bits->bits);
}

static inline void mpeg4_bits_skip(struct mpeg4_bits_t* bits, size_t n)
{
	bits->bits += n;
	if (bits->bits > bits->size * 8)
	{
		bits->error = -1;
	}
}

/// read 1-bit from bit stream(offset position)
/// @param[in] bits bit stream
/// @return -1-error, 1-value, 0-value
static inline int mpeg4_bits_read(struct mpeg4_bits_t* bits)
{
	uint8_t bit;
	assert(bits && bits->data && bits->size > 0);
	if (bits->bits >= bits->size * 8)
	{
		bits->error = -1;
		return 0; // throw exception
	}

	bit = bits->data[bits->bits/8] & (0x80U >> (bits->bits%8));
	bits->bits += 1; // update offset
	return bit ? 1 : 0;
}

/// read n-bit(n <= 64) from bit stream(offset position)
/// @param[in] bits bit stream
/// @return -1-error, other-value
static inline uint64_t mpeg4_bits_read_n(struct mpeg4_bits_t* bits, int n)
{
	int m;
	size_t i;
	uint64_t v;

	assert(n > 0 && n <= 64);
	assert(bits && bits->data && bits->size > 0);
	if (bits->bits + n > bits->size * 8 || n > 64 || n < 0)
	{
		bits->error = -1;
		return 0; // throw exception
	}

	m = n;
	v = bits->data[bits->bits / 8] & (0xFFU >> (bits->bits%8)); // remain valid value
	if (n <= 8 - (int)(bits->bits % 8))
	{
		v = v >> (8 - (bits->bits % 8) - n); // shift right value
		bits->bits += n;
		return v;
	}

	n -= 8 - (int)(bits->bits % 8);
	for (i = 1; n >= 8; i++)
	{
		assert(bits->bits / 8 + i < bits->size);
		v <<= 8;
		v += bits->data[bits->bits / 8 + i];
		n -= 8;
	}

	if (n > 0)
	{
		v <<= n;
		v += bits->data[bits->bits / 8 + i] >> (8 - n);
	}

	bits->bits += m;
	return v;
}

// http://aomedia.org/av1/specification/conventions/#descriptors
static inline uint64_t mpeg4_bits_read_uvlc(struct mpeg4_bits_t* bits)
{
	uint64_t value;
	int leadingZeros;
	for (leadingZeros = 0; !mpeg4_bits_read(bits); ++leadingZeros)
	{
	}

	if (leadingZeros >= 32)
		return (1ULL << 32) - 1;
	
	value = leadingZeros > 0 ? mpeg4_bits_read_n(bits, leadingZeros) : 0;
	return (1ULL << leadingZeros) - 1 + value;
}

static inline uint64_t mpeg4_bits_read_latm(struct mpeg4_bits_t* bits)
{
	int len;
	len = (int)mpeg4_bits_read_n(bits, 2);
	return mpeg4_bits_read_n(bits, (len + 1) * 8);
}

/// write 1-bit
/// @param[in] v write 0 if v value 0, other, write 1
static inline int mpeg4_bits_write(struct mpeg4_bits_t* bits, int v)
{
	assert(bits && bits->data && bits->size > 0);
	if (bits->bits >= bits->size * 8)
	{
		bits->error = -1;
		return -1; // throw exception
	}

	if(v)
		bits->data[bits->bits / 8] |= (0x80U >> (bits->bits % 8));
	bits->bits += 1; // update offset
	return 0;
}

static inline int mpeg4_bits_write_n(struct mpeg4_bits_t* bits, uint64_t v, int n)
{
	int m;
	size_t i;

	assert(n > 0 && n <= 64);
	assert(bits && bits->data && bits->size > 0);
	if (bits->bits + n > bits->size * 8 || n > 64 || n < 0)
	{
		bits->error = -1;
		return -1; // throw exception
	}

	m = n;
	v = v << (64 - n); // left shift to first bit

	bits->data[bits->bits / 8] |= v >> (56 + (bits->bits % 8)); // remain valid value
	v <<= 8 - (bits->bits % 8);
	n -= 8 - (int)(bits->bits % 8);

	for (i = 1; n > 0; i++)
	{
		assert(bits->bits / 8 + i < bits->size);
		bits->data[bits->bits / 8 + i] = (uint8_t)(v >> 56);
		v <<= 8;
		n -= 8;
	}

	bits->bits += m;
	return 0;
}

#ifdef __cplusplus
}
#endif
#endif /* !_mpeg4_bits_h_ */
