/*
*	Codiert beliebige Daten mit reservierter Anzahl an Bits in einen String
*/
function StringEnDecoder() {

	var new_frame_size = 6;	// Bits für ein Zeichen

	var enc_table = new Array();
	var dec_table = new Array();

	var encoded_string="";

	var enc_buffer_size=0;
	var enc_buffer = 0;

	var dec_buffer_pos=0;
	var dec_buffer=0;
	var dec_char_count=0;

	var shift_lock = false;
	var push_lock = false;
	var flush_lock = false;

	init();
	
	function init() { //6bit Zeichentabelle

		for (var i=65; i<=90; i++) { // A-Z
			dec_table[String.fromCharCode(i)] = enc_table.length;
			enc_table[enc_table.length] = String.fromCharCode(i);
		}

		for (var i=97; i<=122; i++) { // a-z
			dec_table[String.fromCharCode(i)] = enc_table.length;
			enc_table[enc_table.length] = String.fromCharCode(i);
		}

		for (var i=48; i<=57; i++) { // 0-9
			dec_table[String.fromCharCode(i)] = enc_table.length;
			enc_table[enc_table.length] = String.fromCharCode(i);
		} 

		dec_table[String.fromCharCode(43)] = enc_table.length; // +
		enc_table[enc_table.length] = String.fromCharCode(43);

		dec_table[String.fromCharCode(47)] = enc_table.length; // /
		enc_table[enc_table.length] = String.fromCharCode(47);
	}

	/*
	*	Setzt den String der decodiert werden soll
	*	@params
	*		str - Encodierter String
	*/
	this.decode = function(str) {
		this.reset();

		push_lock = true;
		flush_lock = true;

		encoded_string = str;
	}

	/*
	*	Reset
	*/
	this.reset = function() {
		this.encoded_string = "";

		enc_buffer = 0;
		enc_buffer_size = 0;
		
		dec_buffer = 0;
		dec_buffer_pos = 0;
		dec_char_count = 0;

		push_lock = false;
		flush_lock = false;
		shift_lock = false;
	}


	/*
	*	Encodiert die übergebenen Daten in einen String
	*	@params
	*		data - Daten bis zu einer maximalen Groesse von 2^size - 1, size <=32
	*		size - Anzahl der Bits für die Daten
	*/
	this.push = function(data, size) {

		if (!push_lock) {
			shift_lock = true;
			
			var bit_count = 32-size;
			data = data << bit_count; //Nullen vorne raus shiften

			while (bit_count != 32) { //die restlichen bits in den buffer shiften
				if (enc_buffer_size == new_frame_size) { //flush buffer
					encoded_string += enc_table[enc_buffer];
					enc_buffer_size = 0;
					enc_buffer = 0;
				} 

				enc_buffer = enc_buffer << 1;			
				enc_buffer = enc_buffer | msb(data);
				data = data << 1;
				enc_buffer_size++;
				bit_count++;
			}
		}
	}

	/*
	*	Liefert den encodierten String
	*	@returns
	*		String
	*/
	this.flush = function() {
		if (!flush_lock) {
			if (enc_buffer != 0) { //flush buffer
				enc_buffer = enc_buffer << (new_frame_size-enc_buffer_size);
				encoded_string += enc_table[enc_buffer];
			}
			shift_lock = false;
			push_lock = true;
			enc_buffer = 0;
			enc_buffer_size = 0;

			return encoded_string;
		} else {
			return null;
		}
	}


	/*
	*	Decodiert aus dem mit decode übergebenen String size Bits.
	*	Achtung: die Reihenfolge und die jeweilige Groesse der shift Operationen
	*	muss mit denen der push Operationen übereinstimmen
	*
	*	@params
	*		size - Anzahl der Bits die aus dem String decodiert werden sollen, size <=32
	*	@returns
	*		data
	*/
	this.shift = function(size) {
		if (!shift_lock) {
			push_lock = true;
			flush_lock = true;

			if (dec_char_count == 0) { //buffer preload
				dec_buffer = encoded_string.charAt(0);
				dec_buffer = dec_table[dec_buffer];
				dec_buffer = dec_buffer << (32-new_frame_size);
				dec_char_count++;
			}

			var data=0;
			var bit_count=0;

			while (size > bit_count) { 
				if (dec_buffer_pos == new_frame_size) { //buffer reload
					dec_buffer = encoded_string.charAt(dec_char_count);
					dec_buffer = dec_table[dec_buffer];
					dec_buffer = dec_buffer << (32-new_frame_size);
					dec_buffer_pos = 0;
					dec_char_count++;
				}
				
				data = data << 1;
				data = data | msb(dec_buffer);
				dec_buffer = dec_buffer << 1;

				dec_buffer_pos++;
				bit_count++;
			}
			return data;
		} else {
			return null;
		}
	}

	//Hilfsfunktionen

	this.toBinary = function(data) {
		var text="";

		while(data > 0) {
			text = data%2 + text;
			data = Math.floor(data / 2);
		}
		
		return "binary:" + text;
	}

	//least significant bit
	function lsb(data) {
		return data&1;
	}

	//most significant bit
	function msb(data) {
		data = data & 2147483648;
		data = data >> 31;
		return data * (-1);
	}
}
