#pragma once

namespace bitreader_helper {

	inline static size_t extract_bit(const t_uint8 * p_stream,size_t p_offset) {
		return (p_stream[p_offset>>3] >> (7-(p_offset&7)))&1;
	}

	inline static size_t extract_int(const t_uint8 * p_stream,size_t p_base,size_t p_width) {
		size_t ret = 0;
		size_t offset = p_base;
		for(size_t bit=0;bit<p_width;bit++) {
			ret <<= 1;
			ret |= extract_bit(p_stream,offset++);
		}
		return ret;
	}

class bitreader
{
public:
	inline bitreader(const t_uint8 * p_ptr,t_size p_base)
		: m_ptr(p_ptr), m_bitptr(p_base)
	{
	}

	inline void skip(t_size p_bits)
	{
		m_bitptr += p_bits;
	}

	template<typename t_ret>
	t_ret read_t(t_size p_bits) {
		t_ret ret = 0;
		for(t_size bit=0;bit<p_bits;bit++)
		{
			ret <<= 1;
			ret |= (m_ptr[m_bitptr>>3] >> (7-(m_bitptr&7)))&1;
			m_bitptr++;
		}
		return ret;
	}

	t_size read(t_size p_bits) {return read_t<t_size>(p_bits);}

	inline t_size get_bitptr() const {return m_bitptr;}

	inline bool read_bit() {
		bool state = ( (m_ptr[m_bitptr>>3] >> (7-(m_bitptr&7)))&1 ) != 0;
		m_bitptr++;
		return state;
	}

private:

	const t_uint8 * m_ptr;
	t_size m_bitptr;
};

class bitreader_fromfile
{
public:
	inline bitreader_fromfile(service_ptr_t<file> const& p_file) : m_file(p_file), m_buffer_ptr(0) {}
	
	t_size read(t_size p_bits,abort_callback & p_abort) {
		t_size ret = 0;
		for(t_size bit=0;bit<p_bits;bit++) {
			if (m_buffer_ptr == 0)
				m_file->read_object(&m_buffer,1,p_abort);

			ret <<= 1;
			ret |= (m_buffer >> (7-m_buffer_ptr))&1;
			m_buffer_ptr = (m_buffer_ptr+1) & 7;
		}
		return ret;
	}

	void skip(t_size p_bits,abort_callback & p_abort) {
		for(t_size bit=0;bit<p_bits;bit++) {
			if (m_buffer_ptr == 0) m_file->read_object(&m_buffer,1,p_abort);
			m_buffer_ptr = (m_buffer_ptr+1) & 7;
		}
	}

	inline void byte_align() {m_buffer_ptr = 0;}

private:
	service_ptr_t<file> m_file;
	t_size m_buffer_ptr;
	t_uint8 m_buffer;
};

class bitreader_limited
{
public:
	inline bitreader_limited(const t_uint8 * p_ptr,t_size p_base,t_size p_remaining) : m_reader(p_ptr,p_base), m_remaining(p_remaining) {}

	inline t_size get_bitptr() const {return m_reader.get_bitptr();}

	inline t_size get_remaining() const {return m_remaining;}

	inline void skip(t_size p_bits) {
		if (p_bits > m_remaining) throw exception_io_data_truncation();
		m_remaining -= p_bits;
		m_reader.skip(p_bits);
	}

	t_size read(t_size p_bits)
	{
		if (p_bits > m_remaining) throw exception_io_data_truncation();
		m_remaining -= p_bits;
		return m_reader.read(p_bits);
	}

private:
	bitreader m_reader;
	t_size m_remaining;
};

inline static t_size extract_bits(const t_uint8 * p_buffer,t_size p_base,t_size p_count) {
	return bitreader(p_buffer,p_base).read(p_count);
}

}