/**
 * @class BitBlockEncoder
 */
var BitBlockEncoder = new Class ({

/**
 * @var		buffer		int				buffer for half-filled current block
 * @private
 */
	buffer: 0,
	
/**
 * @var		count		int				internal pointer for stream seeking
 * @private
 */
	count: 0,
	
/**
 * @var		stream		string			stream itself
 * @private
 */
	stream: "",
	
/**
 * @var		count		int				actual amount of stored bits
 * @private
 */
	len: 0,
	
/**
 * @var		ABC		string			alphabet for encoding
 * @private
 */
	ABC: "",

/**
 * @var		ABCi		array			inverse alphabet index table
 * @private
 */
	ABCi: [],
	
/**
 * @var		blockSize	int				size of the block.
 * @example								6 for base64 and 8 for bytes
 * @private
 */
	blockSize: null,

/**
 * @var		blockSize	int				bit mask for block
 * @private
 */
	blockMask: null,
	
/**
 * @constructor
 * @throws				Exception
 * @param	abc			string			alpabet for encoding
 * @param	stream		mixed			some stream data
 */
	initialize: function(abc, stream){
		this.ABC = BitBlockEncoder.alphabets[abc];
		var l = this.ABC.length;

		var bin = l.toString(2);
		if (!bin.match(/^10*$/)) throw "Non 2^n sized alphabet";
		this.blockSize = bin.length - 1;
		
		this.blockMask = (1 << this.blockSize) - 1;
		for (var i = 0; i < l; i++) {
			this.ABCi[this.ABC.charCodeAt(i)] = i;
		}
		
		if (stream) this.adopt(stream);
	},

/**
 * helper function for stream init
 * //@throws Exception
 * @return			BitBlockEncoder		this
 * @protected
 */
	adopt: function(str) {
/*		if (m = str.match(/[^A-Za-z0-9\/+-]/)) { // allow minus sign
			var chr = m[0];
			var idx = str.indexOf(chr);
			throw new Exception("Unexpected character: "+chr+" on pos: "+idx+" in input stream.");
		}*/
		this.buffer = 0;
		this.count = 0;
		this.stream = str;
		this.len = str.length * this.blockSize;
		return this;
	},

/**
 * flushes and returns stream as string
 * @return				string	stream in current alphabet
 * @private
 */	
	getStream: function() {
		this.flush();
		return this.stream;
	},
	
/**
 * increases bit counter in stream and length
 * @param 	b			int		amount of new bits
 * @private
 */
	inc: function(b) {
		if (!b) b = 1;
		this.count += b;
		this.len += b;
	},
	
/**
 * resets a stream, i.e. rewinds internal pointer to the beginning
 * @protected
 */
	reset: function() {
		this.flush();
		this.buffer = 0;
		this.count = 0;
		return this;
	},

/**
 * clears a stream and resets pointer
 * @public
 */
	clear: function() {
		this.reset();
		this.len = 0;
		this.stream = "";
		return this;
	},
	
/**
 * dumps a bit representaion of stream. e.g.: "0010 1110 1 ... "
 * with blocks separated by space
 * @return			string				bin
 * @public
 */
	bitDump: function() {
		var idx = 0, a = [];
		for (idx = 0, l = Math.floor(this.len/this.blockSize); idx < l; idx++) {
			var str = this.ABCi[this.stream.charCodeAt(idx)].toString(2);
			while (str.length < this.blockSize) {
				str = "0" + str;
			}
			a.push(str);
		}
		var tail = this.len - idx*this.blockSize;
		if (tail > 0) {
			var str = this.buffer.toString(2);
			while (str.length < tail) {
				str = "0" + str;
			}
			a.push(str);
		}
		return a.join(" ");
	},
	
/**
 * dumps a hex representaion of stream. e.g.: "DEADBEEF... "
 * @todo				test this!
 * @return			string				hex
 * @public
 */
	hexDump: function() {
		var idx = 0, a = [];
		var saved = {
			count: this.count,
			buffer: this.buffer
		}
		this.flush();
		this.count = 0; // reset
		for (idx = 0, l = Math.floor(this.len/4); idx < l; idx++) {
			a.push(this.alphabets.BASE16.charAt(
				this.getNbit(4)
			));
		}
		var tail = this.len - idx*4;
		if (tail > 0) {
			a.push(this.alphabets.BASE16.charAt(
				this.getNbit(tail)
			));
		}
		if (this.len % this.blockSize) { // was flushed
			this.stream = this.stream.replace(/.$/, "");
			this.buffer = saved.buffer;
		}
		this.count = saved.count;
		return a.join("");
	},
	
/**
 * flushes buffer for last bit-block
 * @protected
 */
	flush: function() {
		if (this.count > this.stream.length * this.blockSize) {
			this.pushBlock();
		}
	},
	
/**
 * block-processing functions
 * @private
 */
	popBlock: function() {
		var idx = Math.floor(this.count / this.blockSize);
		this.buffer = this.ABCi[this.stream.charCodeAt(idx)];
	},
	pushBlock: function() {
		this.stream += this.ABC.charAt(this.buffer);
		this.buffer = 0;
	},

	
/**
 * adds a bit to stream
 * @param	b		short		bit value
 * @public
 */
	addBit: function(b) {
		var offset = this.count % this.blockSize;
		this.buffer += (b & 1) << (this.blockSize - 1 - offset);
		this.inc(1);
		if (offset == this.blockSize - 1) {
			this.pushBlock();
		}
		return this;
	},
	
/**
 * gets a bit from stream
 * @throws			RangeError
 * @return			short		bit value
 * @public
 */
	getBit: function() {
		if (this.count == this.len) {
			throw new RangeError("Index "+(this.len)+"(bits) is out-of-bound.");
		}
		if (this.count % this.blockSize == 0) {
			var idx = Math.floor(this.count / this.blockSize);
			this.popBlock();
		}
		var sbs = this.blockSize - 1;
		var b = (this.buffer & (1 << sbs)) >> sbs;
		this.count++;
		this.buffer <<= 1;
		this.buffer &= this.blockMask;
		return b;
	},
	
/**
 * adds an N-bit unsigned to stream
 * @param	bits		short		amount of bits
 * @param	n		uint		bit value
 * @public
 */
	addNbit: function(bits, n) {
		for (var b = bits - 1; b >= 0; b--) {
			this.addBit((n & (1 << b)) >> b);
		}
		return this;
	},
/**
 * gets an N-bit unsigned from stream
 * @param	bits		short		amount of bits
 * @return			uint		value
 * @public
 */
	getNbit: function(bits) {
		var n = 0;
		for (var b = 0; b < bits; b++) {
			n <<= 1;
			n += this.getBit();
		}
		return n;
	}
});

/**
 * @see RFC 3548
 */
BitBlockEncoder.alphabets = {
	BASE16: "0123456789ABCDEF",
	BASE32: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
	BASE32HEX: "0123456789ABCDEFGHIJKLMNOPQRSTUV",
	BASE64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
	BASE64URL: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
	BASE64XML_TOKENS: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-",
	BASE64XML_IDENT: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_:",
	BASE64REG_EXP: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!-"
}
