/**
 * Main JavaScript class for the chat
 *
 * @author		Tim DÃ¼sterhus
 * @copyright		2010 - 2011 Tim DÃ¼sterhus
 * @package		timwolla.wcf.chat
 * @license		Creative Commons BY-NC-ND <http://creativecommons.org/licenses/by-nc-nd/3.0/de/>
 */

var Chat = Class.create();
Chat.prototype = {
	/**
	 * Initializes the chat
	 *
	 * @param	integer		id		The next messageID to load
	 * @param	integer		reloadtime	How long should the chat wait between two requests
	 * @param	string		prefix		Prefix for all HTML-Elements
	 * @param	Template	messageTemplate	The Template for each message
	 * @return	void
	 */
	initialize: function(id, reloadtime, prefix, messageTemplate) {
		// set defaults
		/**
		 * The next messageID to load
		 *
		 * @var integer
		 */
		this.id = id;

		/**
		 * Prefix for all HTML-Elements
		 *
		 * @var string
		 */
		this.prefix = prefix;

		/**
		 * Are we currently loading messages?
		 *
		 * @var boolean
		 */
		this.loading = false;

		/**
		 * Is autoscroll active?
		 *
		 * @var boolean
		 */
		this.autoscroll = true;

		/**
		 * Array of users in the userlist
		 *
		 * @var array<string>
		 */
		this.users = [ ];

		/**
		 * Array of open private channels
		 *
		 * @var array<integer>
		 */
		this.privateChannels = [ 0 ];

		/**
		 * ID of the private channel, that is currently open
		 *
		 * @var integer
		 */
		this.activeUserID = 0;

		/**
		 * Username of the user of the open private channel
		 *
		 * @var string
		 */
		this.activeUsername = '';

		/**
		 * Array with Usermenu status
		 *
		 * @var array<boolean>
		 */
		this.menuStatus = [ ];

		/**
		 * Wants the user to be notified on new messages?
		 *
		 * @var boolean
		 */
		this.enableAnimating = false;

		/**
		 * Is autocompletion active?
		 *
		 * @var boolean
		 */
		this.enableAutoComplete = true;

		/**
		 * Internally used by notification
		 */
		this.showText = false;
		this.titleAnimator = null;
		this.text = document.title;
		this.noText = htmlEntityDecode('&nbsp;');

		/**
		 * The Template for each message
		 *
		 * @var Template
		 */
		this.messageTemplate = messageTemplate;

		this.bindFunctions();

		// set timeout
		if (settings.timeout > 0) {
			this.timeoutTimer = window.setTimeout(this._kill, settings.timeout * 60 * 1000);
		}

		// remove the initializing bar
		$(this.prefix+'Initializing').fade({ duration: 1 });

		// register events
		if (!Prototype.Browser.IE) {
			document.observe('focus', this._stopAnimating);
			document.observe('keydown', this._refreshHandler);
		}
		$(this.prefix+'Form').observe('submit', this._submitHandler);
		$(this.prefix+'Input').observe('keydown', this._keydownHandler);

		// load messages
		this.getMessages();
		new PeriodicalExecuter(this._getMessages, reloadtime);

		if (settings.animation) {
			// scroll down in a smooth animation
			new PeriodicalExecuter(function () {
				// Scroll only if the user wants to
				if (this.autoscroll) {
					for (var i = 0; i < this.privateChannels.length; i++) {
						$(this.prefix+'Message'+this.privateChannels[i]).scrollTop = (($(this.prefix+'Message'+this.privateChannels[i]).scrollTop < $(this.prefix + 'Message'+this.privateChannels[i]).scrollHeight) ? $(this.prefix+'Message'+this.privateChannels[i]).scrollTop + 10 : $(this.prefix+'Message'+this.privateChannels[i]).scrollHeight);
					}
				}
			}.bind(this), 0.1);
		}
	},
	/**
	 * Caches the functions
	 *
	 * @return void
	 */
	bindFunctions: function() {
		this._stopAnimating = this.stopAnimating.bindAsEventListener(this);
		this._refreshHandler = this.refreshHandler.bindAsEventListener(this);
		this._submitHandler = this.submitHandler.bindAsEventListener(this);
		this._keydownHandler = this.keydownHandler.bindAsEventListener(this);
		this._getMessages = this.getMessages.bindAsEventListener(this);
		this._kill = this.kill.bindAsEventListener(this);
		this._toggleTitle = this.toggleTitle.bindAsEventListener(this);
		this._update = this.update.bindAsEventListener(this);
		this._failure = this.failure.bindAsEventListener(this);
		this._openList = this.openList.bindAsEventListener(this);
	},
	/**
	 * Handles keydowns in input
	 *
	 * @param	event	event	the fired event-object
	 * @return 	void
	 */
	keydownHandler: function(event) {
		if (!event) {
			event = window.event;
		}

		if (event.which) {
			keynumber = event.which;
		}
		else if (event.keyCode) {
			keynumber = event.keyCode;
		}
		// autocompletion
		if (keynumber == 9 && this.enableAutoComplete) {
			length = $(this.prefix+'Input').value.length;
			split = $(this.prefix+'Input').value.split(" ");
			for (var i = 0; i < this.users.length; i++) {
				if (split[(split.length - 1)].length > 0 && (split[(split.length - 1)].toLowerCase() == this.users[i].substr(0,split[(split.length - 1)].length).toLowerCase())) {
					$(this.prefix+'Input').value += this.users[i].substr(split[(split.length - 1)].length) + ' ';
					break;
				}
			}

			$(this.prefix+'Input').focus();
			$(this.prefix + 'Input').selectionStart = length;
			$(this.prefix + 'Input').selectionEnd = $(this.prefix + 'Input').value.length;
			event.stop();
		}
		else if (keynumber == 13 && this.enableAutoComplete) {
			if ($(this.prefix + 'Input').selectionStart < $(this.prefix + 'Input').selectionEnd) {
				$(this.prefix + 'Input').selectionStart = $(this.prefix + 'Input').selectionEnd = $(this.prefix + 'Input').value.length;
				event.stop();
			}
		}
	},
	/**
	 * Handles keydowns in window
	 *
	 * @param	event	event	the fired event-object
	 * @return 	void
	 */
	refreshHandler: function(event) {
		if (!event) {
			event = window.event;
		}

		if (event.which) {
			keynumber = event.which;
		}
		else if (event.keyCode) {
			keynumber = event.keyCode;
		}
		// f5 key
		if (keynumber == 116) {
			event.stop();
			this.kill = function () { };
			new Ajax.Request('index.php?form=Chat&kill=1'+SID_ARG_2ND, {
				method: 'get',
				onSuccess: function () {
					window.location.reload(true);
				},
				onFailure: function () {
					window.location.reload(true);
				}
			});
		}
	},
	/**
	 * Handles the submitting of the form
	 *
	 * @param	event	event	the fired event-object
	 * @return 	void
	 */
	submitHandler: function(event) {
		if (!$(this.prefix+'Input').value.blank()) {
			if (this.activeUsername !== '') {
				$(this.prefix + 'Input').value = '/whisper "' + this.activeUsername + '" '+$(this.prefix + 'Input').value;
			}
			$(this.prefix + 'Form').request({ parameters: { ajax:'1' }, onSuccess: this._getMessages });
		}
		$(this.prefix + 'Input').clear();

		if (event) {
			event.stop();
		}

		$(this.prefix + 'Input').focus();

		if (settings.timeout > 0) {
			// set timers for automatic timeout
			if (this.timeoutTimer) {
				window.clearTimeout(this.timeoutTimer);
			}
			this.timeoutTimer = window.setTimeout(this._kill, settings.timeout * 60 * 1000);
		}
	},
	/**
	 * Is the given channel an open private channel?
	 *
	 * @param	integer		id	channel to check
	 * @return	boolean			open channel
	 */
	isOpenChannel: function (id) {
		return this.privateChannels.include(id);
	},
	/**
	 * Stops notification
	 *
	 * @return void
	 */
	stopAnimating: function () {
		if (this.titleAnimator !== null) {
			this.titleAnimator.stop();
			Sound.disable();
			document.title = this.text;
		}
	},
	/**
	 * Starts notification
	 *
	 * @return void
	 */
	animate: function () {
		if (!Prototype.Browser.WebKit && !Prototype.Browser.IE && !document.hasFocus()) {
			if (this.titleAnimator !== null) {
				this.titleAnimator.stop();
			}
			Sound.enable();
			Sound.play(RELATIVE_WCF_DIR + 'js/chat.mp3');
			this.titleAnimator = new PeriodicalExecuter(this._toggleTitle, 1);
		}
	},
	/**
	 * Internally used by the notification
	 */
	toggleTitle: function (pe) {
		if (this.showText) {
			document.title = this.text;
			this.showText = false;
		}
		else {
			document.title = this.noText;
			this.showText = true;
		}
	},
	/**
	 * Loads the new messages
	 *
	 * @return void
	 */
	getMessages: function () {
		if (!this.loading) {
			$(this.prefix + 'Loading').style.display = 'inline';
			this.loading = true;
			new Ajax.Request('index.php?page=ChatMessage&id='+this.id+SID_ARG_2ND, {
				method: 'get',
				onSuccess: this._update,
				onFailure: this._failure,
				sanitizeJSON: true
			});
		}
	},
	/**
	 * Handle failure of the getMessages request
	 *
	 * @return void
	 */
	failure: function () {
		this.kill = function () { };
		$(this.prefix+'Error').appear({duration: 1});
	},
	/**
	 * Updates the messages and userlist
	 *
	 * @param	object	transport	the Ajax-reponse
	 * @return	void
	 */
	update: function (transport) {
		this.loading = false;
		$$('#'+this.prefix+'ExtraRoomContainer select')[0].enable();
		$(this.prefix + 'Loading').setStyle({ display: 'none' });

		var json = transport.responseJSON;
		this.handleUserUpdate(json.users, json.permissions);
		this.handleMessageUpdate(json.messages);
	},
	/**
	 * Updates the userlist
	 *
	 * @param	array	users		the userlist
	 * @param	array	permissions	the permissions of the user that is currently logged in
	 * @return	void
	 */
	handleUserUpdate: function (users, permissions) {
		var ul = new Element("ul");
		ul.setStyle({listStyle: 'none'});
		this.users = [ ];
		var size = users.length;
		for (var i = 0; i < size; i++) {
			this.users[this.users.length] = users[i].usernameraw;

			var li = new Element('li');
			
			// add some classes
			if (users[i].userID == settings.userID) {
				li.addClassName('you');
			}
			if (users[i].muted == 1) {
				li.addClassName('muted');
			}
			var a = new Element('a', { 'id': this.prefix + 'UserListItem'+users[i].userID });
			a.observe('click', this._openList);
			
			if (users[i].muted == 1) {
				// if the user is muted he will be striked
				a.setStyle({textDecoration: 'line-through'});
			}

			// users that are marked as away are displayed italic
			if (!users[i].away.empty()) {
				a.writeAttribute({ 'title': users[i].away} );
				a.setStyle({fontStyle: 'italic'});
			}
			a.update(users[i].username);
			li.insert(a);
			var actions = new Element("ul");

			if (this.menuStatus[users[i].usernameraw] !== true) {
				actions.setStyle({ display: 'none' });
			}
			
			// Profile
			action = new Element('li');
			actionhref = new Element('a', { href: 'index.php?page=User&userID='+users[i].userID });
			actionhref.update(language['wcf.chat.profile']);
			action.insert(actionhref);
			actions.insert(action);
			if (users[i].userID != settings.userID) {
				if (users[i].away.empty()) {
					// Whisper insert
					action = new Element('li');
					actionhref = new Element('a');
					actionhref.update(language['wcf.chat.whisperjs']);
					actionhref.observe('click', this.insertWhisper.bind(this, users[i].usernameraw));
					action.insert(actionhref);
					actions.insert(action);
					// Private insert
					action = new Element('li');
					actionhref = new Element('a');
					actionhref.update(language['wcf.chat.private']);
					actionhref.observe('click', this.openPrivate.bind(this, users[i].userID, users[i].usernameraw));
					action.insert(actionhref);
					actions.insert(action);
				}
				if(permissions.canMute == 1 && users[i].muted === 0) {
					// Mute insert
					action = new Element('li');
					actionhref = new Element('a');
					actionhref.update(language['wcf.chat.mute']);
					actionhref.observe('click', this.insert.bind(this, '/mute '+users[i].usernameraw+' ', false, 0, 0, false));
					action.insert(actionhref);
					actions.insert(action);
				}
				if(permissions.canUnMute == 1 && users[i].muted == 1) {
					// Unmute insert
					action = new Element('li');
					actionhref = new Element('a');
					actionhref.update(language['wcf.chat.unmute']);
					actionhref.observe('click', this.insert.bind(this, '/unmute '+users[i].usernameraw, false, 0, 0, true));
					action.insert(actionhref);
					actions.insert(action);
				}
				if(permissions.canBan == 1) {
					// Ban insert
					action = new Element('li');
					actionhref = new Element('a');
					actionhref.update(language['wcf.chat.ban']);
					actionhref.observe('click', this.insert.bind(this, '/ban '+users[i].usernameraw+' ', false, 0,0 , false));
					action.insert(actionhref);
					actions.insert(action);
				}
			}
			li.insert(actions);
			ul.insert(li);
		}
		$$('#chatMembers ul')[0].remove();
		$('chatMembers').insert(ul);
	},
	/**
	 * Updates the messages
	 *
	 * @param	array	messages	the new messages
	 * @return	void
	 */
	handleMessageUpdate: function (messages) {
		var size = messages.length;
		if (size > 0 && this.enableAnimating && !Prototype.Browser.Opera) {
			this.animate();
		}

		this.id = messages[size - 1].id;
		for (var i = 0; i < size; i++) {
			var li = new Element('li', { 'id': this.prefix + 'Message'+messages[i].id });
			li.addClassName('messageType'+messages[i].type);
			if (messages[i].usernameraw == settings.username) {
				li.addClassName('ownMessage');
			}
			switch (parseInt(messages[i].type)) {
				case 9:
					// type 9 is clearing the chat
					$(this.prefix + 'Message0').update('<ul style="list-style:none;"><li>&nbsp;</li></ul>');
				case 1:
				case 2:
				case 3:
				case 4:
				case 5:
				case 6:
				case 7:
					var name = messages[i].username + ' ';
				break;
				case 11:
					// global annoucements
					var name = messages[i].username + ' ' + language['wcf.chat.global'] + ' ';
				break;
				case 10:
					// team annoucements
					var name = messages[i].username + ' ' + language['wcf.chat.team'] + ' ';
				break;
				case 8:
					var name = language['wcf.chat.topic'] +': ';
				break;
				default:
					var name = messages[i].username + ': ';
				break;
			}
			var data = { messageID: messages[i].id, time: messages[i].time, name: name, text: messages[i].text, usernameraw: messages[i].usernameraw };
			li.update(this.messageTemplate.evaluate(data));

			if (settings.animation) {
				li.setStyle({ display: 'none' });
			}

			if (this.isOpenChannel(messages[i].privateID)) {
				// private channel
				$$('#'+this.prefix+'Message'+messages[i].privateID+' ul')[0].insert(li);
				if (this.activeUserID != messages[i].privateID) {
					// mark as new if not in this channel
					$$('#'+this.prefix+'Private'+messages[i].privateID+' a')[0].addClassName('importantPrivate');
				}
			}
			else {
				$$('#'+this.prefix+'Message0 ul')[0].insert(li);
				if (this.activeUserID !== 0) {
					$$('#'+this.prefix+'Private0 a')[0].addClassName('importantPrivate');
				}
			}
			if (settings.animation) {
				li.appear({ duration: 0.4 });
			}
		}

		// Scroll only if the user wants
		if (this.autoscroll && !settings.animation) {
			for (var i = 0; i < this.privateChannels.length; i++) {
				$(this.prefix+'Message'+this.privateChannels[i]).scrollTop = $(this.prefix + 'Message'+this.privateChannels[i]).scrollHeight;
			}
		}
	},
	/**
	 * Opens the usermenu
	 *
	 * @param	event	event	the fired event
	 * @return	void
	 */
	openList: function (event) {
		e = Event.element(event);
		e2 = e;
		if(e.tagName != 'A') {
			e = e.up('a');
		}
		if(e.parentNode.getElementsByTagName('ul')[0].style.display == 'none') {
			e.parentNode.getElementsByTagName('ul')[0].blindDown();
			id = e2.innerHTML;
			this.menuStatus[id] = true;
		}
		else {
			e.parentNode.getElementsByTagName('ul')[0].blindUp();
			id = e2.innerHTML;
			this.menuStatus[id] = false;
		}
	},
	/**
	 * Clears the list of messages in the current channel
	 *
	 * @return void
	 */
	empty: function () {
		$(this.prefix + 'Message'+this.activeUserID).update('<ul style="list-style:none;"><li>&nbsp;</li></ul>');
	},
	/**
	 * Informs the server that the chat was closed
	 *
	 * @return void
	 */
	kill: function () {
		new Ajax.Request('index.php?form=Chat&kill=1'+SID_ARG_2ND, { method: 'get' });
	},
	/**
	 * Inserts text into the input
	 *
	 * @param	string	value		the text to insert
	 * @param	boolean	append		should the text be appended
	 * @param	integer	selectFrom	the starting letter of a selection
	 * @param	integer	selectLength	how long should the selection be
	 * @param	boolean	submit		should the text automatically be submitted
	 * @return	void
	 */
	insert: function (value, append, selectFrom, selectLength, submit) {
		if (append) {
			$(this.prefix + 'Input').value += value;
		}
		else {
			$(this.prefix + 'Input').value = value;
		}

		selectFrom = selectFrom || 0;
		if (selectFrom > 0) {
			$(this.prefix + 'Input').selectionStart = selectFrom;
			$(this.prefix + 'Input').selectionEnd = selectLength + selectFrom;
		}

		if (submit) {
			this.submitHandler();
		}
		else {
			$(this.prefix + 'Input').focus();
		}
	},
	/**
	 * Inserts a smiley into the input
	 *
	 * @param	string	code	the smiley code
	 * @return	void
	 */
	insertSmiley: function (code) {
		this.insert(' '+code+' ', true);
	},
	/**
	 * Inserts the whisper command into the input
	 *
	 * @param	string	name	the username to whisper to
	 * @return	void
	 */
	insertWhisper: function (name) {
		this.insert('/whisper "' +name+'" ', false);
	},
	/**
	 * Returns a comma-seperated list of selected checkboxes
	 *
	 * @return	string		selected boxes
	 */
	getAllChecked: function () {
		var ids = '';
		$$('#' +this.prefix + 'Message'+this.activeUserID+' > ul > li > input[type=checkbox]').each(function (e) {
			if (e.checked) {
				if (ids.empty()) {
					ids += e.value;
				}
				else {
					ids += ','+e.value;
				}
			}
		});
		return ids;
	},
	/**
	 * Displays the checkboxes
	 *
	 * @return void
	 */
	displayCheckBoxes: function () {
		$$('#' +this.prefix + 'Message'+this.activeUserID+' > ul > li > input[type=checkbox]').each(function (e) {
			e.setStyle({ display: '' });
		});
	},
	/**
	 * Unchecks and hides the checkboxes
	 *
	 * @return void
	 */
	clearCheckBoxes: function () {
		$$('#' +this.prefix + 'Message'+this.activeUserID+' > ul > li > input[type=checkbox]').each(function (e) {
			e.checked = false;
			e.setStyle({ display: 'none' });
		});
	},
	/**
	 * Opens the given private channel, if no such channel exists it will create it
	 *
	 * @param	integer	userID		the userID of the channel
	 * @param	integer	username	the username of the channel
	 * @return	void
	 */
	openPrivate: function (userID, username) {
		if (this.privateChannels.length == 1) {
			// display the main tab if it is not
			$(this.prefix + 'Private0').setStyle({display: ''});
		}

		var found = this.isOpenChannel(userID);
		if (!found) {
			// create new tab
			this.privateChannels[this.privateChannels.length] = userID;
			li = new Element("li", {id: this.prefix + 'Private'+userID});
			a = new Element("a");
			Event.observe(a, 'click', this.openPrivate.bind(this, userID, username));
			a.update('<img src="'+settings.warningIcon+'" alt="" /> '+username);
			li.insert(a);
			$(this.prefix + 'Privatelist').insert(li);
			box = new Element('div',  { id: this.prefix + 'Message'+userID });
			box.addClassName('color-2');
			box.setStyle({ display: 'none'});
			ul = new Element('ul');
			ul.setStyle({listStyle: 'none'});
			li = new Element('li');
			li.update('&nbsp;');
			ul.insert(li);
			box.insert(ul);
			$(this.prefix + 'Message').insert(box);
			chat.openPrivate(userID, username);
		}
		else {
			// display existing tab
			if (userID === 0) {
				$(this.prefix + 'Members').removeClassName('hidden');
				$(this.prefix + 'PrivateControl').addClassName('hidden');
				$(this.prefix + 'ExtraRoomContainer').setStyle({ display: '' });
				$$('#'+this.prefix + 'Options img')[0].setStyle({ display: '' });
			}
			else {
				$(this.prefix + 'Members').addClassName('hidden');
				$(this.prefix + 'PrivateControl').removeClassName('hidden');
				$(this.prefix + 'ExtraRoomContainer').setStyle({ display: 'none' });
				$$('#'+this.prefix + 'Options img')[0].setStyle({ display: 'none' });
			}
			$(this.prefix + 'Private' + this.activeUserID).removeClassName('activeSubTabMenu');
			$(this.prefix + 'Private' + userID).addClassName('activeSubTabMenu');
			$$('#'+this.prefix + 'Private' + userID +' a')[0].removeClassName('importantPrivate');
			$(this.prefix + 'Message' + this.activeUserID).setStyle({ display: 'none' });
			$(this.prefix + 'Message' + userID).setStyle({ display: '' });
			this.activeUserID = userID;
			this.activeUsername = username;
		}
		$(this.prefix+'Input').focus();
	},
	/**
	 * Removes the private channel
	 *
	 * @param	integer	userID	userID of the channel to close
	 * @return	void
	 */
	closePrivate: function (userID) {
		userID = userID || this.activeUserID;
		privateChannels = [ ];
		for (var i = 0; i < this.privateChannels.length; i++) {
			if (this.privateChannels[i] != userID) {
				privateChannels[privateChannels.length] = this.privateChannels[i];
			}
		}
		this.privateChannels = privateChannels;
		$(this.prefix + 'Private'+userID).remove();
		$(this.prefix + 'Message'+userID).remove();
		this.activeUserID = 0;
		this.openPrivate(0, '');
	},
	/**
	 * Changes the room
	 *
	 * @param	integer	roomID		roomID of the new room
	 * @param	string	username	username of the guest that is currently logged in
	 * @return	void
	 */
	changeRoom: function (roomID, username) {
		if (!$$('#'+this.prefix+'ExtraRoomContainer select')[0].disabled) {
			$$('#'+this.prefix+'ExtraRoomContainer select')[0].disable();
			this.loading = true;

			if (username !== '') {
				username = '&username='+username;
			}
			new Ajax.Request('index.php?page=Chat'+SID_ARG_2ND+'&ajax=1&room=' + roomID + username,
			{
				onFailure: function () {
					$(this.prefix+'ErrorRoom').appear({ duration: 1 });
					this.kill = function () { };
				}.bind(this),
				onSuccess: function () {
					$('room'+roomID+'Option').selected = true;
					chat.loading = false;
					this._getMessages();
				}.bind(this)
			});
		}
	}
};

