// 8< ---[chatclient.js]---

var chatclient=chatclient||{}
var self=chatclient
chatclient.VisitorConnector=extend.Class({
	// The visitor connector abstracts and connects to the VisitorServer web service.
	// The connector gives access to the web service methods and to the event bus
	// exposed by the web service.
	name:'chatclient.VisitorConnector', parent:undefined,
	shared:{
		DELAY:5000,
		RETRY_DELAY:5000,
		SINGLETON:undefined
	},
	properties:{
		initialData:undefined,
		channel:undefined,
		syncChannel:undefined,
		chatClient:undefined,
		bus:undefined
	},
	initialize:function(prefix){
		var self=this
		prefix = prefix === undefined ? '/api' : prefix
		self.initialData = {}
		self.channel = new channels.AsyncChannel({'prefix':prefix, 'timestamp':true});
		self.syncChannel = new channels.SyncChannel({'prefix':prefix, 'timestamp':true});
		self.bus = new eventbus.EventBus((prefix + '/visitor/bus/iterate'));
		self.getClass() .SINGLETON = self;
	},
	methods:{
		start:function(location, referer){
			var self=this
			self.initialData = self.syncChannel.post('/visitor/currentState', {'location':$.toJSON(location), 'referer':$.toJSON(referer)}).value();
			if ( self.initialData )
			{
				self.bus.dispatchEvent({'name':'init', 'data':self.initialData})
				self.bus.start(self.initialData.position)
			}
		},
		leave:function(){
			var self=this
			self.syncChannel.post('/visitor/leavePage')
		},
		// Ensure bus makes sur that the bus is open. When a visitor becomes INACTIVE
		// and eventually OFFLINE, we have to make sure that the bus is restarted
		// when the user initiates an action (like askForOperator or sendMessage).
		ensureBus:function(){
			var self=this
			if ( (! self.bus.isOpen) )
			{
				self.bus.start()
			}
		},
		setName:function(name){
			var self=this
			self.channel.post('/visitor/setName', {'name':name})
		},
		ping:function(){
			var self=this
			self.channel.get('/visitor/ping')
		},
		askForOperator:function(){
			var self=this
			self.ensureBus()
			self.channel.post('/visitor/askForOperator')
		},
		acceptInvitation:function(){
			var self=this
			self.channel.post('/visitor/acceptInvitation')
		},
		declineInvitation:function(){
			var self=this
			self.channel.post('/visitor/declineInvitation')
		},
		joinDiscussion:function(name){
			var self=this
			return self.syncChannel.post('/visitor/joinDiscussion', {'name':name})
		},
		// Indicates that a message is being written
		startMessage:function(){
			var self=this
			self.ensureBus()
			self.channel.post('/visitor/startMessage')
		},
		// Send a message into the discussion channel
		sendMessage:function(text){
			var self=this
			self.ensureBus()
			self.channel.post('/visitor/sendMessage', {'text':text}).onFail(function(){
				alert('Cannot send message because server is unavailable')
			})
		},
		cancelDiscussion:function(){
			var self=this
			self.ensureBus()
			self.channel.post('/visitor/cancelDiscussion')
		}
	}
})
chatclient.VisitorPresence=extend.Class({
	name:'chatclient.VisitorPresence', parent:undefined,
	shared:{
		ACTIONS:['askForOperator', 'cancelDiscussion']
	},
	properties:{
		ui:undefined,
		actions:undefined,
		connector:undefined
	},
	initialize:function(connector, selector){
		var self=this
		selector = selector === undefined ? '.VisitorPresence' : selector
		self.actions = {}
		self.connector = undefined
		self.connector = connector;
		self.ui = $(selector);
		connector.bus.on('init', function(e){
			self.init(e.data)
		})
		connector.bus.on('discussion:ENDED', function(e){
			self.updateStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
			
			//LF 2009-12-03: clear repid when discussion ends (use self.ui ?)
			$(chatclient.VisitorConnector.SINGLETON.chatClient.ui).removeData('repid');
			
			//self.connector.declineInvitation(); ???
		})
		connector.bus.on('visitor:VIEWING', function(e){
			self.updateStatus(e.data.model)
			self.show()
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('visitor:ASKED_FOR_CHAT', function(e){
			self.updateStatus(e.data.model)
			self.show()
			
			// LF 2009-06-15
			Chatolio.centerChatWindow(e);
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('visitor:INVITED', function(e){
			self.updateStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('visitor:DO_NOT_DISTURB', function(e){
			self.updateStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('visitor:CHATTING', function(e){
			self.updateStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('visitor:INACTIVE', function(e){
			self.updateStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('visitor/offline', function(e){
			self.updateStatus(e.source)
		})
		connector.bus.on('service:AVAILABLE', function(e){
			self.updateServiceStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('service:BUSY', function(e){
			self.updateServiceStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		connector.bus.on('service:OFFLINE', function(e){
			self.updateServiceStatus(e.data.model)
			//FC 2009-09-10
			Chatolio.triggerStatusUpdate(e.data.model);
		})
		$('.do-showDiscussion', self.ui).click(function(){
			connector.chatClient.show()
			connector.ping()
			
			// LF 2009-06-15
			Chatolio.centerChatWindow();
			
			// LF 2009-11-26: action images mgt
			(function(context) {
				$('div.action-image.do-declineInvitation', context).show();
				$('div.action-image.do-acceptInvitation', context).show();
				$('div.action-image.do-joinDiscussion', context).hide();
			})( $('.ChatClient .visitorPanel') );
		})
		// LF 2009-11-26
		connector.bus.on('visitor/acceptInvitation', function(e){
			// LF 2009-11-26: action images mgt
			(function(context) {
				$('div.action-image.do-declineInvitation', context).hide();
				$('div.action-image.do-acceptInvitation', context).hide();
				$('div.action-image.do-joinDiscussion', context).show();
			})( $('.ChatClient .visitorPanel') );
		});
		// LF 2009-07-10
		connector.bus.on('visitor/joinDiscussion', function(e){
			// LF 2009-07-10
			Chatolio.centerChatWindow(e);
		})
		// LF 2009-12-03
		connector.bus.on('visitor/cancelDiscussion', function(e){
			//LF 2009-12-03: clear repid when discussion ends (use self.ui ?)
			$(chatclient.VisitorConnector.SINGLETON.chatClient.ui).removeData('repid');
		});
		// LF 2009-12-03
		connector.bus.on('operator/cancelDiscussion', function(e){
			//LF 2009-12-03: clear repid when discussion ends (use self.ui ?)
			$(chatclient.VisitorConnector.SINGLETON.chatClient.ui).removeData('repid');
		});
		// LF 2009-09-14
		connector.bus.on('discussion:INITIATED', function(e){
			// set focus on name input
			$('input.in.name.w-150').focus();
			
			// LF 2009-09-25
			//Chatolio.debug(e);
			/*
			var chatClient = chatclient.VisitorConnector.SINGLETON.chatClient;
			var messageText = Chatolio.evaluateTemplate("~{{StatusBusyText}}", Y.get("body").getAttribute("xlang"));
			chatClient.addMessageToView(chatClient.getTimeStamp(), $('.dealerName .value').html(), messageText, 'status', true);
			*/
		})
		/** LF 2009-10-01: added event observers for stats **/
		var listOfEvents = [
			"visitor/askForOperator",
			"visitor/declineInvitation",
			"visitor/acceptInvitation",
			"visitor/joinDiscussion",
			"visitor/cancelDiscussion",
			"visitor/timedOut",
			"visitor/offline",
			"visitor/ping",
			"visitor/typing",
			"visitor/idle",
			"visitor/visitPage",
			"visitor/setName",
			"visitor/new",
			"operator/login",
			"operator/available",
			"operator/busy",
			"operator/notAvailable",
			"operator/logout",
			"operator/leave",
			"operator/inviteVisitor",
			"operator/cancelDiscussion",
			"operator/timedOut",
			"discussion/message",
			"service/close",
			"service/busy",
			"service/available"
		];
		$.each(listOfEvents, function() {
			self.connector.bus.on(this, function(e){
				// LF 2009-10-01
				Chatolio.statsObserver(e);
			});
		});
		/***************************************************/
		self.bindUI()
	},
	methods:{
		bindUI:function(){
			var self=this
			extend.iterate(self.getClass() .ACTIONS, function(c){
				self.actions[c] = $(('.do-' + c), self.ui).click(self.getMethod(c));
			}, self)
		},
		askForOperator:function(){
			var self=this
			self.connector.askForOperator()
		},
		init:function(data){
			var self=this
			self.updateStatus(data.visitor)
			self.updateServiceStatus(data.service)
		},
		show:function(){
			var self=this
			$(self.ui).removeClass('hidden')
		},
		hide:function(){
			var self=this
			$('.in.message', self.ui).blur()
			$(self.ui).addClass('hidden')
		},
		updateStatus:function(visitor){
			var self=this
			if ( (visitor.state == 'INACTIVE') )
			{
				if ( (visitor.previousState == 'CHATTING') )
				{
					$('.when-chatting > .body > div', self.ui).addClass('hidden')
					$('.when-chatting .when-inactive', self.ui).removeClass('hidden')
				}
			}
			else if ( true )
			{
				if ( (visitor.state == 'CHATTING') )
				{
					$('.when-chatting > .body > div', self.ui).addClass('hidden')
					$('.when-chatting .when-active', self.ui).removeClass('hidden')
					var selector=('.when-' + visitor.state.toLowerCase());
					$('.w-panels > .panel', self.ui).addClass('hidden')
					$(selector, self.ui).removeClass('hidden')
				}
				else if ( (visitor.state == 'ENDED') )
				{
					$('.when-chatting > .body > div', self.ui).addClass('hidden')
					$('.when-chatting .when-ended', self.ui).removeClass('hidden')
				}
				else if ( true )
				{
					var selector=('.when-' + visitor.state.toLowerCase());
					$('.w-panels > .panel', self.ui).addClass('hidden')
					$(selector, self.ui).removeClass('hidden')
				}
			}
		},
		updateServiceStatus:function(service){
			var self=this
			if ( (service.state == 'OFFLINE') )
			{
				$('.when-service-available', self.ui).addClass('hidden')
				$('.when-service-busy', self.ui).addClass('hidden')
				$('.when-service-offline', self.ui).removeClass('hidden')
			}
			else if ( (service.state == 'AVAILABLE') )
			{
				$('.when-service-available', self.ui).removeClass('hidden')
				$('.when-service-busy', self.ui).addClass('hidden')
				$('.when-service-offline', self.ui).addClass('hidden')
			}
			else if ( (service.state == 'BUSY') )
			{
				$('.when-service-available', self.ui).addClass('hidden')
				$('.when-service-busy', self.ui).removeClass('hidden')
				$('.when-service-offline', self.ui).addClass('hidden')
			}
		}
	}
})
chatclient.ChatClient=extend.Class({
	// The chat client (once plugged on its HTML UI) allows a visitor to discuss
	// with an operator.
	name:'chatclient.ChatClient', parent:undefined,
	shared:{
		INPUTS:['name', 'message'],
		ACTIONS:['declineInvitation', 'acceptInvitation', 'joinDiscussion', 'cancelDiscussion', 'setName', 'clearMessage', 'sendMessage', 'hide']
	},
	properties:{
		ui:undefined,
		inputs:undefined,
		actions:undefined,
		state:undefined,
		connector:undefined,
		discussionID:undefined,
		onSendMessage:undefined,
		onStartMessage:undefined
	},
	// Creates a new chat client instance bound to the given ui (identified by the
	// given CSS 'selector'
	initialize:function(connector, selector){
		var self=this
		selector = selector === undefined ? '.ChatClient' : selector
		self.inputs = {}
		self.actions = {}
		self.state = undefined
		self.connector = undefined
		self.discussionID = undefined
		self.onSendMessage = undefined
		self.onStartMessage = undefined
		self.connector = connector;
		self.ui = $(selector);
		self.bindUI()
	},
	methods:{
		initAsVisitor:function(){
			var self=this
			self.showInvitationPanel()
			self.onStartMessage = self.connector.getMethod('startMessage');
			self.onSendMessage = self.connector.getMethod('sendMessage');
			self.connector.bus.on('init', function(e){
				self.init(e.data)
			})
			self.connector.bus.on('operator/inviteVisitor', function(e){
				self.setDealerInformation(e.source)
				self.showInvitationPanel()
				self.show()
				
				// LF 2009-06-15
				Chatolio.centerChatWindow(e);
				
				// LF 2009-11-26: action images mgt
				(function(context) {
					$('div.action-image.do-declineInvitation', context).show();
					$('div.action-image.do-acceptInvitation', context).show();
					$('div.action-image.do-joinDiscussion', context).hide();
				})( $('.ChatClient .visitorPanel') );
			})
			self.connector.bus.on('operator/offline', function(e){
				self.showEndPanel()
			})
			self.connector.bus.on('operator/timedOut', function(e){
				self.showEndPanel()
			})
			self.connector.bus.on('discussion/message', function(e){
			Chatolio.debug(e.data.message);
				var visitor=self.connector.initialData.visitor;
				var message=e.data.message;
				if ( (message.author.uid != visitor.uid) )
				{
					self.addMessageToView(self.getTimeStamp(), ($('.dealerName .value').html() || message.author.name), message.text, message.type, true)
				}
				else if ( true )
				{
					self.addMessageToView(self.getTimeStamp(), message.author.name, message.text, message.type, false)
				}
			})
			self.connector.bus.on('discussion:ACTIVE', function(e){
				self.updateChatPanel(e.data.model)
				
				// LF 2009-09-14: set focus on textarea
				$('textarea.in.message').focus();
			})
			self.connector.bus.on('discussion:ENDED', function(){
				self.showEndPanel()
			})
			self.discussionID = null;
		},
		initAsOperator:function(discussion){
			var self=this
			self.showChatPanel()
			self.connector.bus.on('discussion/message', function(e){
				var message=e.data.message;
				if ( (e.source.uid == discussion.uid) )
				{
					if ( (message.author.uid == discussion.visitorID) )
					{
						self.addMessageToView(self.getTimeStamp(), message.author.name, message.text, message.type, true)
					}
					else if ( true )
					{
						self.addMessageToView(self.getTimeStamp(), message.author.name, message.text, message.type, false)
					}
				}
			})
			self.connector.bus.on('discussion:ENDED', function(e){
				if ( (e.data.model.uid == discussion.uid) )
				{
					self.showEndPanel()
				}
			})
			self.onStartMessage = function(t){
				self.connector.startMessage(discussion.uid)
			};
			self.onSendMessage = function(t){
				self.connector.sendMessage(t, discussion.uid)
			};
			self.discussionID = discussion.uid;
			$('.when-operator', self.ui).removeClass('hidden')
		},
		// Binds the handlers to elements of the HTML UI
		bindUI:function(){
			var self=this
			extend.iterate(self.getClass() .INPUTS, function(c){
				self.inputs[c] = $(('.in.' + c), self.ui);
			}, self)
			extend.iterate(self.getClass() .ACTIONS, function(c){
				self.actions[c] = $(('.do-' + c), self.ui).click(self.getMethod(c));
			}, self)
			extend.iterate(self.inputs, function(i, k){
				i.click(function(){
					if ( ($(this).hasClass('empty') && (! $(this).hasClass('selected'))) )
					{
						$(this).addClass('selected').select()
					}
					else if ( true )
					{
						$(this).removeClass('selected')
					}
				})
			}, self)
			$('.identification form', self.ui).submit(function(){
				var name=$.trim($('.identification form .in.name', self.ui).val());
				if ( name )
				{
					$('.identification .do-joinDiscussion', self.ui).click()
				}
				return false
			})
			$('.identification .in.name', self.ui).keypress(function(e){
				if ( (e.keyCode == 13) )
				{
					self.joinDiscussion()
				}
			})
			self.inputs.message.keypress(function(e){
				var text=self.inputs.message.val();
				if ( (e.keyCode == 13) )
				{
					if ( e.ctrlKey )
					{
						self.sendMessage(text)
						return false
					}
					else if ( $(this).hasClass('ending') )
					{
						text = extend.slice(text,0,-1);
						self.sendMessage(text)
						return false
					}
					else if ( true )
					{
						$(this).addClass('ending')
					}
				}
				else if ( true )
				{
					if ( (text.length == 0) )
					{
						self.onStartMessage()
					}
					$(this).removeClass('ending')
				}
			})
			self.clearMessage()
		},
		declineInvitation:function(){
			var self=this
			self.connector.declineInvitation()
			self.hide()
		},
		joinDiscussion:function(){
			var self=this
			var name=self.setName();
			if ( name )
			{
				self.connector.joinDiscussion(name).onSucceed(function(d){
					var visitor=self.connector.initialData.visitor;
				})
			}
		},
		acceptInvitation:function(){
			var self=this
			self.connector.acceptInvitation()
			self.showVisitorFormPanel()
		},
		setName:function(){
			var self=this
			var name=$.trim(self.inputs.name.val());
			if ( name )
			{
				self.showChatPanel()
			}
			return name
		},
		getName:function(){
			var self=this
			return $.trim(self.inputs.name.val() || self.connector.initialData.visitor.name)
		},
		cancelDiscussion:function(){
			var self=this
			self.connector.cancelDiscussion(self.discussionID)
			self.showEndPanel()
		},
		clearMessage:function(){
			var self=this
			self.inputs.message.val('')
		},
		sendMessage:function(text){
			var self=this
			text = text === undefined ? null : text
			if ( (! extend.isString(text)) )
			{
				text = self.inputs.message.val();
			}
			text = $.trim(text);
			if ( (text.length > 0) )
			{
				self.onSendMessage(text)
				self.inputs.message.val('').focus()
				return true
			}
			else if ( true )
			{
				return false
			}
		},
		getTimeStamp:function(time){
			var self=this
			time = time === undefined ? undefined : time
			var d=new Date();
			if ( time )
			{d = new Date(time);}
			return sprintf('%02d:%02d:%02d', d.getHours(), d.getMinutes(), d.getSeconds())
		},
		init:function(data){
			var self=this
			if ( (data.visitor.state == 'INVITED') )
			{
				self.setDealerInformation(data.operator)
				self.showInvitationPanel()
				self.hide()
			}
			else if ( ((data.visitor.state == 'CHATTING') || (data.visitor.state == 'INACTIVE')) )
			{
				self.setDealerInformation(data.operator)
				var visitor=self.connector.initialData.visitor;
				if ( data.discussion )
				{
					extend.iterate(data.discussion.messages, function(message){
						// LF 2009-09-28
						var isInbound = (message.author.uid != visitor.uid);
						if(isInbound) {
							self.addMessageToView(self.getTimeStamp(), ($('.dealerName .value').html() || message.author.name), message.text, message.type, isInbound)
						} else {
							self.addMessageToView(self.getTimeStamp(), message.author.name, message.text, message.type, isInbound)
						}
					}, self)
				}
				self.discussionID = data.discussion.uid;
				self.showChatPanel()
				self.scrollToLastMessage()
				self.hide()
			}
			else if ( true )
			{
				self.hide()
			}
		},
		show:function(){
			var self=this
			$(self.ui).removeClass('hidden')
			$('.VisitorPresence .do-showDiscussion').addClass('hidden')
		},
		hide:function(){
			var self=this
			$(self.ui).addClass('hidden')
			$('.VisitorPresence .do-showDiscussion').removeClass('hidden')
		},
		showInvitationPanel:function(){
			var self=this
			$('.main-panes > .panel', self.ui).addClass('hidden')
			$('.visitorPanel', self.ui).removeClass('hidden')
			$('.visitorPanel .panel.N1', self.ui).removeClass('hidden')
			$('.visitorPanel .panel.N2', self.ui).addClass('hidden')
		},
		showVisitorFormPanel:function(){
			var self=this
			$('.main-panes > .panel', self.ui).addClass('hidden')
			$('.visitorPanel', self.ui).removeClass('hidden')
			$('.visitorPanel .panel.N1', self.ui).addClass('hidden')
			$('.visitorPanel .panel.N2', self.ui).removeClass('hidden')
		},
		// Called when a discussion:ACTIVE event is perceived. If the discussion
		// is a different one from the currently displayed discussion, we clear the
		// list of messages.
		updateChatPanel:function(discussion){
			var self=this
			if ( (self.discussionID && (discussion.uid != self.discussionID)) )
			{
				extend.iterate($('.messages li', self.ui), function(message){
					message = $(message);
					if ( (! message.hasClass('hidden')) )
					{
						message.remove()
					}
				}, self)
				self.discussionID = discussion.uid;
			}
		},
		showChatPanel:function(){
			var self=this
			$('.main-panes > .panel', self.ui).addClass('hidden')
			$('.chatPanel', self.ui).removeClass('hidden')
			$('.chatPanel .newMessage', self.ui).removeClass('hidden')
			$('.chatPanel .endMessage', self.ui).addClass('hidden')
			$('.chatPanel .when-not_end', self.ui).removeClass('hidden')
			$('.chatPanel .when-end', self.ui).addClass('hidden')
		},
		showEndPanel:function(){
			var self=this
			$('.in.message', self.ui).blur()
			$('.main-panes > .panel', self.ui).addClass('hidden')
			$('.chatPanel', self.ui).removeClass('hidden')
			$('.chatPanel .newMessage', self.ui).addClass('hidden')
			$('.chatPanel .endMessage', self.ui).removeClass('hidden')
			$('.chatPanel .when-end', self.ui).removeClass('hidden')
			$('.chatPanel .when-not_end', self.ui).addClass('hidden')
		},
		setDealerInformation:function(operator){
			var self=this
			if ( operator )
			{
				// LF 2009-07-07: set onload callback on all dealer pictures
				Chatolio.onLoadImage($('.dealerPicture img', self.ui));
				
				// LF 2009-12-02: keep rep_id on ui element
				$(self.ui).data('repid', operator.repid);
				
				$('.dealerName        .value', self.ui).html(operator.fullname || operator.name)
				$('.dealerDescription .value', self.ui).html(operator.description)
				$('.dealerPicture     img', self.ui).attr({'src':operator.pictureURL, 'alt':operator.name, 'title':operator.name})
			}
		},
		addMessageToView:function(date, author, message, type, isInbound){
			var self=this
			if ( ((type == 'pre-message') && (! isInbound)) )
			{
				return true
			}
			var new_message=$('.templates .message', self.ui).clone();
			new_message.addClass(('is-' + type))
			$('.messageDate', new_message).html((('[' + date) + '] '))
			if ( isInbound )
			{
				$('.messageAuthor', new_message).html((author + ':'))
			}
			else if ( true )
			{
        var transword = Chatolio.evaluateTemplate("~{{textFromYou}}", Y.get("body").getAttribute("xlang"));
				$('.messageAuthor', new_message).html(sprintf('%s ' + transword, self.getName()))
			}
			message = message.replace(/\n/g, "<br>")
			
			$('.messages .is-pre-message', self.ui).remove()
			$('.messageText', new_message).html(message)
			$('.messages ul', self.ui).append(new_message)
			if ( isInbound )
			{
				$(new_message).addClass('inbound')
			}
			else if ( true )
			{
				$(new_message).addClass('outbound')
			}
			if ( (type == 'pre-message') )
			{
				window.setTimeout(function(){
					new_message.remove()
					self.scrollToLastMessage()
				}, (1000 * 10))
			}
			self.scrollToLastMessage()
		},
		scrollToLastMessage:function(){
			var self=this
			var messages=$('.messages', self.ui)[0];
			messages.scrollTop = messages.scrollHeight;
		}
	}
})
chatclient.onUnload=	function(){
		var self=chatclient;
		chatclient.VisitorConnector.SINGLETON.leave()
	}
chatclient.start=	function(location, referer){
		var self=chatclient;
		location = location === undefined ? {'url':undefined, 'title':undefined} : location
		referer = referer === undefined ? {'url':undefined, 'title':undefined} : referer
		var connector=new chatclient.VisitorConnector();
		new chatclient.VisitorPresence(connector)
		var chat=new chatclient.ChatClient(connector);
		connector.chatClient = chat;
		chat_show = chat.getMethod('show');
		chat_hide = chat.getMethod('hide');
		var win_h=$(window).height();
		var win_w=$(window).width();
		var chat_w=($(chat.ui).width() || 400);
		$(chat.ui).css({'left':((win_w / 2) - (chat_w / 2))})
		chat.ui.draggable({'handle':'.is-draggable'})
		chat.initAsVisitor()
		if ( (! location.url) )
		{location.url = ('' + window.location);}
		if ( (! location.title) )
		{location.title = $('head title').text();}
		connector.start(location, referer)
		new eventbus.Applet(connector.bus)
		/*
		window.onbeforeunload = function(){
      chatclient.onUnload()
      window.onbeforeunload = function(_){
      };
      };
		*/
		// LF 2009-11-11: use jQuery wrapper instead of window.onbeforeunload
		$(window).unload(function() {
			$(window).unbind('unload');
			chatclient.onUnload();
		});

	}
chatclient.init=	function(){
		var self=chatclient;
	}
chatclient.init()

