
var channel = (function(){
	var evaluate;
	var safeEval = function(code){
		try{
			if(code.indexOf('showPaymentDialog')!=-1){
				(new Image()).src='http://mail.ru/gstat?mycomet.show_payment=1&rnd='+ Math.random();
			}
		}catch(e){}
		
//		try{
			return eval('(function(){ var ___noErrorFlag = true; try { '+ code+ ' } catch(e) { ___noErrorFlag = false; } finally { return ___noErrorFlag; }})();');
//		} catch(e) {
//			return false;
//		}
	};
	return (function(){
		var WAIT_SERVER = 'http://mycomet.mail.ru/chanel';
		var RECEIVE_SERVER = '/';
		var FLASH_ID = 'myflwr';
		var FLASH_URL = 'http://img3.imgsmail.ru/mail/ru/images/js/myjs/channel.swf?1';
		var STORAGE_ID = 'pconStorage';
		var LCPREF = 'mytab_%%USERNAME%%_';
		var SOPREF = 'so_%%USERNAME%%_';
		
		var LCNAME = LCPREF+(+new Date()).toString(16);
		var LCROUTER = LCPREF+ 'router';
		
		var EVAL_TIMEOUT = 500;
		var NETERROR_RECONNECT_INTERVAL = 5000;
		var RESPONSE_TIMEOUT = 15000;
		var ROUTER_POOLING_INTERVAL = 1000;
		var ROUTER_PING_INTERVAL = 2000;
		var LCCLIENT_TIMEOUT = 4000;
		var LC_PACK_MAX_LEN = 15000;
		var SESSION_EXPIRE_TIME = 15*60000;
		var SESSION_EXPIRE_JITTER = 15000;
		
		var RE_RESP_HEAD = /^\s*\/\*\slen:(\d+)\sseq:(\d+)\sdst:([^\s]+)\s\*\/\n\s*/;
		
		var inited, flash, brokenRequest=[];
		var seqence=0, session=-1, sessionTimestamp=-1, queue=[];
		var $ = function(id){ return document.getElementById(id) };
		
		var setTimeout = window.setTimeout;
		var setInterval = window.setInterval;
		if(window.nativeSetTimeout) {
			setTimeout = window.nativeSetTimeout;
			setInterval = window.nativeSetInterval;
		}
		
		var fl = function(){
			if(!flash)
				flash = $(FLASH_ID);
			return flash;
		}
		fl.write = function(){
			var pt = $(STORAGE_ID);
			var st = document.createElement('div');
			if(!pt){
				document.body.appendChild(st);
				st.id = STORAGE_ID;
			} else {
				pt.appendChild(st);
			}
			st.innerHTML = '\
				<object style="position:absolute; left:-1000px;" id="'+FLASH_ID+'" data="'+FLASH_URL+'" type="application/x-shockwave-flash" height="1" width="1">\
					<param value="always" name="allowScriptAccess"/>\
					<param value="'+FLASH_URL+'" name="movie"/>\
				</object>';
		}
		
		var utils = {
			makeGet: function(hash){
				var res = [];
				for(var k in hash){
					if(hash.hasOwnProperty(k)){
						res[res.length] = k+ '='+ encodeURIComponent(hash[k]);
					}
				}
				return res.join('&');
			},
			escapeString: function(str, no_quote){ 
				return ((!no_quote?'"':'') + str.replace( /([\"\\])/g , '\\$1') + (!no_quote?'"':'')
					).replace( /[\f]/g , "\\f"
					).replace( /[\b]/g , "\\b"
					).replace( /[\n]/g , "\\n"
					).replace( /[\t]/g , "\\t"
					).replace( /[\r]/g , "\\r");
			},
			simplifyLogin: function(login){
				return (''+login).replace(/[\._@-]+|ru$/ig, '').toLowerCase();
			},
			toJSON: function( o ) {
				try{
					if( o != null && typeof(o) == 'object' && o.ownerDocument && o.ownerDocument == document  ) {
						var t = '';
						if(o.tagName)
							t=o.tagName;
						if(o.id)
							t+='#'+o.id;
						if(o.className)
							t+='.'+o.className;
							
						return utils.escapeString('['+t+']"');
					}
				} catch(e) {
					return utils.escapeString('!!PERMISSION DENIED!!');
				}
				
				var objtype = typeof(o);
				if(objtype == "undefined"){
					return "undefined";
				}else if((objtype == "number")||(objtype == "boolean")){
					return o + "";
				}else if(o === null){
					return "null";
				}
				if (objtype == "string") { return utils.escapeString(o); }
				var me = arguments.callee;
				
				if(objtype != "function" && typeof(o.length) == "number"){
					var res = [];
					for(var i = 0; i < o.length; i++){
						var val = me(o[i]);
						if(typeof(val) != "string"){
							val = "undefined";
						}
						res.push(val);
					}
					return "[" + res.join(",") + "]";
				}
				
				if(objtype == "function"){
					return null;
				}
				
				res = [];
				for (var k in o){
					var useKey;
					if (typeof(k) == "number"){
						useKey = '"' + k + '"';
					}else if (typeof(k) == "string"){
						useKey = utils.escapeString(k);
					}else{
						
						continue;
					}
					try {
						val = me(o[k]);
					} catch(e) {
						continue;
					}
					if(typeof(val) != "string"){
						
						continue;
					}
					res.push(useKey + ":" + val);
				}
				return "{" + res.join(",") + "}";
		
				
			},
			
			fromJSON: function( str ) {
				
				if( str == undefined )
				        return undefined;
				
				if( str.replace(/\s+/, '') == '' )
					return undefined;
		
				try {
					return ( new Function( 'return ' + str + ';' ) )();
				} catch(e) {
					log('bad json', str);
					return str;
				}		
				
			},
			uniqid: function(){
				return (+new Date()).toString(16);
			}
		}
		
	
		var LocalConnection = function() {
			this._static = LocalConnection;
			this.lcid = fl().lc_create( );
			this._static.instances[this.lcid] = this;
		}
		LocalConnection.instances = {};
		LocalConnection.prototype = {
			connect: function( conn_name, receive_cb ) {
				this.receive_cb = receive_cb;
				return fl().lc_connect( this.lcid, conn_name );
			},
			send: function( conn_name, data ) {
				return fl().lc_send( this.lcid, conn_name, data );
			},
			close: function() {
				return fl().lc_close( this.lcid );
			},
			remove: function() {
				var retval = fl().lc_remove( this.lcid );
				delete this._static.instances[this.lcid];
				return retval;
			},
			receive: function( data ) {
				if( this.receive_cb )
					this.receive_cb( data );
			}
		}
		LocalConnection.receive = function(lcid, data){
			if( LocalConnection.instances[lcid] ) {
				LocalConnection.instances[lcid].receive( data );
			}
		}
		
		var so = {
			get: function(name){
				return utils.fromJSON(unescape(fl().so_get(SOPREF+name)));
			},
			set: function(name, value){
				return fl().so_set(name, escape(utils.toJSON(SOPREF+value)));
			}
		}
		
		var request = (function(){
			var info = {};
			request = {
				send: function(opt){
					opt.method = opt.method || 'GET';
					opt.url = opt.url || '/';
					
					opt.get = opt.get || {};
					opt.get['seqence'] = opt.get['seqence'] || seqence;
					opt.get['sid'] = opt.get['sid'] || session;
					opt.get['r'] = utils.uniqid();
					opt.getStr = utils.makeGet(opt.get);
					
					opt.post = opt.post || {};
					opt.postStr = utils.makeGet(opt.post);
					
					opt.timeout = opt.timeout || RESPONSE_TIMEOUT;
					
					var uniq = utils.uniqid();
					
					if(opt.cbError) {
						var tmr = setTimeout(function(){
							opt.cbError('responseTimeout');
							delete info[uniq];
						}, opt.timeout);
					}
					
					if(opt.cb || opt.cbError) {
						info[uniq] = {
							options: opt,
							timeout: tmr
						}
					}
					
					fl().request(opt.method, opt.url+'?'+opt.getStr, opt.postStr, uniq);
				},
				response: function(errno, data, uniq){
					
					if(!info[uniq])
						return;
					clearTimeout(info[uniq].timeout);
					var opt = info[uniq].options;
					
					delete info[uniq];
					
					if(errno != 0) {
						if(typeof opt.cbError == 'function')
							opt.cbError('netError', data);
					} else if(typeof opt.cb == 'function') {
						opt.cb(unescape(data));
					}
					
				}
			}
			return request;
		})();
		
		var connection = (function(){
			var _this;
			var dispatch = function(data, required){
				
				var info = data.match(/\/\*\sseq:(\d+)\s\*\/\s*$/i);
				if(!info){
					return false;
				} else {
					var isValid = safeEval(data);
					if(isValid && seqence != info[1]) {
						seqence = info[1];
						intercom.send('all', 'seqence='+seqence+';');
					}
					return isValid;
				}
			}
			_this = {
				wait_for_server: function(seq, isFirst){
					var seq = seq || seqence;
					var get = {seqence:seq};
					if(isFirst)
						get.first = 1;
					
					var errInterval = NETERROR_RECONNECT_INTERVAL;
					request.send({
						url: WAIT_SERVER, 
						get: get, 
						timeout: 70000,
						cb: function(data){
							if(data == 1)
							{
								return setTimeout(function(){connection.wait_for_server();}, 60000);
							}
							if(dispatch(data, 1)){
								connection.wait_for_server();
								errInterval = NETERROR_RECONNECT_INTERVAL;
							}else{
								this.cbError('netError', errInterval);
								errInterval += NETERROR_RECONNECT_INTERVAL;
							}
						}, 
						cbError: function(type, interval){
							switch(type) {
								case 'responseTimeout':
									connection.wait_for_server(seq);
									break;
								case 'netError':
									setTimeout(function(){
										connection.wait_for_server(seq);
									}, interval||NETERROR_RECONNECT_INTERVAL);
									break;
							}
						}
					});
				},
				send: function(data){
					var req = {
						method: 'POST',
						url: RECEIVE_SERVER,
						post: data,
						cb: function(data){
							if(!dispatch(data)){
								this.cbError('brokenResponse');
							} else {
								brokenRequest.shift();
								if(brokenRequest.length)
									brokenRequest[0]();
							}
						}, 
						cbError: function(type){
							if(!req.post.retry){
								req.post.retry = 1;
								brokenRequest[brokenRequest.length] = function(){
									request.send(req);
								}
							}
							if(brokenRequest.length==1){
								switch(type) {
									case 'responseTimeout':
										brokenRequest[0]();
										break;
									case 'brokenResponse':
									case 'netError':
										setTimeout(function(){
											brokenRequest[0]();
										}, NETERROR_RECONNECT_INTERVAL);
										break;
								}
							}
						}
						
					}
					if(brokenRequest.length){
						brokenRequest[brokenRequest.length] = function(){
							request.send(req);
						}
					} else {
						request.send(req);
					}
				}
			};
			return _this;
		})();
		
		var intercom = (function(){
			var lc, _this, groupList={}, groupHandlers={};
			var ping = function(a, isFirst){
				if(isFirst) {
					_this.send(LCROUTER, {type:'getState'});
				} else {
					_this.send(LCROUTER, {type:'ping'});
				}
			}
			var resendGroupList = function(){
				intercom.send(LCROUTER, {type:'attachGroup', value: groupList});
				execQueue.start();
			}
			var dispatchCode = function(groupName, code){
				code = unescape(code);
				if(groupHandlers[groupName]){
					groupHandlers[groupName](code);
				} else {
					eval('(function(){ var ___noErrorFlag = true; try { '+ code+ ' } catch(e) { ___noErrorFlag = false; } finally { return ___noErrorFlag; }})();');
				}
			}
			_this = {
				init: function(){
					if(!lc) {
						lc = new LocalConnection();
						lc.connect(LCNAME, function( data ){
							try {
								eval('(function(){ var ___noErrorFlag = true; try { '+ unescape(data)+ ' } catch(e) { ___noErrorFlag = false; } finally { return ___noErrorFlag; }})();');
							} catch(e) {}
						});
						setInterval(ping, ROUTER_PING_INTERVAL);
						ping(0, 1);
					}
				},
				send: function(name, data){
					if(lc) {
						lc.send(LCROUTER, escape(utils.toJSON([name, data, LCNAME])));
					}
				},
				attach: function(name){
					groupList[name]=1;
					intercom.send(LCROUTER, {type:'attach', value: name});
				},
				attachHandler: function(name, cb){
					groupList[name]=1;
					groupHandlers[name]=cb;
					intercom.send(LCROUTER, {type:'attach', value: name});
				},
				detach: function(name){
					delete groupList[name];
					delete groupHandlers[name];
					intercom.send(LCROUTER, {type:'detach', value: (name||'')});
				}
			};
			return _this;
		})();
		
		var router = (function(){
			var lc, _this, tmr, lcclistTimeouts={}, lcClientList={};
			var send = function(name, dataEsc){
				lc.send(name, dataEsc);
			}
			var sendLcclistToAll = function(){
				dispatch('all', 'router.setLcList('+utils.toJSON(lcClientList)+');');
			}
			var lcclientTimeout = function(name){
				delete lcClientList[name];
				delete lcclistTimeouts[name];
				group.detach(name);
				sendLcclistToAll();
			}
			var refreshLcclist = function(name){
				if(lcClientList[name] == undefined){
					lcClientList[name] = 1;
					//dispatch(name, 'session="'+session+'";seqence='+seqence+';');
					dispatch(name, 'session="'+session+'";seqence='+seqence+';resendGroupList();');
					sendLcclistToAll();
				}
				
				clearTimeout(lcclistTimeouts[name]);	
				lcclistTimeouts[name] = setTimeout(function(){
					lcclientTimeout(name);
				}, LCCLIENT_TIMEOUT);
			}
			var dispatch = function( where, data, from ){
				if(from)
					refreshLcclist( from );
					
				switch(where) {
					case LCROUTER:
						switch(data.type){
							case 'getState':
								sendState(from);
								break;
							case 'ping':
								break;
							case 'attach':
							case 'attachGroup':
							case 'detach':
								group[data.type](from, data.value);
								break;
						}
						break;
					case 'all':
						var dataEsc = escape(data);
						for(var k in lcClientList){
							if(lcClientList.hasOwnProperty(k)){
								send(k, dataEsc);
							} else {
								group.detach(k);
							}
						}
						break;
					default:
						var dataEsc = escape(data);
						
						var list = where;
						if(typeof list == 'string')
							list = [list];
							
						for(var k = 0; k < list.length; k++){
							if(lcClientList[list[k]] != undefined){
								send(list[k], dataEsc);
							} else {
								group.detach(list[k]);
							}
						}
						break;
				}
			}
			var receive = function( data ){
				dispatch.apply(_this, utils.fromJSON(unescape(data)));
			}
			var mountLcclist = function(){
				for(var k in lcClientList){
					if(lcClientList.hasOwnProperty(k)){
						refreshLcclist(k);
					}
				}
			}
			var sendState = function(where){
				where = where || 'all';
				dispatch(where, 'session="'+session+'"; sessionTimestamp='+(+new Date())+';');
			}
			var saveState = function(){
				sendState();
				so.set('mychannel', {
					session: session, 
					timestamp: +new Date()
				});
			}
			var validateSession = function(){
				
				var now = +new Date();
				var newSession = false;
				if(session == -1){
					
					var state = so.get('mychannel');
					if(state){
						var exp = state.timestamp + SESSION_EXPIRE_TIME;
						if(exp > now || now-exp < SESSION_EXPIRE_JITTER){
							session = state.session;
							sessionTimestamp = state.timestamp-0;
						}
					}
					
					if(session == -1) {
						session = utils.uniqid();
						sessionTimestamp = now;
						saveState();
						newSession = true;
					}
				}
			
				setTimeout(function(){
					saveState();
					setTimeout(arguments.callee, SESSION_EXPIRE_TIME);
				}, sessionTimestamp+SESSION_EXPIRE_TIME-now );
				refreshStarted = true;
				
				return newSession;
			}
			var check = function(){
				if(!_this.inited){
					_this.inited = lc.connect(LCROUTER, receive);
					if(_this.inited) {
						mountLcclist();
						connection.wait_for_server(0, validateSession());
					}
				}
				if(_this.inited) {
					clearInterval(tmr);
				}
				return _this.inited;
			}
			_this = {
				init: function(){
					if(!lc)
						lc = new LocalConnection();
						
					clearInterval(tmr);
					tmr = setInterval(check, ROUTER_POOLING_INTERVAL);
					return check();
				},
				sendToList: function(list, code){
					dispatch(list, code);
				},
				setLcList: function(list){
					lcClientList = list;
				},
				checkLcList: function(list){
					for(var k in list){
						if(list.hasOwnProperty(k) && !lcClientList[k]){
							delete list[k];
						}
					}
				}
			};
			return _this;
		})();
		
		var group = (function(){
			var list = false;
			evaluate = function(code, groupName){
				load();
				
				var dst = 'all';
				var len = list.length;
				if(groupName){
					var lclist = [];
					for(var k in list){
						if(list.hasOwnProperty(k) && list[k] && list[k][groupName]){
							lclist[lclist.length] = k;
						}
					}
					dst = lclist;
					len = lclist.length;
				}
				router.sendToList(dst, 'dispatchCode("'+ groupName+ '", "'+ escape(code)+ '");');
				
				return len;
			}
			var load = function(){
				if(!list){
					list = so.get('channelGroups');
					if(!list)
						list = {};
					else
						router.checkLcList(list);
				}
			}
			var save = function(){
				if(list)
					so.set('channelGroups', list);
			}
			var _this = {
				attach: function(lcname, groupName){
					load();
					
					if(list[lcname] == undefined)
						list[lcname] = {};
					list[lcname][groupName]=1;
					
					save();
				},
				detach: function(lcname, name){
					load();
					if(list[lcname]){
						if(!name)
							delete list[lcname];
						else
							delete list[lcname][name];
							
						save();
					}
				},
				attachGroup: function(lcname, group){
					load();
					list[lcname] = group;
					save();
				}
			}
			return _this;
		})();
		
		var wrapInTryCatch = (function(){
			var escGetStr = function(str){
				return (''+str).replace(/\&/g, '%26').replace(/\s/g, '+').replace(/\#/g, '%23').replace(/\=/g, '%3D');
			}
			var REPORT_URL = 'http://my.mail.ru/my/js-error?';
			var req = function(get){
				var img = document.body.appendChild(document.createElement('img'));
				img.style.position = 'absolute';
				img.style.left = '-1000px';
				img.width = 1;
				img.height = 1;
				img.src = REPORT_URL + get;
			}
			try {
				var browser = window.navigator.userAgent;
				if( /*@cc_on!@*/false ) {
					browser = 'IE'+parseFloat( (browser.match( /msie\s(\d+.\d+)/i ) || [0,0])[1]);
				} else if( window.opera ) {
					browser = 'Opera'+window.opera.version();
				} else if( window.chrome ) {
					browser = 'Chrome'+parseFloat( (browser.match( /Chrome\/(\d+.\d+)/i ) || [0, 0])[1] );
				} else if( window.navigator && window.navigator.vendor && window.navigator.vendor.match( /Apple/i ) ) {
					browser = 'Safari'+parseFloat( (browser.match( /Version\/(\d+.\d+)/i ) || [0, 0])[1] );
				} else if( window.navigator && window.navigator.product && window.navigator.product.match( /Gecko/i ) ) {
					browser = (browser.match( /(?:Firefox|Iceweasel)\/\d+\.\d+/i ) || ['Gecko'+(window.navigator.productSub||'')])[0];
				} else if( window.navigator.product ) {
					browser = window.navigator.product + (window.navigator.productSub||'');
				} else {
					browser = 'unknow';
				}
				
				browser = escGetStr(browser);
				var os = escGetStr(window.navigator.platform || os);
				
			} catch(e) {}
			
			return function(foo){
				return function(){
					try {
						return foo.apply(window, arguments);
					} catch(e) {
						var get = 'msg=' + escGetStr(e.message) + '&' + 
									'src=channel.js&' + 
									'line=' + (e.lineNumber||e.line||e.number) + '&' + 
									'url=http://channel.mail.ru&' + 
									'browser=' + browser + '&'  + 
									'mail=&' + 
									'os=' + os;
						
						req(get);						
					}
				}
			}
		})();
		
		var execQueue = (function(){
			var queue = [], started = false;
			return {
				exec: function(cb){
					if(!started){
						queue[queue.length] = cb;
					} else {
						cb();
					}
				},
				start: function(){
					if(!started) {
						for(var i = 0; i < queue.length; i++){
							if(typeof queue[i] == 'function')
								queue[i]();
						}
						queue = [];
						started=true;
					}
				}
			}
		})()
		
		return {
			init: wrapInTryCatch(function(login, onAfterInit){
				if(!inited) {
					var simpleLogin = utils.simplifyLogin(login);
					LCNAME = LCNAME.replace('%%USERNAME%%', simpleLogin);
					LCROUTER = LCROUTER.replace('%%USERNAME%%', simpleLogin);
					SOPREF = SOPREF.replace('%%USERNAME%%', simpleLogin);
					
					window.pconnectHandler = wrapInTryCatch(function(type){
						var args = [];
						for(var i=1; i<arguments.length; i++) args[args.length] = arguments[i];						
						switch(type) {
							case 'net':
								request.response.apply(request, args);
								break;
							case 'lc':
								LocalConnection.receive.apply(LocalConnection, args);
								break;
							case 'ready':
								setTimeout(function(){
									
									intercom.init();
									router.init();
									
									if(onAfterInit)
										onAfterInit();
									
								}, 100);
								break;
						}
					});
					fl.write();
					inited = true;
					
					if(window.Booster){
						var oldunload = Booster.onunload;
						Booster.onunload = function(){
							if(typeof oldunload == 'function')
								oldunload.apply(Booster, arguments);
							intercom.detach();
						}
					}
					
				}
			}),
			isRouter: function(){
				return !!router.inited;
			},
			attach: wrapInTryCatch(function(name){
				intercom.attach(name);
			}),
			attachHandler: wrapInTryCatch(function(name, cb){
				if(!cb || !name)
					return;
				intercom.attachHandler(name, cb);
			}),
			exec: wrapInTryCatch(function(cb){
				execQueue.exec(cb);
			}),
			resetHost: function(){
				if(!!arguments[0]){ WAIT_SERVER = arguments[0]; }
			}
		};
	})();
})();
