//
// bitreader  --  wrapper class for bit input
//
// Copyright (c) 2003 by Joergen Ibsen / Jibz
// All Rights Reserved
//
// http://www.ibsensoftware.com/
//

/// @file   bitreader.hpp
/// @brief  Base class for bit input wrappers.
/// @author Jibz
/// @date   2003.08.02

#ifndef BITREADER_HPP_INCLUDED
#define BITREADER_HPP_INCLUDED

#include <iostream>
#include <string>
#include <cstddef>
#include <climits>

// ms vc (.net2003) warning about performance loss when casting int to bool
#ifdef _MSC_VER
#pragma warning(disable:4800)
#endif

/// Base class for bit input wrappers.
///
/// The bitreader class is a wrapper which provides bit input functionality
/// to anything supporting input of 8-bit chars (bytes). It attempts to be
/// as clean and transparent as possible, while maintaining a fairly decent
/// efficiency.
///
/// The base class contains functions for handling the input by holding bits
/// in a temporary store (a byte), reading more when the store is empty.
///
/// It is used by creating a sub-class and implementing the read_next_byte()
/// and supports_seek() member functions.
///
/// The constructor of a derived class should initialize #length (either
/// directly or through the base constructor). The destructor of the derived
/// class should call finalize().
///
/// @ingroup bitio
///
class bitreader {

public:
    /// Constructor.
    /// @param size_in_bits - the number of bits available.
    bitreader(size_t size_in_bits=0)
	: offs(0), length(size_in_bits), current(0), mask(0) { ; }

    /// Destructor. Derived classes should implement a destructor
    /// which calls the finalize() member function.
    virtual ~bitreader() { ; }

public:
    /// Check for error.
    /// @return @c true if an error occurred.
    virtual bool error() const { return false; }

    /// Align bitreader on a byte boundary.
    void align_byte() { offs = (offs + 7) & ~0x0007; mask = 0; }

public:
    /// @name Size Functions
    /// @{

    /// Check if bitreader is empty.
    /// @return @c true if bitreader is empty, otherwise @c false.
    bool empty() const { return offs >= length; }
    /// Return the number of bits left.
    /// @return the number of bits left in the bitreader.
    size_t bits_left() const { return length - offs; }
    /// Return the number of bytes left.
    /// @return the number of bytes left in the bitreader.
    size_t bytes_left() const { return bits_left() / 8; }

    /// Return the total number of bits available.
    /// @return the number of bits available.
    size_t get_bit_length() const { return length; }

    /// Return the total number of bytes available.
    /// @return the number of bytes available.
    size_t get_byte_length() const { return get_bit_length() / 8; }

    /// Return the current bit position within the bit stream.
    /// @return the current bit position.
    size_t tell_bit() const { return offs; }

    /// Return the current byte position within the bit stream.
    /// @return the current byte position.
    size_t tell_byte() const { return tell_bit() / 8; }

    /// @}

public:
    /// @name Seek Functions
    /// @{

    /// Check if seek functions are supported.
    /// @return @c true if seek functions are supported.
    virtual bool supports_seek() const = 0;

    /// Seek to a specified bit position within the bit stream.
    /// @param pos - the bit position to seek to.
    /// @return the new position.
    size_t seek_bit(size_t pos) { offs = pos; reposition(); return offs; }

    /// Seek to a specified byte position within the bit stream.
    /// @param pos - the byte position to seek to.
    /// @return the new position.
    size_t seek_byte(size_t pos) { return seek_bit(8 * pos); }

    /// Seek to a specified relative bit position within the bit stream.
    /// @param pos  - the relative bit position to seek to.
    /// @param rpos - the position to seek relative to,
    ///  @arg @c std::ios::beg - beginning of stream
    ///  @arg @c std::ios::cur - current position
    ///  @arg @c std::ios::end - end of stream
    /// @return the new position.
    size_t seek_bit(int pos, std::ios::seekdir rpos)
    {
	switch (rpos)
	{
	case std::ios::beg:
	    return seek_bit(pos);
	    break;
	case std::ios::cur:
	    return seek_bit(offs + pos);
	    break;
	case std::ios::end:
	    return seek_bit(length - pos);
	    break;
	}
	return 0;
    }

    /// Seek to a specified relative byte position within the bit stream.
    /// @param pos  - the relative byte position to seek to.
    /// @param rpos - the position to seek relative to,
    ///  @arg @c std::ios::beg - beginning of stream
    ///  @arg @c std::ios::cur - current position
    ///  @arg @c std::ios::end - end of stream
    /// @return the new position.
    size_t seek_byte(int pos, std::ios::seekdir rpos)
    {
	return seek_bit(8 * pos, rpos);
    }

    /// @}

public:
    /// @name Input Functions
    /// @{

    /// Get and return the next bit.
    /// @return the next bit (@c true for 1, @c false for 0).
    bool get_bit()
    {
	++offs;

	if (!mask)
	{
	    current = read_next_byte();
	    mask = 0x80;
	}

	const bool bit = (bool)(current & mask);
	mask >>= 1;

	return bit;
    }

    /// Get and return the next byte (8-bit unsigned value).
    /// @return the next 8 bits.
    unsigned char get_byte()
    {
	offs += 8;

	if (!mask) return read_next_byte();

	size_t bit_offs = offs & 0x0007;
	unsigned char tmp = current << (unsigned char)bit_offs;
	current = read_next_byte();

	return tmp | (current >> (8 - (unsigned char)bit_offs));
    }

    /// Get and return the next word (16-bit unsigned value).
    /// @return the next 16 bits.
    unsigned short get_word()
    {
	unsigned short v = get_byte();
	v = (v << 8) | get_byte();

	return v;
    }

    /// Get and return the next dword (32-bit unsigned value).
    /// @return the next 32 bits.
    unsigned long get_dword()
    {
	unsigned long v = get_byte();
	v = (v << 8) | get_byte();
	v = (v << 8) | get_byte();
	v = (v << 8) | get_byte();

	return v;
    }

    /// Get and return the next float.
    /// @return the next float.
    /// @warning Experimental, not portable.
    /// @todo    Experimental, not portable.
    float get_float()
    {
	float v;
	read((char*)&v, sizeof(float));
	return v;
    }

    /// Get and return the next double.
    /// @return the next double.
    /// @warning Experimental, not portable.
    /// @todo    Experimental, not portable.
    double get_double()
    {
	double v;
	read((char*)&v, sizeof(double));
	return v;
    }

    /// Read and return a string.
    /// @return the string.
    std::string get_string()
    {
	size_t len = get_word();

	if (len == 0) len = get_dword();

	std::string s;

	s.reserve(len);
	for ( ; len; --len) s += get_byte();

	return s;
    }

    /// Copy bytes from the bitreader to an array.
    /// @param buffer - pointer to where the bytes should be stored.
    /// @param len    - the number of bytes to copy.
    void read(char *buffer, size_t len)
    {
	for ( ; len; --len) *buffer++ = get_byte();
    }

    /// @}

public:
    /// @name Polymorphic Input Function Wrappers
    /// @{

    void get(bool &v) { v = get_bit(); }
    void get(char &v) { v = get_byte(); }
    void get(unsigned char &v) { v = get_byte(); }
    void get(short &v) { v = get_word(); }
    void get(unsigned short &v) { v = get_word(); }
#if UINT_MAX == 0xffff
    void get(int &v) { v = get_word(); }
    void get(unsigned int &v) { v = get_word(); }
#else
    void get(int &v) { v = get_dword(); }
    void get(unsigned int &v) { v = get_dword(); }
#endif
    void get(long &v) { v = get_dword(); }
    void get(unsigned long &v) { v = get_dword(); }

    void get(float &v) { read((char*)&v, sizeof(float)); }
    void get(double &v) { read((char*)&v, sizeof(double)); }
    void get(std::string &s) { s = get_string(); }

    /// @}

protected:
    /// Perform cleanup. Derived classes should call this method
    /// from their destructor.
    void finalize() { align_byte(); }

    /// Defines the method of reading a single byte of input.
    /// Derived classes must implement this function.
    /// @return the next byte (8 bits) from the input.
    virtual char read_next_byte() = 0;

    /// Defines the method of setting the position for the next read.
    /// Derived classes should implement this function if possible.
    /// @param pos - the position for the next read.
    virtual void set_position(size_t pos) { ; }

    /// Helper function for reinitializing current and mask after
    /// a seek operation.
    void reposition()
    {
	size_t byte_offs = offs / 8;
	size_t bit_offs = offs & 0x0007;

	set_position(byte_offs);

	mask = 0;

	if (bit_offs)
	{
	    mask = 1 << (7 - (unsigned int)bit_offs);

	    current = read_next_byte();
	}
    }

    size_t offs;    ///< Current bit position within the bit stream.
    size_t length;  ///< Total number of bits available.

private:
    unsigned char current;  ///< The current input byte.
    unsigned char mask;     ///< Mask to access the current bit.
};

#endif // __BITREADER_HPP_INCLUDED
