var BG = BG || {};
/** @debug BEGIN */
if ( ! Object.prototype.hasOwnProperty.call( window, 'JSON' ) )
    try{console.error("BG.push.js requires JSON");} catch(e) {}
/** @debug END */

BG.push = (function(){

    var

    /** @debug */debug = 0,
    wsHost = "ws.boerse-go.de",
    //wsHost = "10.20.50.115",
    wsPorts = [ 80,443,843 ],
    wsPortIndex = 0,
    wsMessagesSinceLastConnect = 0,
    wsConnectRetries = 0,
    jsonpHost = location.protocol + "//data.boerse-go.de/ws/",


    state=0, //  connection status: 0=offline,trying to reconnect, null=offline , 1=connected


    stateCallbacks = [], // which callbacks to call when state changes



    subscriptionCounter = 0,
    subscriptionListeners = [
	],


	Base64 = {

	// private property
	_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

	// public method for encoding
	encode : function (input) {
		var output = "";
		var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
		var i = 0;

		input = Base64._utf8_encode(input);

		while (i < input.length) {

			chr1 = input.charCodeAt(i++);
			chr2 = input.charCodeAt(i++);
			chr3 = input.charCodeAt(i++);

			enc1 = chr1 >> 2;
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
			enc4 = chr3 & 63;

			if (isNaN(chr2)) {
				enc3 = enc4 = 64;
			} else if (isNaN(chr3)) {
				enc4 = 64;
			}

			output = output +
			this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
			this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

		}

		return output;
	},

	// public method for decoding
	decode : function (input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;

		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

		while (i < input.length) {

			enc1 = this._keyStr.indexOf(input.charAt(i++));
			enc2 = this._keyStr.indexOf(input.charAt(i++));
			enc3 = this._keyStr.indexOf(input.charAt(i++));
			enc4 = this._keyStr.indexOf(input.charAt(i++));

			chr1 = (enc1 << 2) | (enc2 >> 4);
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
			chr3 = ((enc3 & 3) << 6) | enc4;

			output = output + String.fromCharCode(chr1);

			if (enc3 != 64) {
				output = output + String.fromCharCode(chr2);
			}
			if (enc4 != 64) {
				output = output + String.fromCharCode(chr3);
			}

		}

		output = Base64._utf8_decode(output);

		return output;

	},

	// private method for UTF-8 encoding
	_utf8_encode : function (string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";

		for (var n = 0; n < string.length; n++) {

			var c = string.charCodeAt(n);

			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}

		}

		return utftext;
	},

	// private method for UTF-8 decoding
	_utf8_decode : function (utftext) {
		var string = "";
		var i = 0;
		var c=0,c1=0,c2=0,c3;

		while ( i < utftext.length ) {

			c = utftext.charCodeAt(i);

			if (c < 128) {
				string += String.fromCharCode(c);
				i++;
			}
			else if((c > 191) && (c < 224)) {
				c2 = utftext.charCodeAt(i+1);
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
				i += 2;
			}
			else {
				c2 = utftext.charCodeAt(i+1);
				c3 = utftext.charCodeAt(i+2);
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}

		}

		return string;
	}

},
	MD5 = (function(){
	function array(n) {
      var i;
	  for(i=0;i<n;i++) this[i]=0;
	  this.length=n;
	}



	/* Einige grundlegenden Funktionen müssen wegen
	 * Javascript Fehlern umgeschrieben werden.
	 * Man versuche z.B. 0xffffffff >> 4 zu berechnen..
	 * Die nun verwendeten Funktionen sind zwar langsamer als die Originale,
	 * aber sie funktionieren.
	 */

	function integer(n) { return n%(0xffffffff+1); }

	function shr(a,b) {
	  a=integer(a);
	  b=integer(b);
	  if (a-0x80000000>=0) {
	    a=a%0x80000000;
	    a>>=b;
	    a+=0x40000000>>(b-1);
	  } else
	    a>>=b;
	  return a;
	}

	function shl1(a) {
	  a=a%0x80000000;
	  if (a&0x40000000==0x40000000)
	  {
	    a-=0x40000000;
	    a*=2;
	    a+=0x80000000;
	  } else
	    a*=2;
	  return a;
	}

	function shl(a,b) {
	  a=integer(a);
	  b=integer(b);
	  for (var i=0;i<b;i++) a=shl1(a);
	  return a;
	}

	function and(a,b) {
	  a=integer(a);
	  b=integer(b);
	  var t1=(a-0x80000000);
	  var t2=(b-0x80000000);
	  if (t1>=0)
	    if (t2>=0)
	      return ((t1&t2)+0x80000000);
	    else
	      return (t1&b);
	  else
	    if (t2>=0)
	      return (a&t2);
	    else
	      return (a&b);
	}

	function or(a,b) {
	  a=integer(a);
	  b=integer(b);
	  var t1=(a-0x80000000);
	  var t2=(b-0x80000000);
	  if (t1>=0)
	    if (t2>=0)
	      return ((t1|t2)+0x80000000);
	    else
	      return ((t1|b)+0x80000000);
	  else
	    if (t2>=0)
	      return ((a|t2)+0x80000000);
	    else
	      return (a|b);
	}

	function xor(a,b) {
	  a=integer(a);
	  b=integer(b);
	  var t1=(a-0x80000000);
	  var t2=(b-0x80000000);
	  if (t1>=0)
	    if (t2>=0)
	      return (t1^t2);
	    else
	      return ((t1^b)+0x80000000);
	  else
	    if (t2>=0)
	      return ((a^t2)+0x80000000);
	    else
	      return (a^b);
	}

	function not(a) {
	  a=integer(a);
	  return (0xffffffff-a);
	}

	/* Beginn des Algorithmus */

	    var state = new array(4);
	    var count = new array(2);
	        count[0] = 0;
	        count[1] = 0;
	    var buffer = new array(64);
	    var transformBuffer = new array(16);
	    var digestBits = new array(16);

	    var S11 = 7;
	    var S12 = 12;
	    var S13 = 17;
	    var S14 = 22;
	    var S21 = 5;
	    var S22 = 9;
	    var S23 = 14;
	    var S24 = 20;
	    var S31 = 4;
	    var S32 = 11;
	    var S33 = 16;
	    var S34 = 23;
	    var S41 = 6;
	    var S42 = 10;
	    var S43 = 15;
	    var S44 = 21;

	    function F(x,y,z) {
	        return or(and(x,y),and(not(x),z));
	    }

	    function G(x,y,z) {
	        return or(and(x,z),and(y,not(z)));
	    }

	    function H(x,y,z) {
	        return xor(xor(x,y),z);
	    }

	    function I(x,y,z) {
	        return xor(y ,or(x , not(z)));
	    }

	    function rotateLeft(a,n) {
	        return or(shl(a, n),(shr(a,(32 - n))));
	    }

	    function FF(a,b,c,d,x,s,ac) {
	        a = a+F(b, c, d) + x + ac;
	        a = rotateLeft(a, s);
	        a = a+b;
	        return a;
	    }

	    function GG(a,b,c,d,x,s,ac) {
	        a = a+G(b, c, d) +x + ac;
	        a = rotateLeft(a, s);
	        a = a+b;
	        return a;
	    }

	    function HH(a,b,c,d,x,s,ac) {
	        a = a+H(b, c, d) + x + ac;
	        a = rotateLeft(a, s);
	        a = a+b;
	        return a;
	    }

	    function II(a,b,c,d,x,s,ac) {
	        a = a+I(b, c, d) + x + ac;
	        a = rotateLeft(a, s);
	        a = a+b;
	        return a;
	    }

	    function transform(buf,offset) {
	        var a=0, b=0, c=0, d=0,i,j;
	        var x = transformBuffer;

	        a = state[0];
	        b = state[1];
	        c = state[2];
	        d = state[3];

	        for (i = 0; i < 16; i++) {
	            x[i] = and(buf[i*4+offset],0xff);
	            for (j = 1; j < 4; j++) {
	                x[i]+=shl(and(buf[i*4+j+offset] ,0xff), j * 8);
	            }
	        }

	        /* Runde 1 */
	        a = FF ( a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
	        d = FF ( d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
	        c = FF ( c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
	        b = FF ( b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
	        a = FF ( a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
	        d = FF ( d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
	        c = FF ( c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
	        b = FF ( b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
	        a = FF ( a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
	        d = FF ( d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
	        c = FF ( c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
	        b = FF ( b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
	        a = FF ( a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
	        d = FF ( d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
	        c = FF ( c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
	        b = FF ( b, c, d, a, x[15], S14, 0x49b40821); /* 16 */

	        /* Runde 2 */
	        a = GG ( a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
	        d = GG ( d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
	        c = GG ( c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
	        b = GG ( b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
	        a = GG ( a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
	        d = GG ( d, a, b, c, x[10], S22,  0x2441453); /* 22 */
	        c = GG ( c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
	        b = GG ( b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
	        a = GG ( a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
	        d = GG ( d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
	        c = GG ( c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
	        b = GG ( b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
	        a = GG ( a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
	        d = GG ( d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
	        c = GG ( c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
	        b = GG ( b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */

	        /* Runde 3 */
	        a = HH ( a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
	        d = HH ( d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
	        c = HH ( c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
	        b = HH ( b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
	        a = HH ( a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
	        d = HH ( d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
	        c = HH ( c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
	        b = HH ( b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
	        a = HH ( a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
	        d = HH ( d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
	        c = HH ( c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
	        b = HH ( b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */
	        a = HH ( a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
	        d = HH ( d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
	        c = HH ( c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
	        b = HH ( b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */

	        /* Runde 4 */
	        a = II ( a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
	        d = II ( d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
	        c = II ( c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
	        b = II ( b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
	        a = II ( a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
	        d = II ( d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
	        c = II ( c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
	        b = II ( b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
	        a = II ( a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
	        d = II ( d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
	        c = II ( c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
	        b = II ( b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
	        a = II ( a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
	        d = II ( d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
	        c = II ( c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
	        b = II ( b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */

	        state[0] +=a;
	        state[1] +=b;
	        state[2] +=c;
	        state[3] +=d;

	    }
	    /* Mit der Initialisierung von Dobbertin:
	       state[0] = 0x12ac2375;
	       state[1] = 0x3b341042;
	       state[2] = 0x5f62b97c;
	       state[3] = 0x4ba763ed;
	       gibt es eine Kollision:

	       begin 644 Message1
	       M7MH=JO6_>MG!X?!51$)W,CXV!A"=(!AR71,<X`Y-IIT9^Z&8L$2N'Y*Y:R.;
	       39GIK9>TF$W()/MEHR%C4:G1R:Q"=
	       `
	       end

	       begin 644 Message2
	       M7MH=JO6_>MG!X?!51$)W,CXV!A"=(!AR71,<X`Y-IIT9^Z&8L$2N'Y*Y:R.;
	       39GIK9>TF$W()/MEHREC4:G1R:Q"=
	       `
	       end
	    */
	    function init() {
            var i;
	        count[0]=count[1] = 0;
	        state[0] = 0x67452301;
	        state[1] = 0xefcdab89;
	        state[2] = 0x98badcfe;
	        state[3] = 0x10325476;
	        for (i = 0; i < digestBits.length; i++)
	            digestBits[i] = 0;
	    }

	    function update(b) {
	        var index,i;

	        index = and(shr(count[0],3) , 0x3f);
	        if (count[0]<0xffffffff-7)
	          count[0] += 8;
	        else {
	          count[1]++;
	          count[0]-=0xffffffff+1;
	          count[0]+=8;
	        }
	        buffer[index] = and(b,0xff);
	        if (index  >= 63) {
	            transform(buffer, 0);
	        }
	    }

	    function finish() {
	        var bits = new array(8);
	        var        padding;
	        var        i=0, index=0, padLen=0,j;

	        for (i = 0; i < 4; i++) {
	            bits[i] = and(shr(count[0],(i * 8)), 0xff);
	        }
	        for (i = 0; i < 4; i++) {
	            bits[i+4]=and(shr(count[1],(i * 8)), 0xff);
	        }
	        index = and(shr(count[0], 3) ,0x3f);
	        padLen = (index < 56) ? (56 - index) : (120 - index);
	        padding = new array(64);
	        padding[0] = 0x80;
	        for (i=0;i<padLen;i++)
	          update(padding[i]);
	        for (i=0;i<8;i++)
	          update(bits[i]);

	        for (i = 0; i < 4; i++) {
	            for (j = 0; j < 4; j++) {
	                digestBits[i*4+j] = and(shr(state[i], (j * 8)) , 0xff);
	            }
	        }
	    }

	/* Ende des MD5 Algorithmus */

	function hexa(n) {
	 var hexa_h = "0123456789abcdef";
	 var hexa_c="";
	 var hexa_m=n, hexa_i;
	 for (hexa_i=0;hexa_i<8;hexa_i++) {
	   hexa_c=hexa_h.charAt(Math.abs(hexa_m)%16)+hexa_c;
	   hexa_m=Math.floor(hexa_m/16);
	 }
	 return hexa_c;
	}


	var ascii="01234567890123456789012345678901" +
	          " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
	          "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";

	function MD5(nachricht)
	{
	 var l,s,k,ka,kb,kc,kd,i;

	 init();
	 for (k=0;k<nachricht.length;k++) {
	   l=nachricht.charAt(k);
	   update(ascii.lastIndexOf(l));
	 }
	 finish();
	 ka=kb=kc=kd=0;
	 for (i=0;i<4;i++) ka+=shl(digestBits[15-i], (i*8));
	 for (i=4;i<8;i++) kb+=shl(digestBits[15-i], ((i-4)*8));
	 for (i=8;i<12;i++) kc+=shl(digestBits[15-i], ((i-8)*8));
	 for (i=12;i<16;i++) kd+=shl(digestBits[15-i], ((i-12)*8));
	 s=hexa(kd)+hexa(kc)+hexa(kb)+hexa(ka);
	 return s;
	}

	return MD5;

	})(),


	time = {
		setServerTimestamp: function(ts) {
			time.diff = parseInt( ts - (+new Date())/1000 );
		},
		getServerTimestamp: function() {
			return time.diff===null?null: parseInt(  +new Date()/1000 + time.diff );
		},
		diff: null,  // time difference between server and client in seconds  (servertime - clienttime)

		_checker:
			setTimeout( function() {
				// no current date received yet, dirty hack to get one
				if ( time.diff === null  ) {
					/** @debug */debug && console.log("[BG.push] fetching current server timestamp via ajax");
					$.ajax( {
						url:'/robots.txt',
						success: function( d, txt, xhr ) {
							time.diff = Math.floor(    Date.parse(xhr.getResponseHeader('Date'))/1000 - (+new Date)/1000 );
						}
					});
				}
			}, 4000 )
	},

	ws = {
		setState: function(st) {
			state = st;
			for ( var i=0; i < stateCallbacks.length; i++ )
				stateCallbacks[i](state);
		},

		socket: null,

		poll: {
			interval: null,
			latestRun: null,
			run: function() {
				ws.poll.latestRun = (new Date()).getTime();
				for ( var i = 0; i < subscriptionListeners.length; i++ ) {
					if ( subscriptionListeners[i] === undefined ) continue;

					jsonp.execute( i );

				}
			},
			enable: function() {
				if ( ws.poll.interval !== null ) return;
				ws.poll.interval = setInterval( ws.poll.run, 15000 );
			},
			disable: function() {
				if ( ws.poll.interval === null ) return;
				clearInterval( ws.poll.interval );
			}
		},

	    disconnect: function() {
	        ws.setState(null);
	        try {
	            ws.socket.close();
	        } catch(e) {

	        }

	    },

	    reconnect: function() {

	    	/** @debug */debug && console.log("[bgpush] timeout occured, reconnecting");

	    	ws.setState(null);
	        try {
	            ws.socket.close();
	        } catch(e) {
	        }

	        ws.setState(0);
	        ws.connect();

		},

	    connect: function() {
	        if ( state !== 0 ) return;



	        try{
	        	var ssl = wsPorts[ wsPortIndex % wsPorts.length ] === 443;
	        	//var host = "ws"+(ssl?'s':'')+"://"+ wsHost + ':' + wsPorts[ wsPortIndex % wsPorts.length ];
	        	var host = "ws://"+ wsHost + ':' + wsPorts[ wsPortIndex % wsPorts.length ];
	        	wsMessagesSinceLastConnect = 0;

	        	/** @debug */debug && console.log("[bgpush] connecting to " + host );

	            ws.socket = new WebSocket(host);
	            ws.poll.enable();

	            ws.socket.onopen    = function(msg){
	                ws.setState(1);
	                wsConnectRetries =0;
	                /** @debug */debug && console.log("[bgpush] ws connection opened: "+this.readyState + ', resubscribing..');

	                ws.poll.disable();

                    try {
                        for ( var i in subscriptionListeners ) {
                            if ( i === undefined )  continue;
                            ws.socket.send( JSON.stringify( subscriptionListeners[i].subscription ));
                        }
					} catch (e) {
                        /** @debug */debug && console.log("[bgpush] error sending string!");
                        // this hopefully fixes random errors on reconnect
                        ws.socket.close();
                    }


	            };

	            ws.socket.onmessage = ws.onMessage;

	            // when connection brakes try to reconnect
	            ws.socket.onclose   = function(msg){
	                if ( state !== null ) {
	                    ws.setState(0);
	                    /** @debug */debug && console.log("[bgpush] ws connection broke: "+this.readyState);

	                    if ( wsMessagesSinceLastConnect ) {
	                    	setTimeout( ws.connect, 2000 );
						} else {
							if ( ++wsPortIndex < wsPorts.length ) {
								ws.connect();
							} else {
								wsPortIndex = 0;

                        		setTimeout( ws.connect, 20000 * Math.min(3,++wsConnectRetries) );
							}
						}
	                }

	                ws.poll.enable();
	                wsMessagesSinceLastConnect = 0;
	            };
	        }
	        catch(ex){
	            if  ( 'console' in window  && typeof console.log =='function' ) {
	                console.log("BoerseGo/Push Websocket Verbindung konnte nicht hergestellt werden:");
	                console.log(ex);
	            }
	            ws.poll.enable();
	        }
	    },

	    messageTimeout: null,
	    onMessage: function(msg){
	    	++wsMessagesSinceLastConnect;

	    	if ( ws.messageTimeout !== null )
	    		clearTimeout( ws.messageTimeout );
	    	ws.messageTimeout = setTimeout( ws.reconnect , 60000 );

	        var data;

	        if (msg.data == '') {
	        	// ping
	        	return;
			}

	        /** @debug */debug >1 && console.log("[bgpush] new message: " + msg.data);

	        try {
	            //data = $.parseJSON(msg.data);
	            //eval ( "data = " + msg.data + ";");
                data = JSON.parse(msg.data);
	        } catch(e) {
	            /** @debug */debug && console.log("[bgpush] invalid json format: '"+ msg.data + "'");
	            return;
	        }

			pushEventCallback( data );

	    }
	},

	pushEventCallback = function( data ) {
		if ( ! (data.subscription instanceof Array)) data.subscription = [ data.subscription ];

		if ( data.event === 'time' ) {
			time.setServerTimestamp( data.data.timestamp );
			return;
		}

		for ( var i = 0; i < data.subscription.length; i++ ) {

			if ( !subscriptionListeners[ data.subscription[i] ]  ) {
        		/** @debug */debug && console.log("[bgpush] no event listener for "+ data.subscription[i] );
			} else {
				subscriptionListeners[ data.subscription[i] ].callback(data);
			}

		}
	},


    /*

    	following is equivalent, all subscribes to _all_ livestream events and requests the last 1 value
    	subscribe( 'livestream', 1, function.. );
    	subscribe( {event:'livestream'}, 1, function.. )
    	subscribe( {data:{event:'livestream'},history:1}, function.. );


    	requesting only new values (equivalent):
    	subscribe( 'eval', function..  );
    	subscribe( {event:'eval'}, function.. );
    	subscribe( {data:{event:'eval'}}, function.. );



    	var subscription = BG.push.subscribe( {
    		data: {
    			event:"article",
    			data: {
    				ts: {gt:133000}
    			}
    		},
    		subscriptionId:101, // this is only a suggestion for the bgpush
    		fields: [ "id" ],
    		history: 2
    	}, function( d ) {
    		console.log(d);
    	});
    	setTimeout( function() {
    		BG.push.unsubscribe( subscription );
    	},6000 );

    */
    subscribe= function( obj, history, cb, options )  {
    	if ( typeof history === 'function')  {
    		options = cb;
    		cb = history;
    		history = undefined;
		}
        if ( !typeof cb === 'callback' ) return false;
        options = options || {};

        if ( typeof obj === 'string' ) obj = { data: {event: obj} };
        else if ( typeof obj !== 'object' ) return false;

        if ( !obj.data || !obj.data.event  ||  (obj.event !== undefined && obj.event!=='subscribe')   ) {
        	var x = {};
        	x.data = obj;
        	obj = x;
		}

		if ( history !== undefined ) obj.history = history;


        obj.event = 'subscribe';
        if ( options.subscriptionId && subscriptionListeners[ options.subscriptionId ] === undefined  ) {
        	obj.subscription =  options.subscriptionId;
		} else {
			while ( subscriptionListeners[subscriptionCounter++] !== undefined ) ;
        	obj.subscription = subscriptionCounter-1;
		}
        //obj.data.data = obj.data.data || {};

        subscriptionListeners[ obj.subscription ] = { callback:cb, subscription:obj , options:options };

        var objString = JSON.stringify(obj);
        /** @debug */debug && console.log( '[bgpush] subscribing to ' + objString );


        jsonp.execute( obj.subscription, true );

        if ( state ===1 ) {
        	try {
        		ws.socket.send( objString );
			} catch(e) {
				/** @debug */debug && console.log("[bgpush] error sending string!");
                ws.socket.close();
			}
		}



		return obj.subscription;

    },

    unsubscribe=  function( subscriptionId ) {
    	delete subscriptionListeners[ subscriptionId ];

    	/** @debug */debug && console.log("[bgpush] unsubscribing " + subscriptionId );

    	var cmd = {
    		event:'unsubscribe',
    		subscription: subscriptionId
		};

		if ( state === 1 ) {
            try {
                ws.socket.send( JSON.stringify(cmd));
            } catch (e) {
                /** @debug */debug && console.log("[bgpush] error sending string!");
                ws.socket.close();
            }

        }

	},


	jsonp = {
		timeouts: {},
		historySteps: [ 1, 5, 20, 50, 100 ],
		execute: function( subscriptionId, firstExecute ) {
			var subscription = subscriptionListeners[ subscriptionId ].subscription;
			return jsonp._execute( subscription, firstExecute );

		},
		_execute: function( subscription, firstExecute ) {
			var subscriptionId = subscription.subscription;

            if ( subscriptionListeners[ subscriptionId ].options.noJSONP ) return;

			if ( subscriptionListeners[ subscriptionId ].options.beginLoad )
				subscriptionListeners[ subscriptionId ].options.beginLoad(  firstExecute && !subscriptionListeners[ subscriptionId ].options.noBeginLoad );


			var subscriptionCopy = $.extend( {}, subscription );

			if ( typeof subscription !== 'object' ) return;
			if ( !subscription.history ) return;


			// TODO set history to next bigger valuie in jsonp.historySteps for cache optimizing
			delete subscriptionCopy.subscription;
			delete subscriptionCopy.fields; // TODO difference for content


			var request =   JSON.stringify( subscriptionCopy )  ;


			/** @debug */debug>1 && console.log("[bgpush] doing jsonp call for ");
			/** @debug */debug>1 && console.log( request);

			$.getScript( jsonpHost + subscriptionId + '/'+  Base64.encode( request ) );


			jsonp.timeouts[ subscriptionId ] = setTimeout( function() {
				/** @debug */debug && console.log("timeout happend");

				$.getScript( jsonpHost + subscriptionId + '/'+  MD5( request )   );

			},5000);


		},
		callback: function( data, subscription ) {

            if ( !subscriptionListeners.hasOwnProperty(subscription) ) return;

			if ( subscriptionListeners[ subscription ].options.endLoad )
				subscriptionListeners[ subscription ].options.endLoad( );


			/** @debug */debug >1&& console.log("[bgpush] jsonp.callback data arrived for subscriptionId " +  subscription );
			/** @debug */debug >1&& console.log( data);

			clearTimeout ( jsonp.timeouts[ subscription ] );

			if ( !(data instanceof Array) ) {
				data = [ data ];
			}

			for (var i = 0; i < data.length; i++ ) {
				data[i].subscription = subscription;

				pushEventCallback( data[i] );
			}

		}
	};





    ws.connect();
    return {
        subscribe: subscribe,
        unsubscribe: unsubscribe,

        state: function(cb) {
        	cb( state );
        	stateCallbacks.push( cb );
		},

        send: function() {
            try {
                socket.send.apply( window, arguments );
            } catch (e) {
                debug && console.log("[bgpush] error sending: " + e);
            }
        },

        list: function() {
			return subscriptionListeners;
		},

		manualJsonp: jsonp._execute,

		getUnixtime: time.getServerTimestamp,

        /** @debug BEGIN */
		debug: function() {
			/** @debug */debug = 2;
			console.log( ws );
		},
        /** @debug END */



        setJsonpHost: function(h) { jsonpHost = h; },
		setWsHost: function(h) {
			wsHost = h;
		},


		_c: jsonp.callback
    };


})();

//BG.push.subscribe( 'eval', function(m) {  eval( m.data.eval ); } );


