#ifndef H_BITIO_ABSTRACT
#define H_BITIO_ABSTRACT
#include "mglong.h"
#include <cstdlib> 
class bitio_buffer {
public:
  virtual int bit() = 0;
  virtual void encodeBit(int bit) = 0;
  virtual void seek(mg_u_long topos) = 0;

  virtual ~bitio_buffer() {}

  static inline void check_positive(char *type, mg_u_long x) {
    if (x <= 0) {
      fprintf(stderr, "Error: Cannot %s encode %u\n", type, x);
      exit(1);
    }
  }

  static inline int floor_log2(mg_u_long x) {
    mg_u_long  _B_x = x;
    int reply;
    
    reply = -1;
    while (_B_x != 0) {
      _B_x >>= 1;
      ++reply;
    }
    return reply;
  }
  
  static inline int ceil_log2(mg_u_long x) {
    register int _B_x;
    mg_u_long reply;
    
    _B_x = x - 1;
    reply = 0;
    while (_B_x != 0) {
      _B_x >>= 1;
      ++reply;
    }
    return reply;
  }

  inline mg_u_long unary_decode(mg_u_long *count) {
    mg_u_long x;
    
    x = 1;
    while (!this->bit()) ++x;
    if (count != NULL) {
      *count += x;
    }
    return x;
  }

  inline void unary_encode(mg_u_long x, mg_u_long *count) {
    register mg_u_long _B_x = x;
    check_positive((char*)"unary", _B_x);
    if (count != NULL) {
      *count += _B_x;
    }
    while(--_B_x) 
      encodeBit(0);
    encodeBit(1);
  }


  inline mg_u_long binary_decode(mg_u_long b, mg_u_long *count) {
    register mg_u_long _B_x = 0;
    register mg_u_long _B_b = b;
    register int _B_i, _B_logofb, _B_thresh;
    register mg_u_long reply;
    
    if (_B_b != 1) {					
      _B_logofb = ceil_log2(_B_b);
      _B_thresh = (1<<_B_logofb) - _B_b;
      --_B_logofb;
      
      if (count != NULL) {
	*count += _B_logofb;
      }
      
      for (_B_i=0; _B_i < _B_logofb; ++_B_i) {
	_B_x += _B_x + this->bit();
      }

      if ((int)_B_x >= _B_thresh) {
	_B_x += _B_x + this->bit();
	_B_x -= _B_thresh;
	if (count != NULL) {
	  *count = *count + 1;
	}
      }
      reply = _B_x+1;
      
    } else reply = 1;
    
    return reply;
  }

  inline void binary_encode(mg_u_long x, mg_u_long b, mg_u_long *count) {
    register mg_u_long _B_x = (x);
    register mg_u_long _B_b = (b);
    register int _B_nbits, _B_logofb, _B_thresh;
    
    check_positive((char*)"binary", _B_x);
    _B_logofb = ceil_log2(_B_b);
    
    _B_thresh = (1<<_B_logofb) - _B_b;
    if ((int)(--_B_x) < _B_thresh) _B_nbits = _B_logofb-1;
    else {
      _B_nbits = _B_logofb;
      _B_x += _B_thresh;
    }
    if (count != NULL) {
      *count += _B_nbits;
    }
    while (--_B_nbits>=0)
      encodeBit((_B_x>>_B_nbits) & 0x1);
  }

  inline mg_u_long bblock_decode(mg_u_long b, mg_u_long *count) {
    register mg_u_long _B_x1, _B_xx = 0;
    register mg_u_long _B_bb = (b);
    register int _B_xdivb;
    
    _B_xdivb = this->unary_decode(count);
    --_B_xdivb;				       
    while (_B_xdivb--) {
      _B_xx += _B_bb;
    }
    _B_x1 = this->binary_decode(_B_bb, count);
    return (_B_xx + _B_x1);
  }
  
  inline void bblock_encode(mg_u_long x, mg_u_long b, mg_u_long *count) {
    register mg_u_long _B_xx = (x);
    register mg_u_long _B_bb = (b);
    register int _B_xdivb = 0;
    check_positive((char*)"bblock", _B_xx);
    --_B_xx;
    while (_B_xx >= _B_bb) {
      ++_B_xdivb;
      _B_xx -= _B_bb;
    }
    unary_encode(_B_xdivb+1, count);
    binary_encode(_B_xx+1, _B_bb, count);
  }

  inline mg_u_long elias_decode(mg_u_long b, double s, mg_u_long *count) {
    register mg_u_long _B_xx;
    register mg_u_long _B_b = b;
    register double _B_s = s;
    register int _B_lower=1, _B_upper=1;
    register int _B_k;
    register double _B_pow=1.0;
    
    _B_k = this->unary_decode(count);
    --_B_k;
    while (_B_k--) {
      _B_lower = _B_upper+1;
      _B_pow *= _B_s;
      _B_upper += (int) (_B_b*_B_pow);
    }
    _B_xx = this->binary_decode(_B_upper-_B_lower+1, count);
    --_B_xx;
    check_positive((char*)"gamma", _B_xx+_B_lower);
    return _B_xx+_B_lower;
  }
  
  inline void elias_encode(mg_u_long x, mg_u_long b, double s, mg_u_long *count) {
    register mg_u_long _B_xx = x;
    register mg_u_long _B_b = b;
    register double _B_s = s;
    register int _B_lower=1, _B_upper=1;
    register int _B_k=0;
    register double _B_pow=1.0;
    
    check_positive((char*)"elias", _B_xx);
    
    while ((int)_B_xx>_B_upper) {
      ++_B_k;
      _B_lower = _B_upper+1;
      _B_pow *= _B_s;
      _B_upper += (int) (_B_b*_B_pow);
    }
    unary_encode(_B_k+1, count);
    binary_encode(_B_xx-_B_lower+1, _B_upper-_B_lower+1, count);
  }
  
  inline mg_u_long gamma_decode(mg_u_long *count) {
    register mg_u_long _B_xx = 1;
    register int _B_nbits = 0;
    
    while(!this->bit()) ++_B_nbits;
    if (count != NULL) {
      *count += _B_nbits*2+1;
    }
    while (_B_nbits-- > 0)
      _B_xx += _B_xx + this->bit();
    return _B_xx;
  }

  inline void gamma_encode(mg_u_long x, mg_u_long *count) {
    register mg_u_long _B_xx = x;
    register int _B_logofb;
    register int _B_nbits;
    
    check_positive((char*)"gamma", _B_xx);
    _B_logofb = floor_log2(_B_xx);
    _B_nbits = _B_logofb+1;
    
    if (count != NULL) { 
      *count += _B_logofb*2+1;
    }
    while(_B_logofb--) encodeBit(0);
    while (--_B_nbits>=0) encodeBit((_B_xx>>_B_nbits) & 0x1);
  }
  
  inline mg_u_long delta_decode(mg_u_long *count) {
    register mg_u_long _B_xxx;
    register int _B_logx;
    _B_logx = gamma_decode(count);
    --_B_logx;
    _B_xxx = binary_decode(1<<_B_logx, count); 
    --_B_xxx;
    return (_B_xxx + (1<<_B_logx));
  }

  inline void delta_encode(mg_u_long x, mg_u_long *count) {
    register mg_u_long _B_xxx = x;
    register int _B_logx;
    _B_logx = floor_log2(_B_xxx);
    gamma_encode(_B_logx+1, count);
    binary_encode(_B_xxx+1, 1<<_B_logx, count);
  }

  inline mg_u_long huff_decode(mg_u_long *mcodes, mg_u_long **values,
				   mg_u_long *count = NULL) {
    register mg_u_long *__min_code = mcodes;
    register mg_u_long *__mclen    = mcodes;
    register mg_u_long **__values  = values;
    register mg_u_long __code = 0;
    do {
      __code += __code + bit();
      if (count != NULL) ++(*count);
    } while (__code < *++__mclen);
    
    return __values[__mclen - __min_code][__code - *__mclen];
  }

  inline void huff_encode(mg_u_long x, mg_u_long *codes, char *lens,
			  mg_u_long *count = NULL) {
    register int __i;
    register int __clen = lens[x];
    register mg_u_long __code = codes[x];
    for (__i=__clen-1; __i>=0; --__i) {
      encodeBit((__code >> __i) & 1);
    }
    if (count != NULL) {
      *count += lens[x];
    }
  }

  static inline mg_u_long unary_length(mg_u_long x) {
    check_positive((char*)"unary", x);
    return x;
  }

  static inline int binary_length(mg_u_long x, mg_u_long b) {
    register mg_u_long _B_x = (x);
    register mg_u_long _B_b = (b);
    register int _B_nbits, _B_logofb, _B_thresh;
    
    check_positive((char*)"binary", _B_x);
    _B_logofb = ceil_log2(_B_b);
    _B_thresh = (1<<_B_logofb) - _B_b;
    if ((int)(--_B_x) < _B_thresh) _B_nbits = _B_logofb-1;
    else _B_nbits = _B_logofb;
    return _B_nbits;
  }

  static inline mg_u_long bblock_length(mg_u_long x, mg_u_long b) {
    register mg_u_long _B_bcount, count, _B_xx = (x);
    register mg_u_long _B_bb = (b);
    register int _B_xdivb = 0;
    
    check_positive((char*)"bblock", _B_xx);
    --_B_xx;
    while (_B_xx >= _B_bb) {
      ++_B_xdivb;
      _B_xx -= _B_bb;
    }
    count = unary_length(_B_xdivb+1);
    _B_bcount = binary_length(_B_xx+1, _B_bb);
    count += _B_bcount;
    return count;
  }
  
  static inline mg_u_long elias_length(mg_u_long x, mg_u_long b, 
					   double s) {
    register mg_u_long _B_xx = x;
    register mg_u_long _B_b = b;
    register double _B_s = s;
    register int _B_lower=1, _B_upper=1;
    register int _B_k=0, _B_ecount;
    register double _B_pow=1.0;
    register mg_u_long count;
    check_positive((char*)"gamma", _B_xx);
    
    while ((int)(_B_xx)>_B_upper) {
      ++_B_k;
      _B_lower = _B_upper+1;
      _B_pow *= _B_s;
      _B_upper += (int) (_B_b*_B_pow);
    }
    count = unary_length(_B_k+1);
    _B_ecount = binary_length(_B_xx-_B_lower+1, _B_upper-_B_lower+1);
    count += _B_ecount;
    return count;
  }
  
  static inline mg_u_long gamma_length(mg_u_long x) {
    register mg_u_long _B_xx = x;
    register int _B_logofb;
    check_positive((char*)"gamma", _B_xx);
    _B_logofb = floor_log2(_B_xx);
    return _B_logofb*2+1;
  }

  static inline mg_u_long delta_length(mg_u_long x) {
    register mg_u_long _B_xxx = x;
    register int _B_logx, _B_dcount;
    register mg_u_long count;
    _B_logx = floor_log2 (_B_xxx);
    count = gamma_length(_B_logx+1);
    _B_dcount = binary_length (_B_xxx+1, 1<<_B_logx);
    count += _B_dcount;
    return count;
  }
};

#endif
