	var JChat = Class.create({
	/**
	 *
	 */
	log: function (message) {
		//window.console.log(message);
	},

	/**
	 * 
	 */
	initialize: function(storage, observer, options) {

		// temp (Надо придумать куда перенести, что б можно было юзать переводы)
		Date.prototype.getMonthName = function() {
		
		var m = ['January','February','March','April','May','June','July', 'August','September','October','November','December'];
		return m[this.getMonth()];
		}
		
		Date.prototype.getDayName = function() {
			var d = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
			return d[this.getDay()];
		}
		// temp
		
		// даем возможность нашему выстреливать и слушать события
		this.observer = observer;
		
		this.storage = storage;
		
		this.activeChat = null;
		this.my_jabber_id = null;
		this.connect_timeout = 8000;
		this.jabber_connection = null;
		this.publicGroupChatName = null;
		this.idleStopped = false;
		this.lastActivityTime = null;
		this.preparedAvatars = false;

		this._loadedHistory = {};
		this._historyTmp = {};

		this.changePasswordUrl = options['changePasswordUrl'] || '';
		// проверяет на предмет изменений данных (для мульти-табов)
		new PeriodicalExecuter(this.onIdle.bind(this), 0.5);
		//this.onIdle();
		
		/********			окно чата				********/
		// событие выйти из активного чата
		this.observer.on('chatWindow.closeActiveChat', this.leaveActiveChat.bind(this));
		
		// слушаем событие отправки сообщение из окна чата
		this.observer.on('chatWindow.sendMessage', this.onChatWindowMessage.bind(this));
		
		
		/********			чат-лист				********/
		// событие выйти из конкретного чата
		this.observer.on('chatList.removeChatFromList', this.leaveActiveChat.bind(this));
		
		// слушаем события по активации какого-то конкретного чата
		this.observer.on('chatList.activateChat', this.onActivateChat.bind(this));
		
		// обзервим добавление чата в chatList
		this.observer.on('chatList.pushPublicGroupChatToList', this.onPushPublicGroupChatToList.bind(this));
		
		// опрделеить display name для чата
		this.observer.on('chatWindow.triggerSetWindowTitle', function(chatFullName) {
//			this.log(chatFullName)
			var data = {
				'chatName': this.getJidText(chatFullName),
				'avatar': this.getAvatarFromJid(chatFullName)
			};
			this.observer.fire('chatWindow.setWindowTitle', data);
		}.bind(this));
		
		/********			нотификейшн бар			********/
		this.observer.on('notificationBar.toggleChatWindow', this.onNotifBarToggleChatWindow.bind(this));
		
		
		/********			групповые чаты			********/
		// события отправки сообщения из общего (группового) чата
		this.observer.on('publicChat.sendMessage', this.onPublicChatMessage.bind(this));
		
		// событие по активации публичного группового чата
		this.observer.on('publicChat.setPublicGroupChat', function(name) {
			this.publicGroupChatName = name;
		}.bind(this));

		// событие по приглашению пользователя в чат
		this.observer.on('inviteToChat.invite', this.inviteToChat.bind(this));
		
		// записать настройки звуков чата 
		this.observer.on('sounds.saveSettings', function(data) {
			this.storage.set('chat_sounds_settings', data);	
		}.bind(this));

		// загрузить аватар публичного чата
		this.observer.on('publicChat.loadIcon', this.loadPublicChatIcon.bind(this));

		// загрузка истории
		this.observer.on('history.load', function(data){
			var chat_name = data['chat_name'] || this.activeChat;
			var hours = data['hours'];
			if (hours > 0) {
				var date = new Date();
				date.setHours(parseInt(date.getHours())-hours);
			} else {
				var date = false;
			}
			this.getMessageArchiveCollectionsList(chat_name, date);
		}.bind(this));

		// смена пароля
		this.observer.on('settings.changePassword', this.changePassword.bind(this));
		this.observer.on('publicChat.testSyncHistory', function (data){
			this.observer.fire('history.load', {'hours':0, 'chat_name': data['publicChatName']});
			this.storage.set(data['publicChatName']+'_last_message_time', new Date().getTime());
		}.bind(this));

		// Contact list updater
		new PeriodicalExecuter(function() {
			new Ajax.Request('/jabber/contacts', {
				onComplete: function(transport) {
					$('chat_contacts_container').innerHTML = transport.responseText;

					$A($('chat_contacts_container').getElementsByClassName('chat-start-link')).each(function(el) {
						el.observe('click', function(e) {
							e.preventDefault();
							this.observer.fire('chatList.activateChatWindow', el.previous('input').getValue());
						}.bind(this));
					}.bind(this));

					this.triggetSyncOnlineUsers();
				}.bind(this)
			});
		}.bind(this), 5);
	},
	
	changePassword: function (new_password) {
		var iq = $iq({'type':'set', 'jabber.kigol.com': 'change1'}).
			c('query',{'xmlns':'jabber:iq:register'}).
			c('username').t(Strophe.getNodeFromJid(this.my_jabber_id)).up().
			c('password').t(new_password);
			
		this.jabber_connection.send(iq.tree());
		
		new Ajax.Request(this.changePasswordUrl , {
			method: 'post',
			parameters: {'password': new_password},
			onSuccess: function (r) {
				$('change_password_container').toggle();
			}
		});
	},
	
	loadPublicChatIcon: function (src) {
		var st = this.storage.get('avatars', {});
		st[this.publicGroupChatName] = {'src': src};
		this.storage.set('avatars', st);

		this.triggerSyncChatList();
	},

	inviteToChat: function (jid) {
		if (this.isGroupChat(this.activeChat)) {	
			var uti = this.storage.get('users_to_invite', []);
			uti.push({
				'jid': jid,
				'room': this.activeChat
			});
			this.storage.set('users_to_invite', uti);
		} else {
			var uniq_room_name = hex_md5(this.my_jabber_id + new Date().getTime());	
			/*
			 *  Создать новую секретную комнату,
			 *  пригласить юзера с которым уже идет беседа
			 *  и того который указан в jid
			 */
			this.pushToPermanentRooms(uniq_room_name+'@conference.jabber.kigol.com');
			this.pushChatToChatList(uniq_room_name+'@conference.jabber.kigol.com');

			(function () {
				var uti = this.storage.get('users_to_invite', []);
				uti.push({
					'jid': this.activeChat,
					'room': uniq_room_name+'@conference.jabber.kigol.com'
				});
				uti.push({
					'jid': jid,
					'room': uniq_room_name+'@conference.jabber.kigol.com'
				});
				this.storage.set('users_to_invite', uti);
			}.bind(this)).delay(1);			
		}
	},

	sendStoredInvetes: function () {
		if (this.jabber_connection) {
			var u = this.storage.get('users_to_invite', []);
			u.each(function (i){
				this.sendInvite(i.jid, i.room);
			}.bind(this));
			if (u.length > 0)
				this.storage.remove('users_to_invite');
		}
	},
	
	onActivateChat: function(chat) {
		this.pushChatToChatList(chat);
		this.setActiveChat(chat);
		this.triggerSyncActiveChat(1);
	},
	
	/**
	 * сменит активный чат
	 */
	setActiveChat: function (chatName) {
		if (this.activeChat != chatName) {
			this.activeChat = chatName;
			this.storage.set('active_chat', this.activeChat);
		}
		
		// уменьшим (если нужно) на единицу число чатов с новыми сообщениями
		var k = 'chats_with_new_messages';
		var cc = this.storage.get(k, []);
		//var n = Strophe.getNodeFromJid(chatName);
		var n = chatName;
		if (cc.indexOf(n) != -1) {
			cc = cc.without(n);
			this.storage.set(k, cc);
			this.triggerUpdateChatsWithNewMessages(cc);
		}
		
		// обнулим число сообщений в данном чате
		var k = 'chats_new_messages';
		var n = Strophe.getNodeFromJid(chatName);
		var cnm = $H(this.storage.get(k, {}));
		cnm.set(n , 0);
		this.storage.set(k, cnm);
		// и 
		this.triggerUpdateChatsNewMessages();

		if (!this.isGroupChat(chatName)) {
			var st = $H(this.storage.get('display_names', {}));
			if (st.get(chatName) == undefined) {
				// gets "from" vCard
				var iq = $iq({
					'from': this.my_jabber_id,
					'id': 'v3',
					'to': chatName, 'type': 'get'
				}).c('vCard', {'xmlns': 'vcard-temp'});
				this.jabber_connection.send(iq.tree());
			}
		}

	},

	/**
	 *	Синкает онлайн юзеров.
	 **/
	triggetSyncOnlineUsers: function () {
		this.observer.fire('contacts.syncOnline', this.storage.get('online_users', []));
	},

	/**
	 * засинкает историю активного чата
	 */
	triggerSyncActiveChat: function (forceSync) {
		/*
		 * Нужно синкать только если реально есть новые сообщения,
		 * Иначе сториджу приходится туго.
		 */
		if (this.activeChat) {
			this.prepareAvatars();
			// сформируем итоговую историю сообщений
			h = [];
			this.storage.get('history_' + this.activeChat, []).each(function(item) {
				if (item.from) {
				  from = item.from.stripScripts().stripTags();
				  var avatar = this.getAvatarFromJid(from);
				  //this.log(from+' '+avatar);
				  /*if (! this.isGroupChat(this.activeChat)) {
					  from = this.getDisplayNameFromJid(from);
				  }*/
				  from = this.getDisplayNameFromJid(from);
				  h.push({'from': from, 'time': item.datetime, 'body': item.body, 'avatar':avatar});
				}
			}.bind(this));
			
			// обновляем список сообщений
			this.observer.fire('chatWindow.syncChatWindowHistory', {
				't': this.storage.get('chat_history_updated_at', 0),
				'forceSync': forceSync,
				'history': h
			});
			
			// события о смене названия окна чата
			this.observer.fire('chatWindow.triggerSetWindowTitle', this.activeChat);
			
			if (this.isGroupChat(this.activeChat)) {
				// событие о синке чат-листа
				this.observer.fire('chatWindow.syncGroupChatUsers', {
					'forceSync': forceSync,
					'users': $H(this.storage.get('room_users_' + this.activeChat)) || [],
					't': this.storage.get('room_users_updated_at_' + this.activeChat) || 0
				});
			}
			else {
				// чат негрупповой, следовательно прячем список юзеров чата (т.к. юзер будет всего 1)
				this.observer.fire('chatWindow.hideChatList');
			}

			this.clearPreparedAvatars();
		}
	},

	pushToPermanentRooms: function (room_name) {
		var permanentRooms = $H(this.storage.get('permanent_rooms', {}));
		permanentRooms.set(room_name, new Date().getTime());
		this.storage.set('permanent_rooms', permanentRooms);
//		this.log(permanentRooms);
	},

	onPushPublicGroupChatToList: function() {
		if (this.publicGroupChatName) {
			this.pushChatToChatList(this.publicGroupChatName);
			this.triggerSyncChatList();
			/**
			 * Добавить в permanent_rooms
			 */
			this.pushToPermanentRooms(this.publicGroupChatName);
		}
	},
	
	pushChatToChatList: function(chatName) {
		var chatList = this.storage.get('chat_list', []);
		if (chatList.indexOf(chatName) < 0) {
			chatList.push(chatName);
			this.storage.set('chat_list_updated_at', new Date().getTime());
			this.storage.set('chat_list', chatList);
			this.checkEmptyHistory(chatName);
		}
	},
	
	/*
	 *  если история чата была в сторидже и затерлась, или юзер поменял бровзер
	 *  нужно загрузить последний разговор с сервера
	 */
	checkEmptyHistory: function (chatName) {
		var history = this.storage.get('history_'+chatName, []);
		if (history.length < 1) {
			var h = this.storage.get('history_to_load_last', []);
			if (h.indexOf(chatName) < 0) {
				h.push(chatName);
				this.storage.set('history_to_load_last', h);
			}
			this.log('Try to load history from server');
		}
	},

	
	/**
	 * 
	 */
	pushMessageToHistory: function(chat_name, from, body, date) {
		var chat = this.storage.get('history_'+chat_name, []);
		var date = date || new Date().getTime();
		chat.push({'from': from, 'body': body, 'datetime': date});
		this.storage.set('history_'+chat_name, chat);
		//this.log(chat);
		this.storage.set('chat_history_updated_at', new Date().getTime());
	},

	/**
	 *
	 */
	onIq: function (iq) {
		if (((iq.getAttribute('id') == 'v3') || (iq.getAttribute('id') == 'v1') )&& (iq.getAttribute('type') == 'result')) {
			var name = Strophe.getBareJidFromJid(iq.getAttribute('from'));
			var fn = iq.getElementsByTagName('FN');

			if (Strophe.getText(fn[0]) != "") {
				// Сохраним все это дело в Storage;
				var st = this.storage.get('display_names', {});
				st[name] = Strophe.getText(fn[0]);
				this.storage.set('display_names', st);
			}

			// avatars
			var photo = iq.getElementsByTagName('PHOTO');
			if (photo.length > 0) {
				var type = photo[0].getElementsByTagName('TYPE'),
					binval = photo[0].getElementsByTagName('BINVAL');
				if (type.length > 0 && binval.length > 0) {
					type = Strophe.getText(type[0]),
					binval = Strophe.getText(binval[0]);

					var st = this.storage.get('avatars', {});
					st[name] = {'type': type, 'binval': binval};
					this.storage.set('avatars', st);
					//this.log(st);
				}
			}

		}


		/**
		 * Retrieve message archive collection items here
		 * 
		 **/
		
		var list = iq.getElementsByTagName('list');
		if ((list.length > 0) && (list[0].getAttribute('xmlns') == 'http://www.xmpp.org/extensions/xep-0136.html#ns')) {
			var chats = list[0].getElementsByTagName('chat');
			if (chats.length > 0) {
				var i = 0;
				while (chats[i]) {
					//this.log(chats[i])
					/** Вытянуть инфу по каждому из чатов
					 *
					 *  FIX ME - максимум сообщений - 100 ? (в одной коллекции xep-0136)
					 **/
					 
					var iq_item = $iq({'type':'get', 'id': 'page1'}).
						c('retrieve', {
								'xmlns': 'http://www.xmpp.org/extensions/xep-0136.html#ns',
								'with': chats[i].getAttribute('with'),
								'start': chats[i].getAttribute('start')
							}
						).
						c('set', {'xmlns': 'http://jabber.org/protocol/rsm'}).
						c('max').t('100')
					
					/*
					 * Нужно оставить какую-то метку, о том, что мы должны загрузить
					 * 
					 **/
					
				    var _jid = Strophe.getBareJidFromJid(chats[i].getAttribute('with'));
					if (this._loadedHistory[_jid] == undefined) {
						this._loadedHistory[_jid] = {}
					}
					this._historyTmp[_jid] = [];
					
					this._loadedHistory[_jid][chats[i].getAttribute('start')] = true;
					//this.log(this._loadedHistory)				    
					this.jabber_connection.send(iq_item.tree());
					i++;
				}
			}
		}

		/**
		 *
		 **/
		
		var chat_item = iq.getElementsByTagName('chat');

		if ((chat_item.length > 0) && (chat_item[0].getAttribute('with'))) {
			// Достать from и to айтемы, а из них потом и body
			var start = chat_item[0].getAttribute('start');
			var start_date = new Date();
			start_date.setFullYear(start.substr(0,4), parseInt(start.substr(5,2))-1, start.substr(8,2));
			start_date.setHours(start.substr(11,2), start.substr(14,2), start.substr(17,2));

			var _jid = Strophe.getBareJidFromJid(chat_item[0].getAttribute('with'));
			
			delete this._loadedHistory[_jid][start];
			
			
			
			this.log(this._loadedHistory);

			/**
			 * Нужно сформировать новую историю - и,
			 * после окончания синка - заменить ее в сторидже
			 **/
			
			var chat_item_from = chat_item[0].getElementsByTagName('from');
			if (chat_item_from.length > 0) {
				var i = 0;
				while (chat_item_from[i]) {
					if ((this.isGroupChat(_jid) && chat_item_from[i].getAttribute('name')) || ((!this.isGroupChat(_jid)))) {

						var secs = chat_item_from[i].getAttribute('secs');

						var body_item = chat_item_from[i].getElementsByTagName('body');
						if (body_item.length > 0) {
							var body_text = Strophe.getText(body_item[0]);
							if (this.isGroupChat(_jid)) {
								var gr_jid = this.getRealJidFromNickName(_jid, chat_item_from[i].getAttribute('name'));
								if (gr_jid == this.getDisplayNameFromJid(this.my_jabber_id)) {
									gr_jid = this.my_jabber_id;
								}
								this._historyTmp[_jid].push({'from':gr_jid, 'body': body_text, 'datetime': parseInt(start_date.getTime())+parseInt(secs)*1000});
							} else {
								this._historyTmp[_jid].push({'from':_jid, 'body': body_text, 'datetime': parseInt(start_date.getTime())+parseInt(secs)*1000});
							}
						}
					}
					i++;
				}
			}

			var chat_item_to = chat_item[0].getElementsByTagName('to');
			if (chat_item_to.length > 0) {
				var i = 0;
				while (chat_item_to[i]) {
					if ((this.isGroupChat(_jid) && chat_item_from[i].getAttribute('name')) || ((!this.isGroupChat(_jid)))) {

						var secs = chat_item_to[i].getAttribute('secs');

						var body_item = chat_item_to[i].getElementsByTagName('body');
						if (body_item.length > 0) {
							var body_text = Strophe.getText(body_item[0]);
							if (this.isGroupChat(_jid)) {
								var gr_jid = this.getRealJidFromNickName(_jid, chat_item_to[i].getAttribute('name'));
								if (gr_jid == this.getDisplayNameFromJid(this.my_jabber_id)) {
									gr_jid = this.my_jabber_id;
								}
								this._historyTmp[_jid].push({'from':gr_jid, 'body': body_text, 'datetime': parseInt(start_date.getTime())+parseInt(secs)*1000});
							} else {
								this._historyTmp[_jid].push({'from':this.my_jabber_id, 'body': body_text, 'datetime': parseInt(start_date.getTime())+parseInt(secs)*1000});
							}
						}
					}
					i++;
				}
			}


			if ($H(this._loadedHistory[_jid]).size() == 0) {
				this.log('History loaded!');
				// sort history
				this.log(this._historyTmp[_jid]);
				var h = this._historyTmp[_jid].sort(this.sortHistoryCallback.bind(this));
				this.storage.set('history_'+_jid, h);
				this.storage.set('chat_history_updated_at', new Date().getTime());
				this.storage.set(_jid+'_last_message_time', new Date().getTime());
			}
			

		}
		
		return true;
	},

	sortHistoryCallback: function (a, b) {
		return a.datetime - b.datetime;
	},

	prepareAvatars: function () {
		this.preparedAvatars = $H(this.storage.get('avatars', {}));
	},

	clearPreparedAvatars: function () {
		this.preparedAvatars = false;
	},

	getAvatarFromJid: function (jid) {
		if (this.preparedAvatars) {
			var st = this.preparedAvatars;
		} else {
			var st = $H(this.storage.get('avatars', {}));
		}
		var avatar = st.get(jid);
		if (avatar != undefined) {
			if (avatar.src) {
				return avatar.src;
			} else {
				return 'data:'+avatar.type+';base64,'+avatar.binval;
			}
		}else {
			if (this.isGroupChat(jid)) {
				return '/i/activ_icos/12.png';
			} else {
				return '/i/16x16.gif';
			}
		}
	},

	getRoomSubject: function(room) {
		var rs = $H(this.storage.get('rooms_subjects', {}));
		//this.log(rs);
		var subject = rs.get(room);
		if (subject == undefined) {
			return Strophe.getNodeFromJid(room);
		}else {
			return subject;
		}
	},

	getDisplayNameFromJid: function (jid) {
		var st = $H(this.storage.get('display_names', {}));
		//this.log(st);
		var name = st.get(jid);
		if (name == undefined) {
			return Strophe.getNodeFromJid(jid);
		}else {
			return name;
		}
	},

	getRealJidFromNickName: function(room, nickname) {
		var ul = $H(this.storage.get('room_users_' + room, {}));
		ul.each(function(item){
			if (item[1] == nickname) {
				nickname = item[0];
			}
		}.bind(this));
		
		return nickname;
	},
	
	/**
	 *
	 */
	onPresence: function (presence) {
		var from = presence.getAttribute('from'), t = new Date().getTime();
		if (presence.getAttribute('type') == 'subscribe') {
			var p = $pres({'type':'subscribed', 'to': Strophe.getBareJidFromJid(from)});
			this.jabber_connection.send(p.tree());
		}

		if (this.isGroupChat(from)) {

			var items = presence.getElementsByTagName('item');
			
			if (items.length > 0) {
				var item = items[0], jid = item.getAttribute('jid');
				
				if (jid) {
					jid = Strophe.getBareJidFromJid(jid);
					
					if (jid != this.my_jabber_id) {					
						var nick = Strophe.getResourceFromJid(from),
							type = presence.getAttribute('type'),
							chat_name = Strophe.getBareJidFromJid(from),
							ul = $H(this.storage.get('room_users_' + chat_name, {}));
							
						if (type == 'unavailable') {
							ul.unset(jid);
						} else {
							ul.set(jid, nick);
						}
						
						this.storage.set('room_users_' + chat_name, ul);

						this.storage.set('room_users_updated_at_' + chat_name, t);
			
						this.triggerUpdatePublicGroupChatUsers();
					}
				}
			}
		} else {
			// Здесь будут определяться статусы ростеров
			/*
			<presence xmlns='jabber:client'
			from='koshak@jabber.kigol.com/Gajim1'
			to='egorbacardi@jabber.kigol.com/kigol'
			xml:lang='en' id='86'>
			*/
		   if (presence.getAttribute('xmlns') == 'jabber:client') {
				var ou = this.storage.get('online_users', []);
				var jid = Strophe.getBareJidFromJid(presence.getAttribute('from'));
				if (presence.getAttribute('type') && presence.getAttribute('type')=='unavailable') {
				   var ind = ou.indexOf(jid);
				   if (ind != -1) {
					   delete ou[ind];
				   }
				} else {
					ou.push(jid);
				}
				//this.log(ou);
				ou = ou.uniq();
				this.storage.set('online_users', ou);
		   }
		   
		}
		return true;
	},

	sendInvite: function (jid, room) {
		
		/* Invite to room XML samle
		<message
			from='koshak@jabber.kigol.com'
			to='club78@conference.jabber.kigol.com'>
			<x xmlns='http://jabber.org/protocol/muc#user'>
				<invite to='egorbacardi@jabber.kigol.com'>
					<reason>
						Havanagila!
					</reason>
				</invite>
			</x>
		</message>
		*/
	   var me = this.my_jabber_id;
	   
	   var message = $msg({
			'from': me,
			'to': room
	   }).c('x', {
		   'xmlns': 'http://jabber.org/protocol/muc#user'
	   }).c('invite', {
		   'to':jid
	   }).c('reason').t('Invite to '+room);

	   this.jabber_connection.send(message.tree());
	},
	
	storeRoomSubject: function(room, subject) {
		var rs = this.storage.get('rooms_subjects', {});
		rs[room] = subject;
		this.storage.set('rooms_subjects', rs);
		//this.log(rs);
	},

	getThreadId: function (jid) {
		var th = $H(this.storage.get('threads', {}));
		if (th.get(jid) != undefined) {
			// get's ThreadID
			return th.get(jid);
		} else {
			// generate and save new ThreadID
			var thread_id = hex_md5(this.my_jabber_id + new Date().getTime());
			th.set(jid, thread_id);
			this.storage.set('threads', th);
			return thread_id;
		}
	},

	saveThreadID: function (jid, threadId) {
		var th = $H(this.storage.get('threads', {}));
		th.set(jid, threadId);
		this.storage.set('threads', th);
	},

	/**
	 * 
	 */
	onMessage: function(msg) {

		with (msg) {
			var to = getAttribute('to'),
				from = getAttribute('from'),
				type = getAttribute('type'),
				elems = getElementsByTagName('body'),
				invite = getElementsByTagName('invite'),
				subject = getElementsByTagName('subject'),
				thread = getElementsByTagName('thread')
		}

		if (thread.length > 0) {
			this.saveThreadID(Strophe.getBareJidFromJid(from), Strophe.getText(thread[0]));
			this.log(Strophe.getText(thread[0]));
		}

		if (subject.length > 0) {
			this.storeRoomSubject(Strophe.getBareJidFromJid(from), Strophe.getText(subject[0]));
		}

		if ((invite.length > 0) && (type='normal')) {
			invite = invite[0];
			/*
				msg.from - room jid
				invite.from - invitee user jid
			*/
		   var room_jid				= from;
		   var invitee_user_jid		= Strophe.getBareJidFromJid(invite.getAttribute('from'));
		   
		   //this.log('invite from '+room_jid+' user - '+invitee_user_jid);

		   /**
			* Push to persistent rooms and chat list
			*/
		   this.pushChatToChatList(room_jid);
		   this.pushToPermanentRooms(room_jid);
		}

		/*
		 *  TODO:
		 *  Узнать, не "задержанное" ли это сообщение, т.е. отправленное нам,
		 *  когда мы были offline. Если так - то нужно записать дату реальной
		 *  отправки сообщения. (Атрибут stamp у элемента delay)
		 */

		var di = msg.getElementsByTagName('x');
		var delayedMessage = false;
		var messageTime = new Date();
		if (di.length > 0) {
			var delay = di[0];
			if (delay.getAttribute('xmlns')=='jabber:x:delay') {
				var d = delay.getAttribute('stamp');
				messageTime.setYear(d.substr(0,4));
				messageTime.setMonth(d.substr(4,2), d.substr(6,2));
				messageTime.setHours(d.substr(9,2), d.substr(12,2), d.substr(15,2));
//					this.log(d);
				delayedMessage = true;
			}
		}

		if (((type == "chat") || type == "groupchat") && elems.length > 0) {
			
			var body = elems[0];
			if (type == "groupchat") {
				var from_text = Strophe.getResourceFromJid(from);
				
				if (!delayedMessage && from_text && (from_text != this.getDisplayNameFromJid(this.my_jabber_id))) {
					this.observer.fire('sounds.onMessage');
					this.log('sounds.onMessage');
				}
				// update last sync history time
				var chat_name = Strophe.getBareJidFromJid(from);
				this.storage.set(chat_name+'_last_message_time', new Date().getTime());
			} else {
				var from_text = Strophe.getBareJidFromJid(from);
			}
			if (type == "chat") {
				this.pushChatToChatList(Strophe.getBareJidFromJid(from));
				this.observer.fire('sounds.onMessage');
				this.log('sounds.onMessage');
			}

			
			/*
			 * ******************************************************************
			 */
			
			if ((type != "groupchat" ) || delayedMessage || from_text != this.getDisplayNameFromJid(this.my_jabber_id)) {
				//this.log('tets');
				if (type == 'groupchat') {
					// get's real jid for nickname
					from_text = this.getRealJidFromNickName(Strophe.getBareJidFromJid(from), from_text);
					if (from_text == this.getDisplayNameFromJid(this.my_jabber_id)) {
						from_text = this.my_jabber_id;
					}
					//this.log(from_text);
				}

				this.pushMessageToHistory(
					Strophe.getBareJidFromJid(from),
					from_text,
					Strophe.getText(body),
					messageTime.getTime()
				);
			}

			var st = $H(this.storage.get('display_names', {}));
			
			if (type=="groupchat") {
				var v_from = from_text;
			} else {
				var v_from = from;
			}
			
			if (st.get(from) == undefined) {
				// gets "from" vCard
				var iq = $iq({
					'from': this.my_jabber_id,
					'id': 'v3',
					'to': v_from, 'type': 'get'
				}).c('vCard', {'xmlns': 'vcard-temp'});
				this.jabber_connection.send(iq.tree());
			}
		}
		
		var chatName = Strophe.getBareJidFromJid(from);
		var a = $('chat_window').offsetHeight,
			b = this.activeChat ? Strophe.getNodeFromJid(this.activeChat) : '',
			c = Strophe.getNodeFromJid(from),
			d = this.storage.get('chat_list', []).indexOf(chatName) != -1;// чат есть в списке активных чатов (фикс для групповых)
			
			var service_message = false;
			if (this.isGroupChat(from) && !Strophe.getResourceFromJid(from)) {
				service_message = true;
			}
			if (delayedMessage) {
				service_message = true;
			}
			//this.log(from);
			//this.log(service_message);
			
		if (!service_message && (a == 0 || b != c) && d) { // если чат закрыт или активный чат не совпадает с from и не служебное сообщение
			var cc = this.storage.get('chats_with_new_messages', []),
				cnm = $H(this.storage.get('chats_new_messages', {})); // новые сообщения в конкретном чате
			
			cnm.set(c, cnm.get(c) ? cnm.get(c) + 1 : 1);
			this.storage.set('chats_new_messages', cnm);
			
			this.triggerUpdateChatsNewMessages();
			
			if (cc.indexOf(chatName) == -1) {
				cc.push(chatName);
				this.storage.set('chats_with_new_messages', cc);
				this.triggerUpdateChatsWithNewMessages(cc);
			}
		}

		return true;
	},
	
	
	/**
	 * число новых сообщений в каждом чате
	 */
	triggerUpdateChatsNewMessages: function() {
		this.observer.fire('chatList.updateMessagesCount', $H(this.storage.get('chats_new_messages', {})));
	},
	
	/**
	 * число чатов с новыми сообщенями
	 */
	triggerUpdateChatsWithNewMessages: function(cc) {
		if (! cc) {
			var cc = this.storage.get('chats_with_new_messages', []);
		}
		this.observer.fire('notificationBar.updateCounter', cc);
	},
	
	/**
	 *
	 */
	onConnect: function() {

	},

	/**
	 *
	 */

	/**
	 *
	 */
	onIdle: function (pe) {
		if (this.idleStopped) {
			return;
		}
		var timeStamp = new Date().getTime();
		
		if (this.storage.get('idle_stopped', 0)) {
			if (timeStamp - this.storage.get('idle_stopped') > this.connect_timeout) {
				this.storage.set('idle_stopped', 0);
			} else {
				//this.log('Idle stopped');
				return;
			}
		}

		this.my_jabber_id = this.storage.get('my_jabber_id', null);
		// TODO: вынести в отдельную функцию
		if (timeStamp - this.storage.get('strope_idle_timestamp', 0) > this.connect_timeout) {
			// connect to chat
			this.storage.set('idle_stopped', timeStamp);
		
			new Ajax.Request('/jabber/connectToChat', {
				onComplete: function(response) {
					var r = response.responseJSON;
					this.attach(r.jid, r.sid, r.rid);
					this.my_jabber_id = r.jid;
					this.storage.set('my_jabber_id', this.my_jabber_id);
					this.storage.set('idle_stopped',0);
				}.bind(this)
			});
		}

		// синкаем чат-лист
		this.triggerSyncChatList();
		
		// синкаем текущий активный чат
		this.triggerSyncActiveChat();

		// отправим сообщения из storage
		this.sendStoredMessages();

		// отправим инвайты
		this.sendStoredInvetes();

		this.enterToPublicChats();

		this.enterToPermanentRooms();
		// если групповой чат
		if (this.publicGroupChatName) {
			
			this.syncPublicChatToEnter();

			//this.enterToPublicChats();
	
			this.triggerSyncPublicChatHistory();
			
			// обновим список пользователей группового чата
			this.triggerUpdatePublicGroupChatUsers();
		}
		
		// число чатов с новыми сообщениями		
		this.triggerUpdateChatsWithNewMessages();
		
		// число новых сообщений в чатах, в которых учавствует пользователь
		this.triggerUpdateChatsNewMessages();
		
		this.triggerSyncChatSoundsSettings();

//		this.triggetSyncOnlineUsers();

		this.triggerGetLastMessages();
	},

	triggerGetLastMessages: function () {
		if (this.jabber_connection && this.my_jabber_id) {
			var h = this.storage.get('history_to_load_last', []);
			if (h.length > 0) {
				h.each(function(item){
					this.log(item);
					var list_params = {'xmlns':'http://www.xmpp.org/extensions/xep-0136.html#ns', 'with': item};
					var iq = $iq({'type': 'get', 'id': 'wrongId'}).
					c('list', list_params).
					c('set', {'xmlns':'http://jabber.org/protocol/rsm'}).c('max').t('1').up().c('before');

					this.jabber_connection.send(iq.tree());
				}.bind(this));
				this.storage.remove('history_to_load_last');
			}
		}
	},

	triggerSyncChatSoundsSettings: function() {
		this.observer.fire('sounds.syncSettings', this.storage.get('chat_sounds_settings'));
	},

	triggerUpdatePublicGroupChatUsers: function() {
		this.observer.fire('publicChat.updatePublicChatUsersList', {
			'time': this.storage.get('room_users_updated_at_' + this.publicGroupChatName, 0),
			'users': this.storage.get('room_users_' + this.publicGroupChatName, {})
		});
	},

	getJidText: function (jid) {
		if (this.isGroupChat(jid)) {
			return this.getRoomSubject(jid);
		} else {
			return this.getDisplayNameFromJid(jid);
		}
	},

	triggerSyncChatList: function() {
		// сообщаем о том, что нужно обновить контейнер с чат-листом.
		// нужный слушатель услышит и сделает все что нужно
		var chat_list = this.storage.get('chat_list', []);
		var display_names = {};
		var avatars = {};
		this.prepareAvatars();
		
		chat_list.each(function(item){
			display_names[item] = this.getJidText(item);
			avatars[item] = this.getAvatarFromJid(item);
		}.bind(this));

		this.clearPreparedAvatars();
		
		this.observer.fire(
			'chatList.resyncList', {
				'time': this.storage.get('chat_list_updated_at', 0), // время последнего обновления
				'cl': chat_list, // чат-лист
				'display_names': $H(display_names),
				'avatars': avatars
			}
		);
	},

	/**
	 * внесем текущий публичный открытый чат
	 * в список комнат, в storage
	 */
	syncPublicChatToEnter: function () {
		/**
		 * TODO: Оптимизировать. Потому что set сториджа делается слишком часто.
		 **/

		if (this.publicGroupChatName) {
			var rooms = $H(this.storage.get('rooms_to_enter', {}));
			var t = new Date().getTime();
			var st = rooms.get(this.publicGroupChatName);
			if ((st == undefined) || (t - st > 2000)) {
				rooms.set(this.publicGroupChatName, t);
				this.storage.set('rooms_to_enter', rooms);
			}

			//this.log(rooms);
		}
	},
	
	enterToPermanentRooms: function () {
		if (this.jabber_connection) {
			var permanentRooms = $H(this.storage.get('permanent_rooms', {})),
				enteredRooms = this.storage.get('entered_rooms', []);
			permanentRooms.each(function(item){
				//this.log(item.key + '='+item.value);

				if (enteredRooms.indexOf(item.key) == -1) {
					var p = $pres({
						'from': this.my_jabber_id,
						'to': item.key + '/' + this.getDisplayNameFromJid(Strophe.getBareJidFromJid(this.my_jabber_id))
					});

					this.jabber_connection.send(p.tree());

					// clear history
					this.storage.remove('history_' + item.key);

					// clear users
					this.storage.remove('room_users_' + item.key);

					enteredRooms.push(item.key);

					this.storage.set('entered_rooms', enteredRooms);
				}

			}.bind(this));
		}
	},
	
	enterToPublicChats: function () {
		if (this.jabber_connection) {
			

			var enteredRooms = this.storage.get('entered_rooms', []),
				roomsToEnter = $H(this.storage.get('rooms_to_enter', {})),
				roomsToEnterTmp = $H({});
				//this.log(roomsToEnter);
				//this.log(enteredRooms);
			roomsToEnter.each(function(item){
				if (new Date().getTime() - item.value > this.connect_timeout) {
					// TODO: Delete non active public room from "$rooms_to_enter"
					//this.log(item.key + ' need to exit');
					
					var p = $pres({
						'type': 'unavailable',
						'from': this.my_jabber_id,
						'to': item.key + '/' + this.getDisplayNameFromJid(Strophe.getBareJidFromJid(this.my_jabber_id))
					});
					this.jabber_connection.send(p.tree());

					enteredRooms = enteredRooms.without(item.key);
					this.storage.set('entered_rooms', enteredRooms);

					return;
				} else {
					roomsToEnterTmp.set(item.key, item.value);
				}
				if (enteredRooms.indexOf(item.key) == -1) {
					var p = $pres({
						'from': this.my_jabber_id,
						'to': item.key + '/' + this.getDisplayNameFromJid(Strophe.getBareJidFromJid(this.my_jabber_id))
					});
					
					this.jabber_connection.send(p.tree());
										
					// clear history
					this.storage.remove('history_' + item.key);
					
					// clear users
					this.storage.remove('room_users_' + item.key);

					enteredRooms.push(item.key);

					this.storage.set('entered_rooms', enteredRooms);
				}
			}.bind(this));

			//this.log(roomsToEnter.keys().length);
			//this.log(roomsToEnterTmp.keys().length);
			if (roomsToEnter.keys().length != roomsToEnterTmp.keys().length) {
				this.storage.set('rooms_to_enter', roomsToEnterTmp);
			}
			//var permanent_rooms = $H(this.storage.get('permatent_rooms', {}));
		}
	},

	triggerSyncPublicChatHistory: function() {
		var h = this.storage.get('history_' + this.publicGroupChatName, []);
		
		var display_names = {};
		var avatars = {};
		this.prepareAvatars();
		
		h.each(function(o){
			var item = o.from;
			if (item) { 
				display_names[item] = this.getJidText(item);
				avatars[item] = this.getAvatarFromJid(item);
			}
		}.bind(this));

		this.clearPreparedAvatars();
		
		this.observer.fire('publicChat.syncHistory', {
			'lastMessageTime': this.storage.get(this.publicGroupChatName + '_last_message_time', 0),
			'history': h,
			'avatars': avatars,
			'displayNames': display_names
		});
	},
	
	/**
	 * 
	 */
	attach: function(jid, sid, rid) {
		this.updateLastActivityTime();
		
		this.storage.set('entered_rooms', []);
		this.storage.set('online_users', []);

		this.jabber_connection = jc = new Strophe.Connection('/xmpp-httpbind');
		
		jc.attach(jid, sid, rid, this.onConnect.bind(this));
		jc.addHandler(this.onMessage.bind(this), null, 'message', null, null,  null);
		jc.addHandler(this.onIq.bind(this), null, 'iq', null, null, null);
		jc.addHandler(this.onPresence.bind(this), null, 'presence', null, null, null)
		jc.send($pres().tree());			
		
		var iq = $iq({
			'from': jid,
			'id': 'v1',
			'type': 'get'
		}).c('vCard', {'xmlns': 'vcard-temp'});
					
		jc.send(iq.tree());

		// subcribe to status
		$A($('chat_contacts_container').getElementsByClassName('chat-start-link')).each(function(el){
			var j = el.previous('input').getValue();
			var iq = $iq({
				'from': j,
				'type':'set',
				'id': 'roster_2'
			}).c('query', {
				'xmlns': 'jabber:iq:roster'
			}).c('item', {
				'jid': jid,
				'subscription': 'none'
			}).c('group').t('Kigol');
			//jc.send(iq.tree());
			var p = $pres({'from': jid, 'to': j, 'type': 'subscribe'})
			jc.send(p.tree());
		}.bind(this));

	},

	updateLastActivityTime: function () {
		var t = new Date().getTime();
	  // если метка старше 2 секунд
		if (t - this.lastActivityTime > 3000) {
//		this.log('updateLastActivityTime: ' + new Date().getTime());
			this.storage.set('strope_idle_timestamp', this.lastActivityTime = t);
		}
	},

	onPublicChatMessage: function(data) {
		this.storeToSend({
			'to': data.publicChatName,
			'body': data.message,
			'type': 'groupchat'
		});
	},

	isGroupChat: function(chatName) {
		return chatName.indexOf('@conference') > 0;
	},

	onChatWindowMessage: function (message) {
		this.storeToSend({
			'to': this.activeChat,
			'body': message,
			'type': this.isGroupChat(this.activeChat) ? 'groupchat' : 'chat'
		});
	},

	/*switchIdle: function () {
		this.idleStopped = !this.idleStopped;
	},*/
	

	/**
	 * Запоминает сообщения в GlobalStorage для
	 * последующиего отправления.
	 */
	storeToSend: function(message) {
		this.pushMessageToHistory(message.to, Strophe.getBareJidFromJid(this.my_jabber_id), message.body);
		this.triggerSyncActiveChat(true);
		var m = this.storage.get('messages_to_send', []);
		m.push(message);
		this.storage.set('messages_to_send', m);
	},

	/**
	 * Отсылает сообщения через jabber
	 */
	sendStoredMessages: function () {
		if (this.jabber_connection && this.my_jabber_id) {
			var m = this.storage.get('messages_to_send', []);
			if (m.length > 0) 
				this.storage.remove('messages_to_send');
			
			m.each(function(item) {
				var reply = $msg({
					'to': item.to,
					'from': this.my_jabber_id,
					'type': item.type
				}).c('body').t(item.body).up().c('thread').t(this.getThreadId(Strophe.getBareJidFromJid(item.to)));
				this.jabber_connection.send(reply.tree());
				//if (item.type != 'groupchat') {
				//	this.pushMessageToHistory(item.to, Strophe.getBareJidFromJid(this.my_jabber_id), item.body);
				//}
			}.bind(this));
		}
	},
	
	/**
	 * 
	 */
	getFeatures: function() {
		
	},
	
	/**
	 * 
	 */
	loadGroupChatHistory: function(room_name) {
		// clear local room history
		/*
		this.log(room_name+' pipi');
		var p = $pres({'from': this.my_jabber_id, 'to': room_name+'/'+this.getDisplayNameFromJid(Strophe.getBareJidFromJid(this.my_jabber_id))}).c('x', {'xmlns':'http://jabber.org/protocol/muc'}).c('history',{'since':'1970-01-01T00:00:00Z'});
		this.jabber_connection.send(p.tree());*/
		
	},
	
	leaveActiveChat: function(chatName) {
		if (! chatName) {
			chatName = this.activeChat;
		}
		
		// уберем из чат-листа
		var chatList = this.storage.get('chat_list', []);
		chatList = chatList.without(chatName);
		this.storage.set('chat_list', chatList);
		this.storage.set('chat_list_updated_at', new Date().getTime());

		// ресинкаем чат-лист
		this.triggerSyncChatList();
		
		// уберем из комнат
		var rooms = $H(this.storage.get('rooms_to_enter', {}));
		rooms.unset(chatName);
		this.storage.set('rooms_to_enter', rooms);
		
		// пересчитаем число чатов с новыми сообщениями
		cnm = this.storage.get('chats_with_new_messages', []);
		cnm = cnm.without(chatName);
		this.storage.set('chats_with_new_messages', cnm);
		this.triggerUpdateChatsWithNewMessages(cnm);
		
		if (chatList.size() > 0) {
			this.onActivateChat(chatList.first());
		}
		else { // юольше чатов нет
			// закрываем окно с чатом
			this.storage.set('active_chat', this.activeChat = null);
			this.observer.fire('chatWindow.toggleChatWindow');
			this.observer.fire('chatWindow.clearMessageContainer');
		}
	},
	
	/**
	 * либо покажем активный чат
	 * либо последний 
	 */
	onNotifBarToggleChatWindow: function() {
		if (! this.activeChat) {
			var chatName = this.storage.get('active_chat', '');
			if (chatName) {
				return this.observer.fire('chatList.activateChatWindow', chatName);
			}
			else {
				// получим список чатов
				chatList = this.storage.get('chat_list', []);
				if (chatList.size() > 0) {
					//TODO: отобразить чат, в котром самое "свежее" сообщение.
					return this.observer.fire('chatList.activateChatWindow', chatList.first());
				}
				else {
					this.observer.fire('chatWindow.toggleChatWindow');
					// пульнем событие бару о том, что чатов нет
					return this.observer.fire('notificationBar.chatListIsEmpty');
				}
			}
		}
		else {
			this.observer.fire('chatWindow.toggleChatWindow');
			this.setActiveChat(this.activeChat);
		}
	},

	getMessageArchiveCollectionsList: function (jid, start_date) {
		/**
		 * FIX ME (Какой должен быть id ???
		 */
		//this.log(start_date);
		var list_params = {'xmlns':'http://www.xmpp.org/extensions/xep-0136.html#ns', 'with': jid};

		if (start_date) {
			var date_str = start_date.getFullYear()+'-'+(start_date.getMonth()+1)+'-'+start_date.getDate();
			date_str +='T00:00:00.000000Z';
			list_params['start'] = date_str;
			this.log(date_str);
		}
		
		var iq = $iq({'type': 'get', 'id': 'wrongId'}).
		c('list', list_params).
		c('set', {'xmlns':'http://jabber.org/protocol/rsm'}).c('max').t('200');
		
		this.jabber_connection.send(iq.tree());
	}
});
