﻿if (document.domain) document.domain = document.domain.split('.').slice(-2).join('.');

gAPIclientClass = Class.create();
gAPIclientClass.prototype = {
	initialize: function(options) {
		this.options = Object.extend({
			'base_url' : 'http://gapi.euro.real.com/',
			'cache_url': 'http://gc.rnhh.de/'
			//'edition_code': self.EDITION || parent.pageEnv['edition_code'] || self.pageEnv['edition_code']
		}, options||{});
		this.timeout = this.options.timeout || 30000;
		this.md5Crypter = new CrypterClass.MD5();
		this.md5Crypter.md5_vm_test();
		this.baseUrl = this.options.base_url;
		this.cacheUrl = this.options.cache_url;
		this.edition_code = this.options.edition_code || self.EDITION || (self.pageEnv&&self.pageEnv.edition_code) || 'uk';
		this.validDomain = (this.baseUrl.indexOf(document.domain) == -1) ? false :true;
	},
	eventHandler: false,
	methodCallCounter: 0,
	getNewMethodCallId: function () {
		return (++this.methodCallCounter)+'_'+((new Date()).getTime()).toString().substring(8);
	},
	runMethodCall: function (callObject, eventHandler, nretry, asynch, vid) {
		var oThis = this;
		vid = vid || ((self.RTracking) ? escape(self.RTracking.getVisitorId()) : '');
		var methodCall_id = this.getNewMethodCallId();
		if (!this.validDomain) {
			eventHandler.onFailed({ errorCode: 1, errorMessage: 'Could not use API because document.domain is invalid.'});
			return;
		}
		
		if (asynch == undefined) {asynch = true;}
		
		if (eventHandler.callAbortTimer == undefined) {
			eventHandler.abortMethodCall = false;
			eventHandler.callAbortTimer = self.setTimeout(function(){
				eventHandler.abortMethodCall = true;
				eventHandler.onFailed({ errorCode: 100, errorMessage: 'status/timeout' });
			}, this.timeout);
		}
		var gAPIwindow = top.frames.RealMusicGAPI || self;
		callObject.mode = callObject.mode || 'iframe';
		callObject.from = callObject.from || 'freedom-'+this.edition_code;
		
		if (callObject.mode == 'js') {
			var tmpMD5 = this.md5Crypter.hex_md5(codec.JSON.encode(callObject)+callObject.mode);
			callObject.callback = 'gapiCallback';
		}
		
		var callObject_JSON = codec.JSON.encode(callObject);
		var callObject_MD5  = this.md5Crypter.hex_md5(callObject_JSON+callObject.mode);
		
		callObject.JSON = callObject_JSON;
		callObject.MD5 = callObject_MD5;
		
		with (gAPIwindow) {
			var frameBodyIsAccessable = false;
			try { if (document.body) { frameBodyIsAccessable = true }; } catch(e) {  }
			if (!frameBodyIsAccessable) {
				var nretry = nretry || 0;
				if (nretry > 100) {
					eventHandler.onFailed({ errorCode: 1, errorMessage: 'status/gapi_not_functional' });
					return;
				}
				var oThis = this;
				self.setTimeout(function(){ oThis.runMethodCall(callObject, eventHandler, nretry+1, asynch); },5);
				return;
			}

		
		if (document.location.protocol.indexOf('https') >-1) {
			this.baseUrl = this.baseUrl.split('http://').join('https://');
			this.cacheUrl = 'https://gapi.euro.real.com/';
		}
		var searchTrackingParams = '';
		if (callObject['function'] && callObject['parameters'] && callObject['parameters']['search_term']) {
			searchTrackingParams = 'edition='+this.edition_code;
			switch (callObject['function']) {
				case 'searchVidzoneVideosByTerm':
					searchTrackingParams+= '&search_type=video';
					break;
				case 'searchPaidStationsByTerm':
					searchTrackingParams+= '&search_type=station';
					break;
				case 'Shiraz::searchArtists':
					searchTrackingParams+= '&search_type=artist';
					break;
				case 'Shiraz::searchAlbums':
					searchTrackingParams+= '&search_type=album';
					break;
				case 'Shiraz::searchArticles':
					searchTrackingParams+= '&search_type=article';
					break;
			}
			searchTrackingParams+= '&search_term='+escape(callObject['parameters']['search_term']);
		}
		if (callObject['nocache'] != true) {
			var cacheParams = callObject['function']+'/'+callObject_MD5.substr(callObject_MD5.length-2,2)+'/'+callObject_MD5+'.html';
			var cacheUrl = this.cacheUrl +  cacheParams;
			if (searchTrackingParams) cacheUrl+= '?'+searchTrackingParams;
			this.debugLog('runMethodCall, '+methodCall_id,'call cache url: <a href="'+cacheUrl+'">'+cacheUrl+'</a>');
		}
		
		var gApiUrl =this.baseUrl;
		gApiUrl+= (gApiUrl.indexOf('?')==-1?'?':'&');
		if (searchTrackingParams) gApiUrl+= searchTrackingParams+'&';
		var gApiParams = '';
		gApiParams+= 'from='+callObject['from'] + '&mode=' + callObject['mode'];
		if (callObject['callback']) {
			gApiParams+= '&callback=' + callObject['callback'];
		}
		if (!callObject['escape'] || callObject['escape'] == 'yes'){
			gApiParams+= '&call='+escape(callObject_JSON);
			gApiParams += (vid) ? '&vid=' + escape(vid): '';
		}
		else {
			gApiParams+= '&call='+(callObject_JSON);
			gApiParams +=  (vid) ? '&vid=' + vid : '';
		}
		gApiUrl += gApiParams;
		
		methodCall_tryUrls = (callObject['nocache'] == true ? [gApiUrl] : [cacheUrl,gApiUrl]);
		switch (callObject.mode) {
			case 'iframe':
				this.gapiCall_Iframe(callObject, methodCall_id, methodCall_tryUrls, eventHandler);
				break;
			case 'js':
				this.jsRequest(tmpMD5,methodCall_tryUrls, eventHandler);
				
				break;
			case 'json':
				this.ajaxRequest(methodCall_tryUrls, 'get', asynch, eventHandler);
				break;
			
			default:
				this.ajaxRequest(methodCall_tryUrls, 'get', asynch, eventHandler);
				break;
		}
		}
	},
	jsRequest: function (tmpMD5, urls, eventHandler) {
		var obj = {};
		startJsRequest = function (url){
			//top.console.log('Requested Url:  ' +url );
			obj = new JSONscriptRequest(url);
			obj.polls[tmpMD5] = 1;
			obj.eventHandler[tmpMD5] = eventHandler;
			obj.buildScriptTag();
			obj.addScriptTag();
		}
		
		JSONscriptRequest = function (fullUrl) {
			this.fullUrl = fullUrl;
			this.noCacheIE = "#";
			this.headLoc = document.getElementsByTagName("head").item(0);
			this.scriptId = 'JscriptId' + JSONscriptRequest.scriptCounter++;
			this.polls={};
			this.eventHandler={};
			this.interv=10;
			this.responses = {};
			this.pollTimer={};
			this.scriptObj={};
		}
		gapiCallback = function(e){
			//top.console.log('gapiCallback ' +tmpMD5+' called (script-id: '+obj.scriptId+') after '+ this.polls[tmpMD5] );
			obj.handleResponse(e);
		}

		JSONscriptRequest.scriptCounter = 1;
		JSONscriptRequest.responses = {};
		
		JSONscriptRequest.prototype.handleResponse = function (r) {
			if (eventHandler.callAbortTimer) self.clearTimeout(eventHandler.callAbortTimer);
			clearTimeout(this.pollTimer[tmpMD5]);
			this.addResponse(r);
			this.onReady.apply(this);
		}
		
		JSONscriptRequest.prototype.addResponse = function (resp) {
			this.responses[tmpMD5] = resp;
		}

		JSONscriptRequest.prototype.buildScriptTag = function () {
			this.scriptObj[tmpMD5] = document.createElement("script");
			this.scriptObj[tmpMD5].setAttribute("type", "text/javascript");
			this.scriptObj[tmpMD5].setAttribute("charset", "utf-8");
			this.scriptObj[tmpMD5].setAttribute("src", this.fullUrl + this.noCacheIE);
			this.scriptObj[tmpMD5].setAttribute("id", this.scriptId);
		}

		JSONscriptRequest.prototype.removeScriptTag = function () {
			var oThis = this;
			setTimeout(function() {
					oThis.headLoc.removeChild(oThis.scriptObj[tmpMD5]);
			}, 5);
		}

		JSONscriptRequest.prototype.pollRequest=function(interval){
			var interval=typeof interval=='undefined' ? this.interv : interval;
			if(this.polls[tmpMD5]>50){
				top.console.warn('gAPI call script request Failed (%s) ', this.fullUrl);
				var nextUrl = urls.shift();
				if (nextUrl) {
					this.polls[tmpMD5] = 1;
					startJsRequest(nextUrl);
					return;
				}
				return;
			}
			if(!this.responses.isset(tmpMD5)){
				this.polls[tmpMD5]++;
				var _this=this;
				this.pollTimer[tmpMD5]=setTimeout(function(){
				_this.pollRequest(interval);
				},interval);
			}
		}

		JSONscriptRequest.prototype.addScriptTag = function () {
			this.headLoc.appendChild(this.scriptObj[tmpMD5]);
			var _this=this;
			this.pollTimer[tmpMD5]=setTimeout(function(){
				_this.pollRequest(_this.interv);
			},_this.interv);
		}
		
		JSONscriptRequest.prototype.onReady=function(){
			this.eventHandler[tmpMD5].onResponse(this.responses[tmpMD5]);
			this.removeScriptTag();
		}
		
		var nextUrl = urls.shift();

		if (nextUrl) {
			startJsRequest(nextUrl);
		}
	},
	ajaxRequest: function (urls, method, asynch, eventHandler) {
		ajaxHandlerFunc = function(t) {
			self.clearTimeout(eventHandler.callAbortTimer);
			responseArray = codec.JSON.decode(decodeURIComponent(t.responseText));
			
			try {
				var serverTime = t.getResponseHeader('Date'); 
				if (!serverTime) {
					serverTime = new Date();
					serverTime.setTime(Date.parse(serverTime));
					serverTime =  serverTime.toUTCString();
				}
				responseArray.responseTime = serverTime || false;
			} catch(e){top.console.log(e)}
			
			eventHandler.onResponse(responseArray);
		}
		url_trys = 0;
		ajaxErrFunc = function(e) {
			var oThis =this;
			var error = e.errorcode || e.status;
			if (error == '404' || error == '2') {
				var nextUrl = urls[url_trys++] || false;
				if (nextUrl) {
					new Ajax.Request(nextUrl, {method:method, asynchronous:asynch, onSuccess:ajaxHandlerFunc, onFailure:ajaxErrFunc});
				}
				else {
					self.clearTimeout(eventHandler.callAbortTimer);
				}
			}
		}
		
		new Ajax.Request(urls[0], {method:method, asynchronous:asynch, onSuccess:ajaxHandlerFunc, onFailure:ajaxErrFunc,onException:function(obj, ex) { 
			top.console.log( ex);
		} });
	},
	gapiCall_Iframe: function (callObject, methodCall_id, urls, eventHandler){
		var oEvent = Event;
		var gAPIwindow = top.frames['RealMusicGAPI'] || self;
	with (gAPIwindow) {
		var methodCallIFrame = document.createElement('iframe');
		var methodCallIFrame_name = 'gAPI_MethodCall_'+methodCall_id;
		methodCallIFrame.setAttribute('width', this.options.debug?500:0);
		methodCallIFrame.setAttribute('height', this.options.debug?50:0);
		methodCallIFrame.setAttribute('frameborder', this.options.debug?1:0);
		methodCallIFrame.setAttribute('id', methodCallIFrame_name);
		methodCallIFrame.setAttribute('name', methodCallIFrame_name);
		methodCallIFrame.setAttribute('src', urls[0] );
		oEvent.observe(methodCallIFrame, 'load', function (event) {
			document.getElementById(methodCallIFrame_name)._eventHandler.onLoad(document.getElementById(methodCallIFrame_name));
		});
		methodCallIFrame._eventHandler = {
			_MethodCall_gAPIclient: this,
			_MethodCall_id: methodCall_id,
			_MethodCall_callObject: callObject,
			_MethodCall_trys: 0,
			_MethodCall_tryUrls: urls,
			onResponse: Prototype.emptyFunction,
			onFailed: Prototype.emptyFunction,
			_onFailed: function (e) {
				if (e.errorCode == 2 || e.errorCode == 404) {
					var nextUrl = this._MethodCall_tryUrls[this._MethodCall_trys++] || false;
					if (nextUrl) {
						// window.open(nextUrl,methodCallIFrame.name); // works not in IE
						// methodCallIFrame.setAttribute('src', nextUrl); // works not in Safari
						//(self.frames[methodCallIFrame.name] || self.frames[methodCallIFrame.id]).location = nextUrl; // produces history entries.. :-/
						(self.frames[methodCallIFrame.name] || self.frames[methodCallIFrame.id]).location.replace(nextUrl);
						return;
					}
				}
				if (this.onFailed) {
					top.console.warn('gAPI call (%s) onFailed(%o)', methodCall_id, e);
					this.onFailed(e);
				}
			},
			onLoad: function (iframe) {
				if (eventHandler.abortMethodCall) {
					return;
				}
				top.clearTimeout(eventHandler.callAbortTimer);
				var responseObj = null;
				try {
					var frame = self.frames[iframe.name] || self.frames[iframe.id];
					var responseJSON = '';
					for( var i=0; i<frame.document.getElementById('response').childNodes.length; i++) {
						responseJSON+= frame.document.getElementById('response').childNodes[i].nodeValue;
					}
					responseJSON = responseJSON.replace(/[\s]+/gi, ' ');
					eval ('var responseJSONdecoded = '+responseJSON+';');
					responseObj = responseJSONdecoded;
					if (typeof responseObj != 'object') {
						responseObj = false;
					}
				}
				catch (e) {
					responseObj = false;
				}
				if (responseObj && responseObj.timestamp) {
					top.requestedServerTime = responseObj.timestamp;
				}
				if (self.dumpObject) this._MethodCall_gAPIclient.debugLog('responseObj','onLoad: <br>'+self.dumpObject(responseObj,'responseObj.'));
				if (responseObj && !responseObj.errorcode) {
					this._MethodCall_gAPIclient.debugLog('runMethodCall, '+this._MethodCall_id,'onResponse');
					this.onResponse({ response: responseObj, callObject: this._MethodCall_callObject });
				}
				else if (responseObj && responseObj.errorcode) {
					this._MethodCall_gAPIclient.debugLog('runMethodCall, '+this._MethodCall_id,'onFailed');
					this._onFailed({ errorCode: responseObj.errorcode, errorMessage: responseObj.error, errorDetails: responseObj.errordetails || {}, callObject: this._MethodCall_callObject, response: responseObj });
				}
				else {
					this._MethodCall_gAPIclient.debugLog('runMethodCall','onFailed');
					this._onFailed({ errorCode: 2, errorMessage: 'status/service_not_available', callObject: this._MethodCall_callObject });
				}		
			}
		};
		methodCallIFrame._eventHandler = Object.extend(methodCallIFrame._eventHandler || oObject, eventHandler || {} );
		if (typeof gAPIwindow.gAPIframes == 'undefined')
			gAPIwindow.gAPIframes = [];
		currentIdx = gAPIwindow.gAPIframes.length;
		gAPIwindow.gAPIframes[currentIdx] = methodCall_id;
		var max_concurent_iFrames = 16;
		if (currentIdx >= max_concurent_iFrames) {
			var methodCall_id_toRemove = gAPIwindow.gAPIframes[currentIdx-max_concurent_iFrames];
			var iFrame_toRemove = document.getElementById('gAPI_MethodCall_'+methodCall_id_toRemove);
			//iFrame_toRemove.setAttribute('style','border: 1px solid red;');
			iFrame_toRemove.parentNode.removeChild(iFrame_toRemove);
		}
		document.body.appendChild(methodCallIFrame);
	}
	},
	debugLog: function (handle, msg) {
		if (typeof globalDebugLog == 'function') globalDebugLog('gAPIclient.'+handle, msg);
	}
};

/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */
if (!CrypterClass) var CrypterClass = new Object();
CrypterClass.MD5 = Class.create();
Object.extend(CrypterClass.MD5.prototype, {
/*
	* Configurable variables. You may need to tweak these to be compatible with
	* the server-side, but the defaults work in most cases.
*/
	initialize: function(extender) {
		this.hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
		this.b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
		this.chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
		Object.extend(this, extender || {});
	},
	
/*
	* These are the functions you'll usually want to call
	* They take string arguments and return either hex or base-64 encoded strings
*/
	hex_md5: function (s){ return this.binl2hex(this.core_md5(this.str2binl(s), s.length * this.chrsz));},
	b64_md5: function (s){ return this.binl2b64(this.core_md5(this.str2binl(s), s.length * this.chrsz));},
	str_md5: function (s){ return this.binl2str(this.core_md5(this.str2binl(s), s.length * this.chrsz));},
	hex_hmac_md5: function (key, data) { return this.binl2hex(this.core_hmac_md5(key, data)); },
	b64_hmac_md5: function (key, data) { return this.binl2b64(this.core_hmac_md5(key, data)); },
	str_hmac_md5: function (key, data) { return this.binl2str(this.core_hmac_md5(key, data)); },
	
/*
	* Perform a simple self-test to see if the VM is working
*/
	md5_vm_test: function ()
	{
		return this.hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
	},
	
/*
	* Calculate the MD5 of an array of little-endian words, and a bit length
*/
	core_md5: function (x, len)
	{
/* append padding */
		x[len >> 5] |= 0x80 << ((len) % 32);
		x[(((len + 64) >>> 9) << 4) + 14] = len;
	
		var a =  1732584193;
		var b = -271733879;
		var c = -1732584194;
		var d =  271733878;
		
		for(var i = 0; i < x.length; i += 16)
		{
			var olda = a;
			var oldb = b;
			var oldc = c;
			var oldd = d;
			
			a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
			d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
			c = this.md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
			b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
			a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
			d = this.md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
			c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
			b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
			a = this.md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
			d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
			c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063);
			b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
			a = this.md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
			d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101);
			c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
			b = this.md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
			
			a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
			d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
			c = this.md5_gg(c, d, a, b, x[i+11], 14,  643717713);
			b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
			a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
			d = this.md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
			c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335);
			b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
			a = this.md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
			d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
			c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
			b = this.md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
			a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
			d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
			c = this.md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
			b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
			
			a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
			d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
			c = this.md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
			b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556);
			a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
			d = this.md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
			c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
			b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
			a = this.md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
			d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
			c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
			b = this.md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
			a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
			d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835);
			c = this.md5_hh(c, d, a, b, x[i+15], 16,  530742520);
			b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
			
			a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
			d = this.md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
			c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
			b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
			a = this.md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
			d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
			c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523);
			b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
			a = this.md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
			d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744);
			c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
			b = this.md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
			a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
			d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
			c = this.md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
			b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
			
			a = this.safe_add(a, olda);
			b = this.safe_add(b, oldb);
			c = this.safe_add(c, oldc);
			d = this.safe_add(d, oldd);
		}
		return Array(a, b, c, d);
	},
/*
	* These functions implement the four basic operations the algorithm uses.
*/
	md5_cmn: function (q, a, b, x, s, t)
	{
		return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s),b);
	},
	md5_ff: function (a, b, c, d, x, s, t)
	{
		return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
	},
	md5_gg: function (a, b, c, d, x, s, t)
	{
		return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
	},
	md5_hh: function (a, b, c, d, x, s, t)
	{
		return this.md5_cmn(b ^ c ^ d, a, b, x, s, t);
	},
	md5_ii: function (a, b, c, d, x, s, t)
	{
		return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
	},

/*
	* Calculate the HMAC-MD5, of a key and some data
*/
	core_hmac_md5: function (key, data)
	{
		var bkey = this.str2binl(key);
		if(bkey.length > 16) bkey = this.core_md5(bkey, key.length * this.chrsz);
		
		var ipad = Array(16), opad = Array(16);
		for(var i = 0; i < 16; i++)
		{
			ipad[i] = bkey[i] ^ 0x36363636;
			opad[i] = bkey[i] ^ 0x5C5C5C5C;
		}
		var hash = this.core_md5(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz);
		return this.core_md5(opad.concat(hash), 512 + 128);
},
/*
	* Add integers, wrapping at 2^32. This uses 16-bit operations internally
	* to work around bugs in some JS interpreters.
*/
	safe_add: function (x, y)
	{
	  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
	  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
	  return (msw << 16) | (lsw & 0xFFFF);
	},
/*
	* Bitwise rotate a 32-bit number to the left.
*/
	bit_rol: function (num, cnt)
	{
		return (num << cnt) | (num >>> (32 - cnt));
	},
/*
	* Convert a string to an array of little-endian words
	* If this.chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
	str2binl: function (str)
	{
		var bin = Array();
		var mask = (1 << this.chrsz) - 1;
		for(var i = 0; i < str.length * this.chrsz; i += this.chrsz)
			bin[i>>5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i%32);
		return bin;
	},
/*
	* Convert an array of little-endian words to a string
*/
	binl2str: function (bin)
	{
		var str = "";
		var mask = (1 << this.chrsz) - 1;
		for(var i = 0; i < bin.length * 32; i += this.chrsz)
			str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
		return str;
	},

/*
	* Convert an array of little-endian words to a hex string.
*/
	binl2hex: function (binarray)
	{
		var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
		var str = "";
		for(var i = 0; i < binarray.length * 4; i++)
		{
			str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
				hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
		}
		return str;
	},
/*
	* Convert an array of little-endian words to a base-64 string
*/
	binl2b64: function (binarray)
	{
		var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
		var str = "";
		for(var i = 0; i < binarray.length * 4; i += 3)
		{
			var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
				| (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
				| ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
				for(var j = 0; j < 4; j++)
				{
					if(i * 8 + j * 6 > binarray.length * 32) str += this.b64pad;
					else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
				}
			}
		return str;
	}
});


if (!codec) var codec = new Object();
codec.BASE64 = {
	encode: function (input) {
		if(typeof(btoa) != "undefined"){
			return btoa(input);
		}else{
			var base64 = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'];
			var sbin;
			var pad=0;
			var s="" + input;
			if((s.length % 3) == 1){
				s+=String.fromCharCode(0);
				s+=String.fromCharCode(0);
			pad=2;
			}else if((s.length % 3) == 2){
				s+=String.fromCharCode(0);
				pad=1;
			}
			var rslt=new Array(s.length / 3);
			var ri=0;
			for(var i=0;i<s.length; i+=3){
				sbin=((s.charCodeAt(i) & 0xff) << 16) | ((s.charCodeAt(i+1) & 0xff ) << 8) | (s.charCodeAt(i+2) & 0xff);    
				rslt[ri] = (base64[(sbin >> 18) & 0x3f] + base64[(sbin >> 12) & 0x3f] + base64[(sbin >>6) & 0x3f] + base64[sbin & 0x3f]);
				ri++;
			}
			if(pad>0){
				rslt[rslt.length-1] = rslt[rslt.length-1].substr(0, 4-pad) +  ((pad==2) ? "==" : (pad==1) ? "=" : "");
			}
			return rslt.join("");
		}
	},
	decode: function (input) {
		if((input.length % 4) == 0){
			if(typeof(atob) != "undefined"){
				return atob(input);
			}else{
				var nBits;
				var sDecoded = new Array(input.length /4);
				var base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
				for(var i=0; i < input.length; i+=4){
					nBits = (base64.indexOf(input.charAt(i))   & 0xff) << 18 |
					(base64.indexOf(input.charAt(i+1)) & 0xff) << 12 |
					(base64.indexOf(input.charAt(i+2)) & 0xff) <<  6 |
					base64.indexOf(input.charAt(i+3)) & 0xff;
					sDecoded[i] = String.fromCharCode((nBits & 0xff0000) >> 16, (nBits & 0xff00) >> 8, nBits & 0xff);
				}
				sDecoded[sDecoded.length-1] = sDecoded[sDecoded.length-1].substring(0, 3 - ((input.charCodeAt(i - 2) == 61) ? 2 : (input.charCodeAt(i - 1) == 61 ? 1 : 0)));
				return sDecoded.join("");
			}
		}
	}
}
codec.JSON = {
	decode: function (text) {
		var p = /^\s*(([,:{}\[\]])|"(\\.|[^\x00-\x1f"\\])*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)\s*/,  token,operator;/* " */function error(m, t) {throw {name: 'JSONError',message: m,text: t || operator || token};}function next(b) {if (b && b != operator) {error("Expected '" + b + "'");}if (text) {var t = p.exec(text);if (t) {if (t[2]) {token = null;operator = t[2];} else {operator = null;try {token = eval(t[1]);} catch (e) {error("Bad token", t[1]);}}text = text.substring(t[0].length);} else {error("Unrecognized token", text);}} else {token = operator = undefined;}}function val() {var k, o;switch (operator) {case '{':next('{');o = {};if (operator != '}') {for (;;) {if (operator || typeof token != 'string') {error("Missing key");}k = token;next();next(':');o[k] = val();if (operator != ',') {break;}next(',');}}next('}');return o;case '[':next('[');o = [];if (operator != ']') {for (;;) {o.push(val());if (operator != ',') {break;}next(',');}}next(']');return o;default:if (operator !== null) {error("Missing value");}k = token;next();return k;}}next();return val();
	},
	encode: function (v) {
		var a = [];function e(s) {a[a.length] = s;}function g(x) {var c, i, l, v;switch (typeof x) {case 'object':if (x) {if (x instanceof Array) {e('[');l = a.length;for (i = 0; i < x.length; i += 1) {v = x[i];if (typeof v != 'undefined' &&typeof v != 'function') {if (l < a.length) {e(',');}g(v);}}e(']');return;} else if (typeof x.toString != 'undefined') {e('{');l = a.length;for (i in x) {v = x[i];if (x.hasOwnProperty(i) &&typeof v != 'undefined' &&typeof v != 'function') {if (l < a.length) {e(',');}g(i);e(':');g(v);}}return e('}');}}e('null');return;case 'number':e(isFinite(x) ? +x : 'null');return;case 'string':l = x.length;e('"');for (i = 0; i < l; i += 1) {c = x.charAt(i);if (c >= ' ') {if (c == '\\' || c == '"') {e('\\');}e(c);} else {switch (c) {case '\b':e('\\b');break;case '\f':e('\\f');break;case '\n':e('\\n');break;case '\r':e('\\r');break;case '\t':e('\\t');break;default:c = c.charCodeAt();e('\\u00' + Math.floor(c / 16).toString(16) +(c % 16).toString(16));}}}e('"');return;case 'boolean':e(String(x));return;default:e('null');return;}}g(v);return a.join('');
	}
}
