/* @preserve
 * @name:			ui.js
 * @author:			Philip Pryce <philip.pryce@me.com>
 * @description:	UI, the user interface events dispatcher and controller
 * @version:		1.0b
 **/

/**
 * The HUGE UI object!
 **/
var UI = {
	
	datastore: {
		current_view: { type: '', name: '' },
		topics: {},
		empty_pchat_rooms: {},
		focused: false,
		max_messages: 120
	},
	
	
	/**
	 * Kick it all off!
	 **/
	init: function ()
	{
		if (!!Jolt.hash_parser().theme) { config.theme = Jolt.hash_parser().theme; }
		
		if (!!config.theme && config.theme !== '')
		{
			UI.set_theme(config.theme);
		}
		
		UI.bind_events();
		// A necessary evil, unfortunately
		if (navigator.userAgent.indexOf('MSIE') > -1) { $('body').addClass('ie'); }
	},
	
	set_theme: function (theme)
	{
		if ($('#custom-theme').length === 1) { $('#custom-theme').remove(); }
		
		var head = document.getElementsByTagName('head')[0];
		var link = document.createElement('link');
		link.type = 'text/css';
		link.rel = 'stylesheet';
		link.media = 'screen';
		link.id = 'custom-theme';
		link.href = 'themes/' + theme + '/style.css';
		head.appendChild(link);
	},
	
	/**
	 * Bind all the main events.
	 **/
	bind_events: function ()
	{
		// Correct everything
		setTimeout(UI.onresize, 50);
		
		// Make sure everything stays correct on resize.
		$(window).bind('resize', UI.onresize);
		
		// Focus composer when you click the view
		$('.view').bind('click', UI.composer.focus);
		
		$('input, textarea, select')
			.bind('focus', function () { UI.datastore.focused = this; })
			.bind('blur', function () { UI.datastore.focused = false; });
		
		$(document).bind('keypress', function (event)
		{
			if (UI.datastore.focused !== false) { return; }
			var data = {
				ctrl: event.ctrlKey,
				alt: event.altKey,
				shift: event.shiftKey,
				code: event.keyCode
			};
			Events.emit('UI:shortcut', data);
		});
		$('a.chat-link').live('click', function (event)
		{
			event.preventDefault(true);
			var data = this.href.split('#')[1].split('&');
			var type = data.shift();
			if (type === "channel")
			{
				var channel = Util.trim(unescape(data[0])) || false;
				if (channel === false) { return; }
				Packet(['join', 'ns=' + channel.substr(1)]);
			}
			if (type === "youtube")
			{
				var id = Util.trim(unescape(data[0])) || false;
				if (id === false) { return; }
				var youtube = [
					'<object width="434" height="264">',
					'<param name="movie" value="http://www.youtube.com/v/' + id + '&hl=en_US&fs=1&rel=0&autoplay=1"></param>',
					'<param name="allowFullScreen" value="true"></param>',
					'<param name="allowscriptaccess" value="always"></param>',
					'<embed src="http://www.youtube.com/v/' + id + '&hl=en_US&fs=1&rel=0&autoplay=1" ',
					'type="application/x-shockwave-flash" allowscriptaccess="always" ',
					'allowfullscreen="true" width="434" height="263"></embed>',
					'</object>'
				].join('');
				
				UI.notifications.show({
					title: "Youtube Video",
					content: youtube,
					buttons: UI.notifications.DISMISS
				});
			}
		});
		
		// Sidebar events
		UI.sidebar.events();
		// Composer events
		UI.composer.events();
		
		UI.controls.add('settings', function ()
		{
			UI.window.show('preferences', 'settings', function ()
			{
				var extensions = Extensions.loaded;
				var html = [];
				for (var name in extensions)
				{
				    html.push('<li><div class="extension-title">');
				    html.push('<span class="extension-name">' + name + '</span>');
				    html.push('<span class="extension-version">v' + extensions[name].version + '</span>');
				    html.push('<span class="extension-author">' + extensions[name].author + '</span>');
				    html.push('</div><div class="extension-description">');
				    html.push('<p>' + extensions[name].description + '</p>');
				    html.push('</div></li>');
				}
				$('.extension-list').empty().append(html.join(''));
				UI.preferences.load();
			});
		}).set_title('Change settings');
	},
	
	/**
	 * When browser is resized, correct visual elements.
	 **/
	onresize: function ()
	{
		var doc_height = $(window).height();						// page height
		var com_height = $('.composer').height();					// composer height
		var con_height = $('.controls').height();					// controls height
		var top_height = $('.topic:visible').height() || 0;			// topic height
		
		var split_height = doc_height;
		
		$('.split').height(split_height);							// split height = page_height
		$('.view-list').height( split_height - com_height - top_height );
		$('.sidebar-list').height( split_height - con_height );
		
	},
	
	timestamp: {
		enable: function ()
		{
			$('body').addClass('timestamp-' + UI.preferences.get('timestamp_location'));
		},
		disable: function ()
		{
			$('body').removeClass('timestamp-always').removeClass('timestamp-hover');
		},
		
		hover_mode: function ()
		{
			if (UI.preferences.get('timestamp_enabled') === false) { return; }
			$('body').removeClass('timestamp-always').addClass('timestamp-hover');
		},
		always_mode: function ()
		{
			if (UI.preferences.get('timestamp_enabled') === false) { return; }
			$('body').removeClass('timestamp-hover').addClass('timestamp-always');
		}
	},
	
	/**
	 * Title API
	 **/
	title: {
		previous: "",
		current: "",
		interval: null,
		
		set: function (name)
		{
			UI.title.current = name;
			document.title = config.title + " :: " + name;
			
			return UI.title;
		},
		start_toggle: function (split_name)
		{
			if (UI.title.interval !== null) { return; }
			UI.title.previous = UI.title.current;
			UI.title.set(split_name);
			var stage = false;
			UI.title.interval = setInterval(function ()
			{
				var title;
				if (stage === false) { title = UI.title.previous; }
				else { title = UI.title.current; }
				document.title = config.title + " :: " + title;
				stage = !stage;
			}, 1000);
			
			return UI.title;
		},
		stop_toggle: function ()
		{
			clearInterval(UI.title.interval);
			UI.title.interval = null;
			UI.title.set(UI.title.previous);
			UI.title.current = UI.title.previous;
			
			return UI.title;
		}
	},
	
	/**
	 * Preferences API
	 **/
	preferences: {
		current: {},
		defaults: {
			sound_enabled: "true",
			sound_unread: "false",
			sound_aimed: "true",
			sound_notification: "true",
			composer_button: "false",
			timestamp_enabled: "true",
			timestamp_location: "always"
		},
		
		load: function ()
		{
			jQuery.each(UI.preferences.defaults, function (name, value)
			{
				var saved_value = UI.storage.load(name);
				if (saved_value !== null) { value = saved_value; }
				else { UI.storage.save(name, value); }
				
				var elm = $('#' + name, '.modal-windows');
				if (elm.length == 1)
				{
					if (elm.attr('type') == 'checkbox')
					{
						elm.attr('checked', (value === true || value == "true" ? 'checked' : ''));
						
						var id_split = elm.attr('id').split('_');
						
						if (id_split[1] == 'enabled')
						{
							var group = id_split[0];
							var sel = 'input[id^="' + group + '"]:not(#' + name + '), select[id^="' + group + '"], textarea[id^="' + group + '"]';
							$(sel, '.modal-windows').attr('disabled', (value === true || value == "true" ? '' : 'disabled' ));
						}
					}
					else
					{
						elm.val(value);
					}
				}
				UI.preferences.current[name] = value;
				
				if (value == "true") { value = true; }
				if (value == "false") { value = false; }
				
				Events.emit('UI:preferences:' + name, { "value": value });
			});
		},
		get: function (name)
		{
			var value, saved_value = UI.storage.load(name);
			if (saved_value !== null) { value = saved_value; }
			else { value = UI.preferences.defaults[name] || null; }
			
			if (value == "true") { return true; }
			if (value == "false") { return false; }
			
			return value;
		},
		set: function (name, value)
		{
			UI.storage.save(name, value);
			UI.preferences.current[name] = value;
			
			if (value == "true") { value = true; }
			if (value == "false") { value = false; }
			
			Events.emit('UI:preferences:' + name, { "value": value });
		}
	},
	
	/**
	 * Sidebar API
	 **/
	sidebar: {
		events: function ()
		{
			$('#sidebar-console', '.sidebar-list').unbind('click').bind('click', UI.console.focus);
			$('#sidebar-channellist', '.sidebar-list').unbind('click').bind('click', UI.channellist.focus);
			$('dt.channel', '.sidebar-list').unbind('click').bind('click', function (event)
			{
				var name = this.id.substr(16);
				UI.channel.focus(name);
			});
			$('dt.pchat', '.sidebar-list').unbind('click').bind('click', function (event)
			{
				var name = this.id.substr(14);
				UI.pchat.focus(name);
			});
			
			$('.close-button', '.sidebar-list').unbind('click').bind('click', function ()
			{
				var item = $(this).parents('dt');
				if (item.hasClass('channel') === true)
				{
					// Channel part.
					var channel = item.attr('id').substr(16);
					
					Packet(["part", "ns=" + channel]);
				}
				if (item.hasClass('pchat') === true)
				{
					// Trigger pchat part
					var user = item.attr('id').substr(14);
					
					if (UI.datastore.empty_pchat_rooms[user] == 1)
					{
						delete UI.datastore.empty_pchat_rooms[user];
						UI.pchat.remove(user);
						return;
					}
					
					Packet(["pchat part", "u=" + user]);
				}
			});
			
			UI.onresize();
		},
		
		set_current: function (type, name) { UI.datastore.current_view = { type: type, name: name }; },
		
		current_type: function () { return UI.datastore.current_view.type; },
		current_name: function () { return UI.datastore.current_view.name; },
		
		empty: function () { $('.channel, .pchat', '.sidebar-list').remove(); }
	},
	
	/**
	 * View API
	 **/
	view: {
		scroll: function (type, name)
		{
			var offset;
			if (type == 'console')
			{
				offset = $('#view-console', '.view').get(0).scrollHeight || 0;
				$('#view-console', '.view').animate({ scrollTop: offset }, 'fast');
				return;
			}
			offset = $('#view-' + type + '-' + name, '.view').get(0).scrollHeight || 0;
			$('#view-' + type + '-' + name, '.view').animate({ scrollTop: offset }, 'fast');
			
			return UI.view;
		},
		unbind: function () { $('.view-list').unbind('click'); return UI.view; },
		empty: function () { $('ul.channel, ul.pchat', '.view').remove(); return UI.view; }
	},
	
	/**
	 * Console API
	 **/
	console: {
		visible: false,
		
		/**
		 * Toggle visiblity of the console
		 **/
		toggle: function ()
		{
			if (UI.console.visible === true)
			{
				$('#sidebar-console').hide();
				// TODO: focus first channel if console is current
				UI.console.visible = false;
				return;
			}
			$('#sidebar-console').show();
			UI.console.visible = true;
		},
		
		/**
		 * Focus the console
		 **/
		focus: function ()
		{
			$('.topic').hide();
			
			// Sidebar
			$('dt, dd', '.sidebar-list').removeClass('current');
			$('#sidebar-console', '.sidebar-list').addClass('current');
			// View panes
			$('.view-list', '.view').removeClass('current');
			var view = $('#view-console', '.view').addClass('current');
			
			UI.sidebar.set_current('console', false);
			
			UI.sidebar.events();
			UI.composer.enable();
			UI.composer.focus();
			
			UI.title.set('Console');
			
			var scroll_top = view.get(0).scrollTop,
				scroll_height = view.get(0).scrollHeight - view.height();
			
			view.get(0).scrollTop = scroll_height;
		},
		
		/**
		 * Log information to the console
		 **/
		log: function (type, message, packet)
		{
			var types = { log: 0, error: 0, incoming: 0, outgoing: 0 };
			if (!(type.toLowerCase() in types)) { return false; }
			var html = [
				'<li class="' + type + '">',
					'<span class="wrap">',
						'<span class="title">' + type.toUpperCase() + ':</span>',
						'<span class="content">',
							(packet === true ? '<pre>' + message + '</pre>' : message),
						'</span>',
					'</span>',
				'</li>'
			].join('');
			var view = $('#view-console');
			var scroll_top = view.get(0).scrollTop,
				scroll_height = view.get(0).scrollHeight - view.height();
			
			view.append(html);
			
			if ($('li', '#view-console').length >= UI.datastore.max_messages)
			{
				var count = ($('li', '#view-console').length - UI.datastore.max_messages) + 1;
				$('li:lt(' + count + ')', '#view-console').remove();
			}
			
			if (scroll_height === scroll_top) { view.get(0).scrollTop = scroll_height; }
		}
	},
	
	// ChannelList Controller
	channellist: {
		sorting: "users",
		interval: null,
		
		focus: function ()
		{
			$('.topic').hide();
			// sidebar
			$('dt, dd', '.sidebar-list').removeClass('current');
			$('#sidebar-channellist', '.sidebar-list').addClass('current');
			// view panes
			$('.view-list', '.view').removeClass('current');
			$('#view-channellist', '.view').addClass('current');
			
			UI.sidebar.set_current('channellist');
			
			UI.sidebar.events();
			UI.composer.enable();
			UI.composer.value('');
			UI.composer.focus();
			
			UI.title.set('Channel List');
			
			UI.channellist.load();
			
			$('button.pill-left, button.pill-mid, button.pill-right', '.channellist-actions').unbind('click').bind('click', function ()
			{
				UI.channellist.sorting = this.name.substr(5);
				$('.channellist-actions button.selected').removeClass('selected');
				$(this).addClass('selected');
				UI.channellist.load();
			});
			$('button[name="channellist-refresh"]').unbind('click').bind('click', function ()
			{
				UI.channellist.load();
			});
			$('button[name="channellist-signout"]').unbind('click').bind('click', function ()
			{
				UI.storage.clear('authtoken');
				UI.storage.clear('username');
				Packet("disconnect");
			});
			if (UI.channellist.interval) { clearInterval(UI.channellist.interval); }
			UI.channellist.interval = setInterval(UI.channellist.load, 60000);
		},
		enable: function () { $('#sidebar-channellist').show(); },
		disable: function () { $('#sidebar-channellist').hide(); },
		
		load: function (sorting)
		{
			if (UI.channellist.interval && $('.view-list.channellist.current').length != 1) { clearInterval(UI.channellist.interval); }
			sorting = sorting || UI.channellist.sorting;
			Packet(["list", "s=" + sorting]);
		},
		
		add_channels: function (channels)
		{
			var tpl = [];
			$('.channellist-container', '.channellist').empty();
			
			jQuery.each(channels, function (channel, data)
			{
				var users = data.users,
					topic = data.topic.substr(0, 46);
				
				if (data.topic.length > 46) { topic += "..."; }
				
				var p = (users == 1); // Plural
				tpl.push(
					'<li id="channellist-name-' + channel + '">' +
					'<span class="name">#' + channel + '</span>' +
					'<span class="users">' + users + ' user' + (p ? '' : 's') + '</span>' +
					'<span class="info">' + topic + '</span>' +
					'<span class="actions"><button type="button" class="button join"><span>Join</span></button></span>' +
					'</li>'
				);
			});
			$('.channellist-container', '.channellist').append(tpl.join('')).find('button.join').bind('click', function (event)
			{
				var channel = $(this).parents('li').get(0).id.substr(17) || false;
				if (channel === false) { return; }
				
				Packet(["join", "ns=" + channel]);
			});
			
		}
	},
	
	channel: {
		/**
		 * Add a channel to the UI
		 **/
		add: function (name)
		{
			if ($('#sidebar-channel-' + name, '.sidebar-list').length !== 0) { return false; }
			
			var sidebar_html = [
				'<dt class="channel" title="#' + name + '" id="sidebar-channel-' + name + '">',
					'<span class="icon"></span>',
					'<span class="title">#' + name + '</span>',
					'<span class="counter">0</span>',
					'<span class="close-view"><button class="close-button">x</button></span>',
				'</dt>',
				'<dd class="channel" id="sidebar-channel-privclasses-' + name + '">',
					'<dl class="privclasses">',
					'</dl>',
				'</dd>'
			].join('');
			
			$('.sidebar-list').append(sidebar_html);
			$('.topic', '.view').after('<ul class="view-list channel" id="view-channel-' + name + '"></ul>');
			
			UI.sidebar.events();
			setTimeout(UI.onresize, 5);
		},
		
		/**
		 * Remove channel from the UI
		 **/
		remove: function (name)
		{
			if ($('#sidebar-channel-' + name, '.sidebar-list').length === 0) { return false; }
			
			var prev = $('#sidebar-channel-' + name, '.sidebar-list').prevAll('dt');
			
			$('.topic').hide();
			UI.composer.disable();
			
			if (prev.hasClass('channel') === true)
			{
				var channel = prev.get(0).id.substr(16);
				UI.channel.focus(channel);
			}
			if (prev.hasClass('pchat') === true)
			{
				var pchat = prev.get(0).id.substr(14);
				UI.pchat.focus(pchat);
			}
			if (prev.hasClass('channellist') === true)
			{
				UI.channellist.focus();
			}
			if (prev.hasClass('console') === true && UI.console.visible === true)
			{
				UI.console.focus();
			}
			
			$('#sidebar-channel-' + name, '.sidebar-list').remove();
			$('#sidebar-channel-privclasses-' + name, '.sidebar-list').remove();
			$('#view-channel-' + name, '.view').remove();
			
			UI.sidebar.events();
			
			setTimeout(UI.onresize, 5);
		},
		
		/**
		 * Focus a channel in the UI
		 **/
		focus: function (name)
		{
			if (!name) { name = $('.channel:first', '.sidebar-list').attr('id').substr(16) || ""; }
			if ($('#sidebar-channel-' + name, '.sidebar-list').length === 0) { return false; }
			
			$('.topic').show();
			
			// Sidebar
			$('dt, dd', '.sidebar-list').removeClass('current');
			$('#sidebar-channel-' + name, '.sidebar-list').addClass('current');
			$('#sidebar-channel-privclasses-' + name, '.sidebar-list').addClass('current');
			// View panes
			$('.view-list', '.view').removeClass('current');
			var view = $('#view-channel-' + name, '.view').addClass('current');
			
			UI.composer.set_value(UI.sidebar.current_type(), UI.sidebar.current_name(), UI.composer.value());
			
			UI.sidebar.set_current('channel', name);
			
			var topic = UI.channel.get_topic(name);
			if (topic !== null) { $('.topic .wrap').html(topic); }
			
			UI.sidebar.events();
			UI.composer.enable();
			UI.composer.focus();
			
			UI.unread.clear('channel', name);
			
			var value = UI.composer.get_value('channel', name);
			UI.composer.value(value);
			
			UI.title.set('#' + name);
			
			var scroll_top = view.get(0).scrollTop,
				scroll_height = view.get(0).scrollHeight - view.height();
			
			view.get(0).scrollTop = scroll_height;
			
			setTimeout(UI.onresize, 5);
		},
		
		/**
		 * Add a message a channel
		 **/
		add_message: function (channel, type, user, message)
		{
			var view, html;
			if (channel === true) { view = $('.view-list.channel', '.view'); } // All channels mode
			else { view = $('#view-channel-' + channel, '.view'); }
			if (view.length === 0) { return false; }
			
			var aimed = ( message.indexOf( Jolt.datastore.username ) > -1 ? true : false );
			
			message = message.replace(new RegExp("\\n", "g"), "<br />");
			
			if (type == 'main')
			{
				html = [
					'<li class="message' + ( aimed ? ' aimed' : '' ) + '">',
						'<span class="wrap">',
							'<span class="timestamp"><span>' + Jolt.timestamp() + '</span></span>',
							'<span class="username"><span>&lt;' + user + '&gt;</span></span>',
							'<span class="message"><span>' + message + '</span></span>',
						'</span>',
					'</li>'
				].join(' ');
			}
			if (type == 'action')
			{
				html = [
					'<li class="action' + ( aimed ? ' aimed' : '' ) + '">',
						'<span class="wrap">',
							'<span class="timestamp"><span>' + Jolt.timestamp() + '</span></span>',
							'<span class="prefix"><span>*</span></span>',
							'<span class="username"><span>' + user + '</span></span>',
							'<span class="message"><span>' + message + '</span></span>',
						'</span>',
					'</li>'
				].join(' ');
			}
			if (type == 'server')
			{
				html = [
					'<li class="server">',
						'<span class="wrap">',
							'<span class="timestamp"><span>' + Jolt.timestamp() + '</span></span>',
							'<span class="prefix"><span>**</span></span>',
							'<span class="message"><span>' + message + '</span></span>',
						'</span>',
					'</li>'
				].join(' ');
			}
			
			var scroll_top = view.get(0).scrollTop,
				scroll_height = view.get(0).scrollHeight - view.height();
					
			view.append(html);
			
			if (view.find('li').length >= UI.datastore.max_messages)
			{
				var count = (view.find('li').length - UI.datastore.max_messages) + 1;
				view.find('li:lt(' + count + ')').remove(); // If some how more than the max messages are appended, remove them too
			}
			
			if (UI.sidebar.current_name() != channel && type != 'server')
			{
				UI.unread.update('channel', channel, aimed);
			}
			
			// Scroll if the user was at the bottom of the views scrollable area
			if (scroll_height === scroll_top) { view.get(0).scrollTop = view.get(0).scrollHeight - view.height(); }
		},
		
		/**
		 * Clear a channel of its messages
		 **/
		clear: function (channel) { $('#view-channel-' + channel).empty(); },
		
		/**
		 * Topic of a channel.
		 **/
		set_topic: function (channel, topic)
		{
			if (!!topic) { UI.datastore.topics[channel] = topic.toString(); }
			return false;
		},
		get_topic: function (channel)
		{
			if (!!UI.datastore.topics[channel]) { return UI.datastore.topics[channel].toString(); }
			return null;
		},
		
		/**
		 * Privclass sub object
		 **/
		privclass: {
			/**
			 * Add a privclass to channel
			 **/
			add: function (channel, name)
			{
				if ($('#sidebar-channel-privclasses-' + channel).length === 0) { return false; }
				
				var html = '<dt class="pclass pclass-' + name + '">' + name + '</dt>';
				$('.privclasses', '#sidebar-channel-privclasses-' + channel).append(html);
			},
			
			/**
			 * Add a user to a privclass inside a channel
			 **/
			add_user: function (channel, pclass, user)
			{
				if ($('#sidebar-channel-privclasses-' + channel).length === 0) { return false; }
				if ($('.privclasses .pclass-' + pclass, '#sidebar-channel-privclasses-' + channel).length === 0) { return false; }
				
				var userclick = function (event)
				{
					Events.emit('UI:user', {
						channel: channel,
						pclass: pclass,
						user: user,
						click_event: event
					});
				};
				
				var html = '<dd class="user user-' + user + '" id="channel-' + channel + '-user-' + user + '-pclass-' + pclass + '">' + user + '</dd>';
				$(html).bind('click', userclick).insertAfter('#sidebar-channel-privclasses-' + channel + ' .privclasses .pclass-' + pclass);
			},
			
			/**
			 * Clear all users and privclasses from the channels sidebar
			 **/
			clear: function (channel)
			{
				if ($('#sidebar-channel-privclasses-' + channel).length === 0) { return false; }
				
				$('.privclasses', '#sidebar-channel-privclasses-' + channel).empty();
			}
			
		}
	},
	
	/**
	 * Private chat
	 **/
	pchat: {
		
		/**
		 * Add a private chat to the UI.
		 **/
		add: function (name)
		{
			if ($('#sidebar-pchat-' + name, '.sidebar-list').length !== 0) { return; }
			
			var sidebar_html = [
				'<dt class="pchat" title="Private Chat with ' + name + '" id="sidebar-pchat-' + name + '">',
					'<span class="icon"></span>',
					'<span class="title">' + name + '</span>',
					'<span class="counter">0</span>',
					'<span class="close-view"><button class="close-button">x</button></span>',
				'</dt>'
			].join('');
			
			$('.sidebar-list').append(sidebar_html);
			$('.topic', '.view').after('<ul class="view-list pchat" id="view-pchat-' + name + '"></ul>');
			
			UI.sidebar.events();
		},
		
		/**
		 * Remove a private chat from the UI
		 **/
		remove: function (name)
		{
			if ($('#sidebar-pchat-' + name, '.sidebar-list').length === 0) { return false; }
			
			var prev = $('#sidebar-pchat-' + name, '.sidebar-list').prevAll('dt');
			
			UI.composer.disable();
			
			if (prev.hasClass('channel') === true)
			{
				var channel = prev.get(0).id.substr(16);
				UI.channel.focus(channel);
			}
			if (prev.hasClass('pchat') === true)
			{
				var pchat = prev.get(0).id.substr(14);
				UI.pchat.focus(pchat);
			}
			if (prev.hasClass('channellist') === true)
			{
				UI.channellist.focus();
			}
			if (prev.hasClass('console') === true && UI.console.visible === true)
			{
				UI.console.focus();
			}
			
			$('#sidebar-pchat-' + name, '.sidebar-list').remove();
			$('#view-pchat-' + name, '.view').remove();
			
			UI.sidebar.events();
			UI.onresize();
		},
		
		/**
		 * Focus a private chat in the UI
		 **/
		focus: function (name)
		{
			if (!name) { name = $('.pchat:first', '.sidebar-list').attr('id').substr(14) || ""; }
			if ($('#sidebar-pchat-' + name, '.sidebar-list').length === 0) { return false; }
			
			$('.topic').hide();
			
			// Sidebar
			$('dt, dd', '.sidebar-list').removeClass('current');
			$('#sidebar-pchat-' + name, '.sidebar-list').addClass('current');
			// View panes
			$('.view-list', '.view').removeClass('current');
			var view = $('#view-pchat-' + name, '.view').addClass('current');
			
			UI.composer.set_value(UI.sidebar.current_type(), UI.sidebar.current_name(), UI.composer.value());
			
			UI.sidebar.set_current('pchat', name);
			
			UI.sidebar.events();
			UI.composer.enable();
			UI.composer.focus();
			
			var value = UI.composer.get_value('pchat', name);
			UI.composer.value(value);
			
			UI.unread.clear('pchat', name);
			
			UI.title.set('Private Chat with ' + name);
			
			var scroll_top = view.get(0).scrollTop,
				scroll_height = view.get(0).scrollHeight - view.height();
			
			view.get(0).scrollTop = scroll_height;
		},
		
		/**
		 * Add a message to the private chat
		 **/
		add_message: function (channel, type, user, message)
		{
			var html, view = $('#view-pchat-' + channel, '.view');
			if (view.length === 0) { return false; }
			
			message = message.replace(new RegExp("\\n", "g"), "<br />");
			
			if (type == 'main')
			{
				html = [
					'<li class="message">',
						'<span class="wrap">',
							'<span class="timestamp"><span>' + Jolt.timestamp() + '</span></span>',
							'<span class="username"><span>&lt;' + user + '&gt;</span></span>',
							'<span class="message"><span>' + message + '</span></span>',
						'</span>',
					'</li>'
				].join(' ');
			}
			if (type == 'action')
			{
				html = [
					'<li class="action">',
						'<span class="wrap">',
							'<span class="timestamp"><span>' + Jolt.timestamp() + '</span></span>',
							'<span class="prefix">*</span>',
							'<span class="username"><span>' + user + '</span></span>',
							'<span class="message"><span>' + message + '</span></span>',
						'</span>',
					'</li>'
				].join(' ');
			}
			if (type == 'server')
			{
				html = [
					'<li class="server">',
						'<span class="wrap">',
							'<span class="timestamp"><span>' + Jolt.timestamp() + '</span></span>',
							'<span class="prefix"><span>**</span></span>',
							'<span class="message"><span>' + message + '</span></span>',
						'</span>',
					'</li>'
				].join(' ');
			}
			var scroll_top = view.get(0).scrollTop,
				scroll_height = view.get(0).scrollHeight - view.height();
			
			view.append(html);
			
			if (view.find('li').length >= UI.datastore.max_messages)
			{
				view.find('li:first').remove();
				var count = (view.find('li').length - UI.datastore.max_messages) + 1;
				view.find('li:lt(' + count + ')').remove();
			}
			
			if (UI.sidebar.current_name() != channel)
			{
				UI.unread.update('pchat', channel);
			}
			
			// Scroll if the user was at the bottom of the views scrollable area
			if (scroll_height === scroll_top) { view.get(0).scrollTop = view.get(0).scrollHeight - view.height(); }
		},
		
		/**
		 * Clear all messages from the private chat
		 **/
		clear: function (channel) { $('#view-pchat-' + channel).empty(); }
	},
	
	/**
	 * Controls API
	 **/
	controls: {
		/**
		 * Making chaning possible, such as adding titles to links
		 **/
		chain: function (elm)
		{
			return {
				set_title: function (title)
				{
					elm.attr('title', title);
					return UI.controls.chain(elm);
				},
				set_link: function (href)
				{
					elm.attr('href', href);
					return UI.controls.chain(elm);
				}
			};
		},
		/**
		 * Add a control with associated function.
		 **/
		add: function (name, fn)
		{
			name = name.replace(' ', '-');
			var html = '<a href="#" class="' + name.toLowerCase() + '-control">' + name + '</a>';
			
			if (typeof fn != "function") { throw new Error("No function supplied."); }
			
			var onclick = function (event)
			{
				fn.call(this, event);
				event.preventDefault();
			};
			
			var elm = $(html).bind('click', onclick).appendTo('.controls .wrap');
			
			UI.onresize();
			
			return UI.controls.chain(elm);
		},
		
		/**
		 * Remove a control from the UI
		 **/
		remove: function (name)
		{
			$('.' + name + '-control', '.controls .wrap').remove();
			
			UI.onresize();
		},
		
		/**
		 * Show Indicator
		 **/
		show_indicator: function () { $('.controls .spinner').stop(true, true).show(); },
		
		/**
		 * Hide Indicator
		 **/
		hide_indicator: function () { $('.controls .spinner').fadeOut(800); }
	},
	
	/**
	 * Composer API
	 **/
	composer: {
		datastore: {
			channel: {},
			pchat: {}
		},
		
		set_value: function (type, name, value)
		{
			if (UI.composer.datastore[type]) { UI.composer.datastore[type][name] = value; }
		},
		get_value: function (type, name) { return UI.composer.datastore[type][name] || ""; },
		
		onsend: function ()
		{
			var value = UI.composer.value();
		    var command = Jolt.command.parser(value);
		    if (command)
		    {
		    	if (Jolt.command.exists(command.command)) {
		    		Events.emit('Jolt:command:' + command.command, command);
		    		UI.composer.value('');
		    	}
		    	else
		    	{
		    		UI.notifications.show({
		    			title: "Command not Recognised",
		    			content: "The command " + command.command + " was not recognised, please make sure you typed it correctly.",
		    			buttons: UI.notifications.DISMISS
		    		});
		    	}
		    	return;
		    }
		    if (value == "") { return; }
		    
		    Events.emit('UI:send', {
		    	'value': value,
		    	'mode': UI.sidebar.current_type()
		    });
			UI.composer.value('');
		},
		
		/**
		 * Events for the composer.
		 **/
		events: function ()
		{
			$('.composer form').bind('click', function () { $('textarea#message').trigger('focus'); });
			$('.composer form button.button').bind('click', function () { UI.composer.onsend(); })
			$('#message')
				.bind('expand', UI.onresize)
				.bind('keydown', function (event)
				{
					
					
					if (event.keyCode == Keys.ENTER && event.altKey === false)
					{
						event.preventDefault(true);
						UI.composer.onsend();
					}
					if (event.keyCode == Keys.TAB)
					{
						event.preventDefault(true);
						Events.emit('UI:tab', {
							'value': this.value,
							'mode': UI.sidebar.current_type()
						});
					}
					if ((event.keyCode == Keys.UP  && event.altKey === true) || (event.keyCode == Keys.DOWN && event.altKey === true))
					{
						event.preventDefault(true);
						Events.emit('UI:history', {
							'UP': (event.keyCode == Keys.UP),
							'DOWN': (event.keyCode == Keys.DOWN),
							'mode': UI.sidebar.current_type(),
							'name': UI.sidebar.current_name()
						});
					}
					
					
				})
				.TextAreaExpander();
			/* END CHAIN */
		},
		
		/**
		 * Disable the composer
		 **/
		disable: function ()
		{
			$('.composer form').addClass('disabled').find('textarea').attr('disabled', 'disabled');
		},
		
		/**
		 * Enable the composer
		 **/
		enable: function ()
		{
			$('.composer form').removeClass('disabled').find('textarea').removeAttr('disabled');
		},
		
		/**
		 * Current status of the composer, disabled or enabled
		 **/
		status: function () { return $('.composer form').hasClass('disabled') ? 'disabled' : 'enabled'; },
		
		/**
		 * Focus the composer, unless its disabled
		 **/
		focus: function ()
		{
			if (UI.composer.status() === 'disabled') { return; }
			setTimeout(UI.onresize, 100);
			$('#message').trigger('focus');
			return UI.composer;
		},
		
		blur: function ()
		{
			if (UI.composer.status() === 'disabled') { return; }
			$('#message').trigger('blur');
			return UI.composer;
		},
		
		select: function (text)
		{
			if (!text) { return UI.composer; }
			
			var elm = $('#message').get(0);
			
			if (typeof elm.selectionStart != "undefined")
			{
				var value = UI.composer.value();
				
				var start = value.indexOf(text);
				var end = start + text.length;
				
				elm.selectionStart = start;
				elm.selectionEnd = end;
			}
			return UI.composer;
		},
		
		/**
		 * Return or set a value into the composer.
		 **/
		value: function (val)
		{
			if (typeof val != "undefined")
			{
				$('#message').val( val );
				return UI.composer;
			}
			return $('#message').val();
		}
	},
	
	/**
	 * Login API
	 **/
	login: {
		type: 'user',
		
		/**
		 * Event bindings
		 **/
		events: function ()
		{
			$('form', '.login-inset').unbind('submit').bind('submit', function (event)
			{
				event.preventDefault();
				
				if (UI.login.type == 'guest') { UI.login.guest_login(); }
				else { UI.login.process(); }
			});
			
			$('.user-guest', '.login-inset').unbind('click').bind('click', function (event)
			{
				var new_type = ( UI.login.type == 'user' ? 'guest' : 'user' );
				UI.login.show(new_type);
			});
		},
		
		hide_guest: function ()
		{
			$('input[name="guest[name]"]', '.login-inset').hide();
			return UI.login;
		},
		
		/**
		 * Guest login processing
		 **/
		guest_login: function ()
		{
			var name = $('input[name="guest[name]"]', '.login-inset').val();
			Events.emit('UI:guest', { name: name });
			return UI.login;
		},
		
		/**
		 * User login processing
		 **/
		process: function ()
		{
			var user = $('input[name="login[user]"]', '.login-inset').val();
			var pass = Util.sha1($('input[name="login[pass]"]', '.login-inset').val());
			Events.emit('UI:login', { user: user, pass: pass });
			return UI.login;
		},
		
		/**
		 * Display a message on the UI
		 **/
		note: function (message)
		{
			if (message)
			{
				$('.note', '.login-inset').html(message).show();
				return UI.login;
			}
			
			$('.note', '.login-inset').hide();
			
			return UI.login;
		},
		
		/**
		 * Show the login form
		 **/
		show: function (type) 
		{
			//UI.notifications.empty_queue();
			UI.notifications.hide();
			
			type = type || "user";
			
			$('.view-list', '.view').removeClass('current');
			$('.view-list.login-view', '.view').addClass('current');
			$('.login-inset').show();
			
			if (type == 'guest')
			{
				$('.login-inset h2').text('Guest Pass');
				$('.login-inset .user-login').hide();
				$('.login-inset .guest-login').show();
				$('.login-inset .guest-help').hide();
				$('.login-inset .user-guest span').text('Back');
				UI.login.type = 'guest';
			}
			else
			{
				$('.login-inset h2').text('Login');
				$('.login-inset .guest-login').hide();
				$('.login-inset .user-login').show();
				$('.login-inset .guest-help').show();
				$('.login-inset .user-guest span').text('Guest');
				UI.login.type = 'user';
			}
			
			UI.login.events();
			
			return UI.login;
		},
		
		/**
		 * Hide the login form
		 **/
		hide: function ()
		{
			$('.login-inset').hide();
			return UI.login;
		},
		
		/**
		 * Disable the login form
		 **/
		disable: function ()
		{
			$('input, button', '.login-inset form').attr('disabled', 'disabled');
			return UI.login;
		},
		
		/**
		 * Enable the login form
		 **/
		enable: function ()
		{
			$('input, button', '.login-inset form').removeAttr('disabled');
			return UI.login;
		},
		
		/**
		 * Focus the login form
		 **/
		focus: function ()
		{
			var selector = (UI.login.type == 'user' ? 'input[name="login[user]"]' : 'input[name="guest[name]"]');
			$(selector, '.login-inset').trigger('focus');
			return UI.login;
		},
		
		/**
		 * Clear the login form
		 **/
		clear: function ()
		{
			$('input', '.login-inset').val('');
			return UI.login;
		}
		
	},
	
	/**
	 * Sound effects API
	 **/
	soundfx: {
		/**
		 * Play a sound effect
		 **/
		play: function (name)
		{
			if (UI.preferences.get('sound_enabled') === false) { return false; }
			return Joules.soundfx(name);
		}
	},
	
	/**
	 * Message history API
	 **/
	history: {
		index: 0,
		max: 15,
		timeout: null,
		
		datastore: {
			channel: {},
			pchat: {}
		},
		
		record: function (type, channel, input)
		{
			if (input == "") { return; }
			if (!UI.history.datastore[type]) { return; }
			if (!UI.history.datastore[type][channel]) { UI.history.datastore[type][channel] = []; }
			if (UI.history.datastore[type][channel].length > UI.history.max) { UI.history.datastore[type][channel].shift(); }
			UI.history.datastore[type][channel].push(input);
			UI.history.index = UI.history.datastore[type][channel].length;
		},
		reset: function (type, channel)
		{
			UI.history.index = UI.history.datastore[type][channel].length;
		},
		
		get: function (type, channel)
		{
			var i = UI.history.index;
			return UI.history.datastore[type][channel][i] || "";
		},
		
		previous: function (type, channel)
		{
			if (!UI.history.datastore[type]) { return; }
			if (!UI.history.datastore[type][channel]) { UI.history.datastore[type][channel] = []; }
			
			var o = UI.history.index;
			if (o - 1 < 0) { return UI.history.datastore[type][channel][0]; }
			var i = --UI.history.index;
			var msg = UI.history.datastore[type][channel][i] || '';
			return msg;
		},
		
		next: function (type, channel)
		{
			if (!UI.history.datastore[type]) { return; }
			if (!UI.history.datastore[type][channel]) { UI.history.datastore[type][channel] = []; }
			
			var o = UI.history.index;
			if (o + 1 > UI.history.datastore[type][channel].length) { return UI.history.datastore[type][channel][o]; }
			var i = ++UI.history.index;
			var msg = UI.history.datastore[type][channel][i] || '';
			return msg;
		}
		
	},
	
	/**
	 * Unread messages API
	 **/
	unread: {
		datastore: { channel: {}, pchat: {} },
		totalcount: 0,
		
		update: function (type, name, aimed)
		{
			if (!UI.unread.datastore[type][name]) { UI.unread.datastore[type][name] = 1; }
			else { ++UI.unread.datastore[type][name]; }
			
			++UI.unread.totalcount;
			
			Events.emit('UI:unread:update', {
				type: type,
				name: name,
				aimed: aimed || false,
				count: UI.unread.datastore[type][name],
				totalcount: UI.unread.totalcount
			});
		},
		
		clear: function (type, name)
		{
			var previous_value = UI.unread.datastore[type][name] || 0;
			
			UI.unread.totalcount = UI.unread.totalcount - previous_value;
			
			UI.unread.datastore[type][name] = 0;
			
			Events.emit('UI:unread:clear', {
				type: type,
				name: name,
				totalcount: UI.unread.totalcount
			});
		}
	},
		
	/**
	 * Window API
	 **/
	window: {
		/**
		 * Events binding
		 **/
		events: function ()
		{
			$('.toggle-item', '.header').unbind('click').bind('click', function (event)
			{
				var name = this.id.substr(12);
				UI.window.change_tab(name);
				event.preventDefault(true);
			});
			$('form', '.modal-windows').bind('submit', function (event)
			{
				UI.window.hide();
				event.preventDefault(true);
			});
			$('input, textarea, select', '.modal-windows .control.live').bind('change', function (event)
			{
				var target = $(event.target);
				var data = {
					id: target.attr('id'),
					value: target.attr('value'),
					type: target.attr('type'),
					target: target
				};
				if (data.type == 'checkbox') { data.value = target.attr('checked') ? true : false; }
				Events.emit('UI:window:inputchange', data);
			});
		},
		
		/**
		 * Request a window and display it
		 **/
		show: function (name, tab, fn)
		{
			$('.modal-windows').show();
			
			
			$.get("windows/" + name + ".html", function (html)
			{
				$('.window', '.modal-windows').html(html).show();
				
				UI.window.events();
				if (tab) { UI.window.change_tab(tab); }
				if (fn) { fn.call(window); }
			});
		},
		
		/**
		 * Hide a window
		 **/
		hide: function ()
		{
			$('.modal-windows').hide();
			$('.window', '.modal-windows').empty().hide();
		},
		
		/**
		 * Change tab in the current window.
		 **/
		change_tab: function (name)
		{
			if ($('#toggle-item-' + name, '.header').length === 0) { return; }
			
			// Toggle item
			$('.toggle-item', '.header').removeClass('current');
			$('#toggle-item-' + name, '.header').addClass('current');
			// Content view
			$('.content', '.window').removeClass('current');
			$('#window-content-' + name, '.window').addClass('current');
		}
	},
	
	/**
	 * Notification API
	 **/
	notifications: {
		queue: [  ],
		open: false,
		timeout: null,
		selector: '.notifications .notification',
		
		DISMISS: [ { label: "Dismiss", onclick: function () { UI.composer.focus(); } } ],
		
		/**
		 * Show a notification or queue is one is already visible
		 **/
		show: function (options, bypass)
		{
			var settings;
			// If bypass === true, forget setting defaults
			if (bypass) { settings = options; }
			else
			{
				// This is already done for queued items, so lets skip it.
				var defaults = {
					title: null,
					buttons: [],
					content: '',
					hide: false,
					empty_queue: false,
					soundfx: false,
					default_focus: 0
				};
				settings = jQuery.extend(defaults, options);
			}
			
			if (UI.notifications.open === true && bypass !== true)
			{
				// A notification was already open, push this one into the queue.
				UI.notifications.queue.push(settings);
				return;
			}
			
			// Title
			if (settings.title) { $('h2', UI.notifications.selector).text( settings.title ).show(); }
			else { $('h2', UI.notifications.selector).hide(); }
			
			// Buttons
			if (settings.buttons.length === 0) { $('.buttons', UI.notifications.selector).hide(); }
			else
			{
				var button_wrapper = $('.buttons', UI.notifications.selector).empty();
				
				Util.each(settings.buttons, function (item)
				{
					var origclick = item.onclick;
					var onclick = function ()
					{
						if (typeof origclick == "function") { origclick(); }
						UI.notifications.hide();
					};
					$('<button type="button" class="button"><span>' + item.label + '</span></button>').click(onclick).appendTo(button_wrapper);
				});
				
				button_wrapper.show();
			}
			
			// Auto Hide
			if (settings.hide)
			{
				UI.notifications.timeout = setTimeout(UI.notifications.hide, settings.hide);
			}
			
			// Empty Queue
			if (settings.empty_queue) { UI.notifications.empty_queue(); }
			
			if (settings.soundfx && UI.preferences.get('sound_enabled') && UI.preferences.get('sound_notification'))
			{
				UI.soundfx.play('notification');
			}
				
			
			UI.composer.blur();
			
			// Display!
			$('.content', UI.notifications.selector).html(settings.content);
			$('.notifications, .notification').show();
			
			$('.buttons button:eq(' + parseInt(settings.default_focus, 10) + ')', UI.notifications.selector).trigger('focus');
			
			// Set the open flag.
			UI.notifications.open = true;
			
		},
		
		/**
		 * Empty the notification queue
		 **/
		empty_queue: function () { UI.notifications.queue = []; },
		
		/**
		 * Hide the notification, then display the next in queue if it exists
		 **/
		hide: function ()
		{
			if (UI.notifications.timeout) { clearTimeout(UI.notifications.timeout); }
			if (UI.notifications.queue.length === 0)
			{
				UI.notifications.open = false;
				
				$('.notifications, .notification').fadeOut('fast');
				return;
			}
			
			var settings = UI.notifications.queue.shift();
			if (settings)
			{
				$('.notifications, .notification').hide();
				setTimeout( function ()
				{
					UI.notifications.show(settings, true);
				}, 200);
			}
		}
	},
	
	/**
	 * Dropdown Menu
	 **/
	dropdown: {
		
		create: function (options)
		{
			var defaults = {
				titles: [],
				items: [],
				has_pointer: true,
				top: false,
				left: false,
				right: false,
				bottom: false
			};
			var settings = jQuery.extend(defaults, options), css = { width: "auto" };
			
			if (settings.top) { css.top = parseInt(settings.top, 10) + "px"; }
			if (settings.left) { css.left = parseInt(settings.left, 10) + "px"; }
						
			var dd = $('.dropdown').css(css).find('dl').empty().end();
			
			if (settings.has_pointer === true) { dd.addClass('has-pointer'); }
			else { dd.removeClass('has-pointer'); }
			
			if (settings.titles.length === 0)
			{
				Util.each(settings.items, function (item)
				{
					if (typeof item == "string") { UI.dropdown.add_seperator(); }
					if (item.label && item.fn) { UI.dropdown.add_item(item.label, item.fn); }
				});
			}
			else {
				Util.each(settings.titles, function (title, i)
				{
					UI.dropdown.add_title(title);
					if (!!settings.items[title])
					{
						Util.each(settings.items[title], function (item)
						{
							UI.dropdown.add_item(item.label, item.fn, true);
						});
					}
					
					if (i + 1 != settings.titles.length) { UI.dropdown.add_seperator(); }
				});
			}
			
			$('.dropdown-overlay').show().unbind('click').bind('click', UI.dropdown.destroy);
			Events.emit('UI:dropdown:created');
		},
		
		add_title: function (label)
		{
			$('<dt><span>' + label + '</span></dt>').appendTo('.dropdown dl');
		},
		add_item: function (label, fn, hastitle)
		{
			$('<dd><a href="#">' + label + '</a></dd>').addClass((hastitle === true ? 'with-title' : '')).click(fn).appendTo('.dropdown dl');
		},
		add_seperator: function () { $('<dd class="seperator">&nbsp;</dd>').appendTo('.dropdown dl'); },
		
		destroy: function ()
		{
			$('.dropdown-overlay').hide();
			Events.emit('UI:dropdown:destroyed');
		}
	}
	
};

function first_responder ()
{
	var r, args = Array.prototype.slice.call(arguments);
	for (var i in args)
	{
		if (args.hasOwnProperty(i))
		{
			try { r = new args[i](); break; }
			catch (e) { }
		}
	}
	return r || null;
}

function CookieStorage () {}
function FlashStorage ()
{
	this.flash = Joules.flash;
	if (!this.flash.storage)
	{
		throw new Error("flashStorage is not enabled in this interface. Update recommended.");
	}
}
function LocalStorage ()
{
	if (!window.localStorage)
	{
		throw new Error("localStorage object not found. Modern browser required.");
	}
}

CookieStorage.prototype.type = "CookieStorage";
CookieStorage.prototype.save = function (name, value)
{
	var days = days || 14;
	var date = new Date();
	date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
	var expires = "; expires=" + date.toGMTString();
	document.cookie = name + "=" + value + expires + "; path=/";
};
CookieStorage.prototype.load = function (name)
{
	var nameEQ = name + "=";
	var ca = document.cookie.split(';'), len = ca.length;
	for (var i = 0; i < len; i++)
	{
		var c = ca[i];
		while (c.charAt(0) === ' ') { c = c.substr(1,c.length); }
		if (c.indexOf(nameEQ) === 0) { return c.substr(nameEQ.length,c.length); }
	}
	return null;
};
CookieStorage.prototype.clear = function (name) { this.save(name, ''); };

FlashStorage.prototype.type = "FlashStorage";
FlashStorage.prototype.save = function (name, value) { this.flash.storage_save(name, value); };
FlashStorage.prototype.load = function (name) { return this.flash.storage_load(name); };
FlashStorage.prototype.clear = function (name) { this.save(name, null); };

LocalStorage.prototype.type = "LocalStorage";
LocalStorage.prototype.save = function (name, value) { window.localStorage.setItem(name, value); };
LocalStorage.prototype.load = function (name) { return window.localStorage.getItem(name); };
LocalStorage.prototype.clear = function (name) { window.localStorage.removeItem(name); };


// save, load, clear
UI.storage = first_responder(LocalStorage, FlashStorage, CookieStorage);

/**
 * Kick start.
 **/
$(UI.init);
