///////////////////////////////////////////////////////////////////////////////
//
//
//    ビット指向入出力クラス
//
//
#ifndef BSTREAM_H
#define BSTREAM_H

#include "stream.h"


#define BIG_ENDIAN 0
#define SMALL_ENDIAN 1

#define SYSTEM_ENDIAN SMALL_ENDIAN

typedef unsigned long  m_ui32;
typedef unsigned short m_ui16;
typedef unsigned char  m_ui8;



///////////////////////////////////////////////////////////////////////////////
// ファイル限定マクロ - ファイルの最後で undef

// 例外のセット
#define UNEXPECTED_EOF_EXCEPTION()  "unexpected eof exception"
#define NULL_POINTER_EXCEPTION()    "null pointer exception"
#define IO_FAILED_EXCEPTION()       "IO failed exception"



///////////////////////////////////////////////////////////////////////////////
//
//  ビット指向入力クラス
//
class BitwiseInputStreamReader {

  InputStream *stream_;
  m_ui32 buffer_;
  unsigned int left_;
  bool eof_;

  unsigned int pointer_;

  void fill_buffer() {
    unsigned char c;
    if (eof_) return;

    while (left_ < 25) {
      if (stream_->read(&c, 1) == 0) {
        eof_ = true;
        break;
      }
      buffer_ = (buffer_ << 8) | c;
      left_ += 8;
      ++pointer_;
    }
  }

  void require(unsigned bits) {
    fill_buffer();

    if (left_ < bits)
      throw UNEXPECTED_EOF_EXCEPTION();
  }


public:
  //! 今現在，ファイルのどこを読んでいるか
  unsigned int getPointer() const {
    if (24 < left_) return pointer_ - 4;
    if (16 < left_) return pointer_ - 3;
    if ( 8 < left_) return pointer_ - 2;
    if ( 0 < left_) return pointer_ - 1;
    return pointer_;
  }


  /*! ビット位置
   * @return 0〜7  ビット位置 -- MSB は 0
   */
  unsigned int getBitPos() const {
    return (32 - left_) & 7;
  }

  //! コンストラクタ -- InputStream 指定
  BitwiseInputStreamReader(InputStream *stream) {
    if (!stream)
      throw NULL_POINTER_EXCEPTION();

    buffer_ = 0;
    left_ = 0;
    eof_ = false;
    stream_ = stream;

    pointer_ = 0;
  }



  //! dtor
  ~BitwiseInputStreamReader() {
    delete stream_;
  }


  //! InputStream オブジェクトの破棄責任を放棄
  InputStream * detach() {
    InputStream *obj = stream_;
    stream_ = 0;
    return obj;
  }



  /*! 読み込み位置は 8 ビット単位に揃っているか？
   * @retval true  揃っている
   * @retval false 揃っていない
   */
  bool isAligned() {
    return (left_ & 7) == 0;
  }



  //! 読み込み位置を 8 ビット単位に揃える
  void beAligned() {
    left_ -= (left_ & 7);
  }



  //! 1 ビット読み込み
  m_ui32 show1() {
    if (!left_) require(1);
    return (buffer_ >> (left_-1)) & 1;
  }



  //! 1 ビット読み込み
  m_ui32 read1() {
    if (!left_) require(1);
    --left_;
    return (buffer_ >> left_) & 1;
  }



  /*! bits ビット読み込み
   * @param bits  読み込むビット -- 25 以内であること
   *          26 ビット以上読み込むと失敗する場合がある
   */
  m_ui32 show(unsigned int bits) {
    require(bits);
    return (buffer_ >> (left_ - bits)) & ((1 << bits) - 1);
  }



  /*! bits ビット読み込み
   * @param bits  読み込むビット -- 25 以内であること
   *          26 ビット以上読み込むと失敗する場合がある
   */
  m_ui32 read(unsigned int bits) {
    require(bits);
    left_ -= bits;
    return (buffer_ >> left_) & ((1 << bits) - 1);
  }



  //! 読み込み位置を 8 ビット単位に揃えて 8 ビット先読み
  m_ui32 show8() {
    beAligned();
    require(8);
    return (buffer_ >> (left_ - 8)) & 0xff;
  }


  //! 読み込み位置を 8 ビット単位に揃えて 8 ビット読み込み
  m_ui32 read8() {
    beAligned();
    require(8);
    left_ -= 8;
    return (buffer_ >> left_) & 0xff;
  }


  //! 読み込み位置を 8 ビット単位に揃えて 16 ビット先読み
  m_ui32 show16() {
    beAligned();
    require(16);
    return (buffer_ >> (left_ - 16)) & 0xffff;
  }


  //! 読み込み位置を 8 ビット単位に揃えて 16 ビット読み込み
  m_ui32 read16() {
    beAligned();
    require(16);
    left_ -= 16;
    return (buffer_ >> left_) & 0xffff;
  }


  //! 読み込み位置を 8 ビット単位に揃えて 32 ビット先読み
  m_ui32 show32() {
    beAligned();
    require(32);
    return buffer_;
  }


  //! 読み込み位置を 8 ビット単位に揃えて 32 ビット読み込み
  m_ui32 read32() {
    beAligned();
    require(32);
    left_ = 0;
    return buffer_;
  }

};




///////////////////////////////////////////////////////////////////////////////
//
//  ビット指向出力クラス
//
class BitwiseOutputStreamWriter {
  OutputStream *stream_;
  unsigned int left_;
  m_ui32 buffer_;


  void write_buffer() {
    unsigned char c;

    while (left_ < 25) {
      c = (unsigned char)((buffer_ >> 24) & 0xff);
      stream_->write(&c, 1);
      left_ += 8;
      buffer_ <<= 8;
    }

  }

  void require(unsigned int bits) {
    write_buffer();
    if (left_ < bits)
      throw IO_FAILED_EXCEPTION();
  }


public:
  /*! コンストラクタ -- OutputStream 指定
   */
  BitwiseOutputStreamWriter(OutputStream *stream) {
    if (! stream)
      throw NULL_POINTER_EXCEPTION();

    stream_ = stream;
    left_ = 32;
    buffer_ = 0;
  }


  //! dtor
  ~BitwiseOutputStreamWriter() {
    flush();
    delete stream_;
  }



  //! OutputStream オブジェクトの破棄責任を放棄する
  OutputStream * detach() {
    OutputStream * obj = stream_;
    stream_ = 0;
    return obj;
  }



  /*! 8 ビットに揃っているかどうか
   * @retval true  揃っている
   * @retval false 揃っていない
   */
  bool isAligned() const {
    return (left_ & 7) == 0;
  }



  /*! 8 ビットに揃える
   * 途中の部分は 0 で埋まる
   */
  void beAligned() {
    if (left_ & 7) {
      buffer_ &= 0xffffffff - ((1 << left_) - 1);
      left_ -= (left_ & 7);
    }
  }



  /*! バッファに残っている分を全て書き込む
   * 8 ビットに揃っていなかった場合，0 で埋まる
   */
  void flush() {
    beAligned();
    write_buffer();
  }



  /*! bits ビットをバッファに書き出す
   * bits は 24 以下であること
   * 25 以上の場合，成功する場合もあるし失敗する場合もある
   *  (事前に flush() をしておけば必ず成功する)
   * @param v    書き出す値
   * @param bits 書き出すサイズ
   */
  void write(m_ui32 v, unsigned int bits) {
    require(bits);
    v &= (1 << bits) - 1;
    left_ -= bits;
    buffer_ |= v << left_;
  }



  /*! ビット位置を 8 ビット単位に揃えて 8 ビット書き出す
   * もしこれらのメソッド付近で異常があるなら，
   * SYSTEM_ENDIAN の定義を見直す
   * @param v 書き出す値
   */
  void write8(m_ui32 v) {
    flush();

#if SYSTEM_ENDIAN == BIG_ENDIAN
    v <<= 24;
#endif

    stream_->write(&v, 1);
  }



  /*! ビット位置を 8 ビットに揃えて 16 ビット書き出す
   * @param v 書き出す値
   */
  void write16(m_ui32 v) {
    flush();

#if SYSTEM_ENDIAN == SMALL_ENDIAN
    v = ((v & 0xff00) >> 8) | ((v & 0x00ff) << 8);
#else // SYSTEM_ENDIAN == BIG_ENDIAN
    v <<= 16;
#endif

    stream_->write(&v, 2);
  }



  /*! ビット位置を 8 ビットに揃えて 32 ビット書き出す
   * @param v 書き出す値
   */
  void write32(m_ui32 v) {
    flush();

#if SYSTEM_ENDIAN == SMALL_ENDIAN
    v = (v >> 16) | (v << 16);
    v = ((v & 0xff00ff00) >> 8) | ((v & 0x00ff00ff) << 8);
#endif

    stream_->write(&v, 4);
  }

};




#undef UNEXPECTED_EOF_EXCEPTION
#undef NULL_POINTER_EXCEPTION
#undef IO_FAILED_EXCEPTION


#endif // BSTREAM_H

