moxyfy = function() {
	
};

$.extend(moxyfy, {
	skin             : null,
	base_url         : null,
	domain           : null,
	https            : false,
	time_offset      : 0,

	ctrl_key_pressed : false,

	// friend list
	flist_requests    : [],
	flist_online      : [],
	flist_offline     : [],
	flist_hidden      : true,
	flist_overlay     : null,
	flist_overlay_settings : {
		opacity  : .4,
		duration : 200,
		zIndex   : 100
	},
	flist_save_enabled : true,
	flist_idle_timeout : {},
	flist_time_to_idle : 3600000, // 10 minutes to idle

	flist_logout_timeout : {},
	flist_logout_delay   : 5000, // 5 seconds

	// prompt
	prompt_element : null,
	prompt_overlay : null,
	prompt_overlay_settings : {
		opacity  : .4,
		duration : 200,
		zIndex   : 100
	},
	
	message_alert : null,

	init : function(settings) {
		moxyfy.skin         = settings.skin;
		moxyfy.base_url     = settings.base_url;
		moxyfy.domain       = settings.domain;
		moxyfy.https        = settings.https;
		moxyfy.this_user_id = settings.this_user_id;

		// init comet
		moxyfy.Comet.init();

		// determine time offset
		var parts = settings.server_time.split(' ');
		var date  = parts[0].split('-');
		var time  = parts[1].split(':');
		var today = new Date();

		var server_hours = $.parse_int(time[0]);
		var local_hours  = today.getHours();

		if(
				today.getYear()  > date[0] ||
				today.getMonth() > date[1] ||
				today.getDate()  > date[2])
		{
			local_hours += 24;
		}
		else if(
				today.getYear()  < date[0] ||
				today.getMonth() < date[1] ||
				today.getDate()  < date[2])
		{
			server_hours += 24;
		}

		moxyfy.time_offset = local_hours - server_hours;
	},

	// friend list functions
	flist_init : function(flist_button) {
		var self = this;

		// make sure flist is unlocked
		this.flist_locked = false;

		this.flist_button = flist_button;
		// init flist button
		flist_button.onclick(function() {
			self.flist_toggle();
		});

		// create overlay
		this.flist_overlay = new Overlay({
			opacity       : this.flist_overlay_settings.opacity,
			duration      : this.flist_overlay_settings.duration,
			zIndex        : this.flist_overlay_settings.zIndex,
			hide_callback : function() { moxyfy.flist_hide(); }
		});

		// fix zIndex for button and list
		$('div#flist')       .css({zIndex: (this.flist_overlay_settings.zIndex+1)});

		// update number of friends online
		this.flist_set_friends_online();

		// resize the list
		this.flist_resize_list();
		this.flist_resize();

		// register resize function
		moxyfy.Event.register('resize', function() { self.flist_resize(); });
		
		// register logout function
		moxyfy.Event.register('logout', function(){ self.flist_logout(); });

		// register unload function
		$(window).bind('unload', function(){ self.flist_save(); self.flist_logout(); });
		
		// load chats
		this.load_chats();

		moxyfy.comet.startBatch();
		// subscribe to our own chat channel
		moxyfy.comet_subscriptions.push(moxyfy.comet.subscribe('/chat/'+moxyfy.this_user.id,   this, self.receive_chat));
		// publish 'online' status update
		moxyfy.comet.publish('/user/'+moxyfy.this_user.id, {
			id    : moxyfy.this_user.id,
			type  : 'status',
			value : 'online'
		});
		moxyfy.comet.endBatch();
	},
	receive_friend_update : function(update) {
		if(update.data.type == 'status')
		{
			// delay offline status change for a short duration (in case page reload)
			if(update.data.value == 'offline')
			{
				// offline
				moxyfy.flist_logout_timeout[update.data.id] = setTimeout("moxyfy.flist_update_friend_status("+update.data.id+", 'offline'); moxyfy.flist_set_friends_online();", moxyfy.flist_logout_delay);
			}
			else
			{
				if(update.data.value == 'online')
				{
					if(typeof moxyfy.flist_logout_timeout[update.data.id] != 'undefined')
					{
						clearTimeout(moxyfy.flist_logout_timeout[update.data.id]);
					}
				}
				moxyfy.flist_update_friend_status(update.data.id, update.data.value);
				moxyfy.flist_set_friends_online();
			}
		}
	},
	cometd_meta_handshake : function() {
		
	},
	cometd_meta_conenct : function() {
		
	},
	flist_update_friend_status : function(id, status) {
		// compare current status
		if(moxyfy.get_user(id).get('status') == status)
		{
			return;
		}

		// switch which list the friend is located
		moxyfy.switch_friend_status(id, moxyfy.get_user(id).get('status'), status);
	},
	flist_set_friends_online : function() {
		$('div#flist_button div.friends_online span.total').html(this.flist_online.length);

		// update the lists
		if(moxyfy.flist_online.length == 0)
		{
			$('div#flist div.online div.list div.empty').show();
		}
		else
		{
			$('div#flist div.online div.list div.empty').hide();
		}

		if(moxyfy.flist_offline.length == 0)
		{
			$('div#flist div.offline div.list div.empty').show();
		}
		else
		{
			$('div#flist div.offline div.list div.empty').hide();
		}
	},
	flist_logout : function() {
		// disable save
		this.flist_save_enabled = false;

		this.comet.startSyncMode();

		// force publish to be synchronous
		moxyfy.comet.startBatch();
		moxyfy.comet.publish('/user/'+moxyfy.this_user.id, {
			id    : moxyfy.this_user.id,
			type  : 'status',
			value : 'offline'
		});

		// unsubscribe from all channels
		for(var i=0; i<moxyfy.comet_subscriptions.length; i++)
		{
			moxyfy.comet.unsubscribe(moxyfy.comet_subscriptions[i]);
		}
		moxyfy.comet_subscriptions = [];
		moxyfy.comet.endBatch();

		for(var i=0; i<moxyfy.comet_meta_subscriptions.length; i++)
		{
			moxyfy.comet.removeListener(moxyfy.comet_meta_subscriptions[i]);
		}
		moxyfy.comet_meta_subscriptions = [];

		moxyfy.comet.disconnect();

		this.comet.endSyncMode();
		
		return true;
	},
	flist_save : function() {
		if(!this.flist_save_enabled) return;
		this.save_chats();
	},
	// resize overlay
	flist_resize : function() {
		if(!this.flist_overlay.hidden)
			this.flist_hide({quick: true});

		var page_size = $.get_page_size();
		// resize back to page size
		$(this.flist_overlay.element).css('width',  (page_size['window_width']-$('#flist').width()));
		$(this.flist_overlay.element).css('height', (page_size['window_height']-$.parse_int($('#user_bar').css('lineHeight'))));
		$(this.flist_overlay.element).css('top',    $.parse_int($('#user_bar').css('lineHeight'))+'px');

		if(!this.flist_overlay.hidden)
			this.flist_show({quick: true});
	},
	// resize list
	flist_resize_list : function() {
		var page_size = $.get_page_size();
		
		var height = page_size['window_height']
			- $('div#user_bar').height()
			- $.parse_int($('div#flist').css('borderTopWidth'))
			- $.parse_int($('div#flist').css('borderBottomWidth'));
		
		$('div#flist').css({height : height+'px'});
	},
	idle_friend : function(id) {
		moxyfy.flist_update_friend_status(id, 'away');
//		moxyfy.switch_friend_status(id, moxyfy.get_user(id).get('status'), 'away');
//		moxyfy.flist_idle_timeout[id] = setTimeout(function() { moxyfy.offline_friend(id); }, moxyfy.flist_time_to_idle);
	},
	offline_friend : function(id) {
		moxyfy.flist_update_friend_status(id, 'offline');
//		moxyfy.switch_friend_status(id, moxyfy.get_user(id).get('status'), 'offline');
	},
	switch_friend_status : function(id, old_status, new_status) {
		var from_list = old_status != 'offline' ? moxyfy.flist_online : moxyfy.flist_offline;
		var to_list   = new_status != 'offline' ? moxyfy.flist_online : moxyfy.flist_offline;

		// update their status throughout the page
		$('div.user_'+id+' .status_image img').attr('src', $('div.user_'+id+' div.status_image img').attr('src').replace(old_status, new_status));
		$('div.user_'+id+' .status').html(new_status);

		moxyfy.get_user(id).set('status', new_status);

		if(new_status == 'online')
		{
			clearTimeout(moxyfy.flist_idle_timeout[id]);
			moxyfy.flist_idle_timeout[id] = setTimeout(function() { moxyfy.idle_friend(id); }, moxyfy.flist_time_to_idle);
		}

		// don't switch lists if status is "away"
		if(new_status == 'away')
		{
			return;
		}

		// remove friend from from_list
		var from_list_length = from_list.length;
		var index = 0;
		var found = false;
		while(index < from_list_length && !found)
		{
			if(id == from_list[index].id)
			{
				found = true;
			}
			else
			{
				index++;
			}
		}
		from_list.splice(index, 1);

		// add friend to the to_list
		var to_list_length = to_list.length;
		var index = 0;
		var found = false;
		var nickname = moxyfy.get_user(id).get('nickname');
		while(index < to_list_length && !found)
		{
			if(nickname < to_list[index].get('nickname'))
			{
				found = true;
			}
			else
			{
				index++;
			}
		}
		// add to the end of the list
		if(!found)
		{
			to_list.push(moxyfy.get_user(id));
		}
		// add to the front of the list
		else if(index == 0)
		{
			to_list.unshift(moxyfy.get_user(id));
		}
		else
		{
			// add to the index position
			to_list.splice(index, 0, moxyfy.get_user(id));
		}

		// move the friend to the new list in the DOM
		if(index == 0)
		{
			$('div#flist div.'+new_status+' div.list').prepend($('div#flist_item_'+id));
		}
		else if(index == to_list_length)
		{
			$('div#flist div.'+new_status+' div.list').append($('div#flist_item_'+id));
		}
		else
		{
			$('div#flist_item_'+to_list[index-1].id).after($('div#flist_item_'+id));
		}
	},
	add_user_to_list : function(list_name, user) {
		var list = list_name=='online' ? this.flist_online : this.flist_offline;
		var list_length = list.length;
		var index = 0;
		var found = false;
		while(index < list_length && !found)
		{
			// if the user has already been added, just
			// stop everything now and return
			if(user.id == list[index].id)
			{
				return;
			}

			if(user.get('nickname') < list[index].get('nickname'))
			{
				found = true;
			}
			else
			{
				index++;
			}
		}

		// flist item
		var self = this;
		var list_item = $('<div></div>')
		.attr('id', 'flist_item_'+user.id)
		.addClass('friend')
		.addClass('user_'+user.id)
		.append(
			$('<div></div>')
			.addClass('avatar')
			.html('<a href="profile/view/id/'+user.id+'"><img class="framed" src="'+moxyfy.get_user(user.id).get('avatar')+'" /></a>')
		)
		.append(
			$('<div></div>')
			.addClass('nickname')
			.html(user.get('nickname'))
		)
		.append(
			$('<div></div>')
			.addClass('status_image')
			.html('<img src="skins/'+moxyfy.skin+'/images/icons/chat/status_'+user.get('status')+'.png" />')
		)
		.bind('dblclick', function() {
			moxyfy.add_chat($.parse_int(this.id.replace('flist_item_','')));
		})
		.disable_selection()
		.bind('mouseenter mouseleave', function() {
			$(this).toggleClass('friend_highlight');
		});

		// if not found, or belongs at end, push
		if(!found || index == list_length)
		{
			list.push(user);
			$('div#flist div.'+list_name+' div.list').append(list_item);
		}
		else
		{
			// if belongs in front
			if(index == 0)
			{
				list.unshift(user);
				$('div#flist div.'+list_name+' div.list').prepend(list_item);
			}
			// if belongs somewhere inbetween
			else
			{
				list = [].concat(list.slice(0, index), user, list.slice(index));
				$('div#flist div.'+list_name+' div.list flist_item_'+list[index].id).after(list_item);
			}
		}
	},
	flist_set_num_requests : function(num) {
		if(num > 0)
		{
			$('div#flist div.requests span.total').html(num);
			$('div#flist div.requests').show();
			
			if(num == 1)
			{
				$('div#flist div.requests span.plural').html('');
			}
		}
		else
		{
			$('div#flist div.requests').hide();
		}
	},
	flist_accept_request : function(id) {
		// mark this user as our friend
		var user = moxyfy.get_user(id);
		user.get('is_friend') = 1;

		// add them to online list
		if(user.get('status') == 'offline')
		{
			this.add_user_to_list('offline', user);
		}
		else
		{
			this.add_user_to_list('online', user);
			this.flist_set_friends_online();
		}
		
		// subtract one from friend request total
		var num = $.parse_int($('div#flist div.requests span.total').html())-1;
		this.flist_set_num_requests(num);
		
		// subscribe to their personal channel for updates
		moxyfy.comet_subscriptions.push(moxyfy.comet.subscribe('/user/'+id, this, moxyfy.receive_friend_update));
	}
});

$.extend(moxyfy, {
	Request : {
		sent_text : {
			friend : 'Awaiting friend confirmation'
		},
		
		send : function(type, user_id, message) {
			if(!message)
			{
				message = '';
			}
			
			$.ajax_request({
				data : {
					app     : 'user',
					mod     : 'request',
					act     : 'add',
					type    : 'friend',
					user_id : user_id,
					message : message
				},
				success : function(response) {
					$('.friend_request_link_'+user_id).html(moxyfy.Request.sent_text['friend']);
					moxyfy.prompt_hide();
				}
			});
		},
		show_friend_request_prompt : function(id) {
			var user = moxyfy.UserList.get_user(id);

			moxyfy.prompt_show({
				title   : 'Add '+moxyfy.Display.user_name(user)+' as a friend',
				content : ''+
					'<table class="add_friend">'+
					'<tr>'+
						'<td valign="top" class="avatar">'+moxyfy.Display.user_avatar_link(user,'framed')+'</td>'+
						'<td valign="top">'+
							'<div class="info">'+moxyfy.Display.user_name(user)+' will need to accept your friend request.</div>'+
							'<div class="message">Personal Message (optional):</div>'+
								'<div id="friend_request_input" class="input input_area_container">'+
									'<div class="input_area">'+
									'</div>'+
								'</div>'+
							'</div>'+
						'</td>'+
					'</tr>'+
					'<tr>'+
						'<td colspan="2" class="form_actions"><input type="button" value="Send Request" onclick="moxyfy.Request.send(\'friend\', '+user.id+', $(\'#friend_request_input .input_area\').html());" /></td>'+
					'</tr>'+
					'</table>'
			});
			
			$('#friend_request_input .input_area').input_area({
				auto_expand    : {
					height        : 18,
					active_height : 36,
					max_height    : 180
				},
				blur_safe_area : '#friend_request_input'
			});
		}
	},

	RequestList : {
		items           : {},
		item_index      : {},
		num_items       : 0,
		empty_list_text : 'You have no requests.',

		add_request : function(data) {
			if(!moxyfy.RequestList.items[data.type])
			{
				moxyfy.RequestList.items[data.type] = [];
			}

			// add user
			moxyfy.UserList.add_user({
				id         : data.user_id,
				avatar     : data.avatar,
				username   : data.username,
				nickname   : data.nickname,
				first_name : data.first_name,
				last_name  : data.last_name
			});

			// add request
			moxyfy.RequestList.items[data.type].push({
				data : data
			});
			moxyfy.RequestList.num_items++;
		},
		add_requests : function(data) {
			var data_length = data.length;
			for(var i=0;i<data_length;i++)
			{
				moxyfy.RequestList.add_request(data[i]);
			}
		},
		accept_request : function(id) {
			$('#request_'+id).html('<p>loading...</p>');
			$.ajax_request({
				data : {
					app : 'user',
					mod : 'request',
					act : 'accept',
					id  : id
				},
				success : function(response) {
					$('#request_'+id).html('<p>'+response.text+'</p>');
				}
			});
			//moxyfy.RequestList.item_index[id]
		},
		deny_request : function(id) {
			$('#request_'+id).html('<p>loading...</p>');
			$.ajax_request({
				data : {
					app : 'user',
					mod : 'request',
					act : 'deny',
					id  : id
				},
				success : function(response) {
					$('#request_'+id).html('<p>'+response.text+'</p>');
				}
			});
			//moxyfy.RequestList.item_index[id]
		},
		list : function(container_id) {
			if(moxyfy.RequestList.num_items == 0)
			{
				$('#'+container_id).html('<p class="empty">'+moxyfy.RequestList.empty_list_text+'</p>');
				return;
			}

			var list_html = '';
			var first = true;
			for(var type in moxyfy.RequestList.items)
			{
				if(first) { first = false; } else { list_html += '</div>'; }

				list_html += '<div id="'+type+'_requests"><h2>'+type.substr(0,1).toUpperCase()+type.substr(1)+' Requests</h2>';

				var items_length = moxyfy.RequestList.items[type].length;
				for(var i=0;i<items_length;i++)
				{
					var request_text = '';
					var details      = '';
					var user = moxyfy.UserList.get_user(moxyfy.RequestList.items[type][i].data.user_id);
					if(type == 'friend')
					{
						request_text = '<p>'+moxyfy.Display.user_link(user)+' wants to be your friend.</p>';
						if(moxyfy.RequestList.items[type][i].data.message && moxyfy.RequestList.items[type][i].data.message != '')
						{
							details = '<i class="small">Personal Message:</i><br /><p class="quoted_message">'+moxyfy.Display.textarea(moxyfy.RequestList.items[type][i].data.message)+'</p>';
						}
					}

					list_html += '<div id="request_'+moxyfy.RequestList.items[type][i].data.id+'" class="request item">'+
						'<div class="mini_profile">'+moxyfy.Display.user_avatar_link(user,'small')+'</div>'+
							'<div class="content">'+
								'<div class="description">'+request_text+'</div>'+
								'<div class="time">'+moxyfy.Display.time(moxyfy.RequestList.items[type][i].data.date)+'</div>'+
								'<div class="details">'+details+'</div>'+
								'<div class="actions">'+
									'<input type="button" value="Accept" onclick="moxyfy.RequestList.accept_request('+moxyfy.RequestList.items[type][i].data.id+')" /> <input type="button" value="Deny" onclick="moxyfy.RequestList.deny_request('+moxyfy.RequestList.items[type][i].data.id+')" />'+
								'</div>'+
							'</div>'+
						'</div>';

					moxyfy.RequestList.item_index[moxyfy.RequestList.items[type][i].data.id] = moxyfy.RequestList.items[type][i];
				}
			}
			list_html += '</div>';

			$('#'+container_id).html(list_html);
		}
	}
});

$.extend(moxyfy, {
	overlay_zIndex      : 100,
	overlays            : [],
	overlay_showing     : null
});

var Overlay = function(settings) {
	var defaults = {
		opacity       : .5,
		duration      : 100,
		color         : '000000',
		hide_callback : null
	};

	this.settings = $.extend(defaults, settings);
	this.init();
	// add object
	moxyfy.overlays.push(this);
};

$.extend(Overlay.prototype, {
	init : function() {
		// set default vars
		this.locked = false;
		this.hidden = true;

		var self = this;

		// create DOM element
		this.create();
	},
	create : function()	{
		var page_size = $.get_page_size();

		this.element = $('<div></div>')
		.attr('id', 'overlay_'+moxyfy.overlays.length)
		.addClass('overlay')
		.width(page_size['window_width'])
		.height(page_size['window_height'])
		.css({
			position        : 'fixed',
			top             : '0',
			left            : '0',
			backgroundColor : '#'+this.settings.color,
			zIndex          : moxyfy.overlay_zIndex,
			opacity         : this.settings.opacity
		})
		.appendTo(document.body)
		.hide();
		
		var self = this;
		moxyfy.Event.register('resize', function() { self.resize(); });
	},
	toggle : function() {
		if(this.hidden)
			this.show();
		else
			this.hide();
	},
	resize : function() {
		var page_size = $.get_page_size();

		$(this.element)
		.width(page_size['window_width'])
		.height(page_size['window_height']);
	},
	show : function(settings) {
		if(this.locked) return false;
		if(!this.hidden) return false;

		var defaults = {
			width_diff  : 0,
			height_diff : 0,
			callback    : null
		};
		settings = $.extend(defaults, settings);

		// close the currently open overlay
		if(moxyfy.overlay_showing != null)
		{
			moxyfy.overlay_showing.hide();
		}

		// lock the hide function for duration
		this.locked = true;

		var page_size = $.get_page_size();

		// fade in overlay
		var self = this;
		$(this.element).css({
			'opacity': 0
		})
		.width(page_size['window_width']+settings.width_diff)
		.height(page_size['window_height']+settings.height_diff)
		.show()
		.fadeTo(this.settings.duration, this.settings.opacity, function() {
			self.hidden = false;
			self.locked = false;
			if(typeof settings.callback == 'function')
			{
				settings.callback.apply(self);
			}
		});

		moxyfy.overlay_showing = this;
	},
	hide : function(settings) {
		if(this.locked) return false;
		if(this.hidden) return false;

		// lock the hide function for duration
		this.locked = true;

		// fade out overlay
		var self = this;
		$(this.element).fadeTo(this.settings.duration, 0.0, function() {
			$(this).css('opacity', 0.0);
			$(this).hide();
			self.hidden = true;
			self.locked = false;
			if(typeof settings == 'object' && typeof settings.callback == 'function')
			{
				settings.callback.apply(self);
			}
		});
		
		// hide callback
		if(typeof this.settings.hide_callback == 'function')
		{
			this.settings.hide_callback.apply(this);
		}

		moxyfy.overlay_showing = null;
	}
});

$.extend(moxyfy, {
	User : function(id, data) {
		var default_data = {
			username   : '',
			nickname   : '',
			first_name : '',
			last_name  : '',
			avatar     : '',
			status     : 'offline'
		};

		this.id   = id;
		this.data = $.extend(default_data, data);
		this.init();
	}
});
$.extend(moxyfy.User.prototype, {
	init : function() {
		this.is_friend         = false;
	},
	is_empty : function() {
		return (this.id == 0) ? true : false;
	},
	get : function(key, default_val) {
		if(typeof this.data[key] == 'undefined' || this.data[key] == null)
		{
			return default_val;
		}
		return this.data[key];
	},
	set : function(key, val) {
		this.data[key] = val;
	},
	set_is_friend : function(is_friend) {
		this.is_friend = is_friend;
	},
	befriend : function() {
		moxyfy.UserList.add_friend(this.id);
	},
	defriend : function() {
		moxyfy.UserList.remove_friend(this.id);
	}
});

$.extend(moxyfy, {
	this_user : null,

	UserList : {
		objects              : [],
		index                : {},
		friend_index         : {},
		friend_request_index : {},
		num_pending_friend_requests : 0,
		
		init : function(settings) {
			var defaults = {
				friend_requests : 0
			};
			settings = $.extend(defaults, settings);
			
			moxyfy.UserList.friend_requests = settings.friend_requests;
		},
		set_num_pending_friend_requests : function(num) {
			moxyfy.UserList.num_pending_friend_requests = num;
		},
		is_friend_request : function(user) {
			return (moxyfy.UserList.friend_request_index[user.id]) ? true : false;
		},
		make_friend_requests : function(ids) {
			var ids_length = ids.length;
			for(var i=0; i<ids_length; i++)
			{
				moxyfy.UserList.make_friend_request(ids[i]);
			}
		},
		make_friend_request : function(id) {
			// make friend request
			moxyfy.UserList.friend_request_index[id] = true;
		},
		make_friend : function(id) {
			// if doesn't exist, return
			if(typeof moxyfy.UserList.index[id] != 'number')
			{
				return;
			}

			// make friend
			moxyfy.UserList.friend_index[id] = true;
			moxyfy.UserList.objects[moxyfy.UserList.index[id]].set_is_friend(true);
			// clear friend request
			moxyfy.UserList.friend_request_index[id] = false;

			// subscribe
			moxyfy.Chat.subscribe(id);
		},
		make_friends : function(ids) {
			var ids_length = ids.length;
			for(var i=0; i<ids_length; i++)
			{
				moxyfy.UserList.make_friend(ids[i]);
			}
		},
		lose_friend : function(id) {
			// if doesn't exist, return
			if(typeof moxyfy.UserList.index[id] != 'number')
			{
				return;
			}

			// make not friend
			moxyfy.UserList.friend_index[id] = false;
			moxyfy.UserList.objects[moxyfy.UserList.index[id]].set_is_friend(false);

			// unsubscribe
			moxyfy.Chat.unsubscribe(id);
		},
		user_exists : function(id) {
			return (typeof moxyfy.UserList.index[id] == 'number') ? true : false;
		},
		add_user : function(data) {
			// if already exists, update instead
			if(typeof moxyfy.UserList.index[data.id] == 'number')
			{
				for(var key in data)
				{
					// make sure we don't overwrite the nickname
					if(key == 'nickname')
					{
						var nickname = moxyfy.UserList.objects[moxyfy.UserList.index[data.id]].get('nickname','');
						if(nickname != '')
						{
							data[key] = nickname;
						}
					}
					moxyfy.UserList.objects[moxyfy.UserList.index[data.id]].set(key, data[key]);
				}
			}
			else
			{
				moxyfy.UserList.index[data.id] = moxyfy.UserList.objects.length;
				moxyfy.UserList.objects.push(new moxyfy.User(data.id, data));
			}

			if(data.id == moxyfy.this_user_id)
			{
				moxyfy.this_user = moxyfy.UserList.objects[moxyfy.UserList.index[data.id]];
			}

			return moxyfy.UserList.objects[moxyfy.UserList.index[data.id]];
		},
		add_users : function(data) {
			var data_length = data.length;
			for(var i=0;i<data_length;i++)
			{
				moxyfy.UserList.add_user(data[i]);
			}
		},
		get_user : function(id) {
			// if doesn't exist, return empty object
			if(typeof moxyfy.UserList.index[id] != 'number')
			{
				return new moxyfy.User(0);
			}
			else
			{
				return moxyfy.UserList.objects[moxyfy.UserList.index[id]];
			}
		}
	}
});

/*
$.extend(moxyfy, {
	this_user    : null,
	user_objects : [],
	user_index   : {},

	set_this_user : function(id, data) {
		moxyfy.this_user = moxyfy.add_user(id, data);
	},
	add_friends : function(friends) {
		for(var id in friends)
		{
			moxyfy.add_friend(id, friends[id]);
		}
	},
	add_friend : function(id, data) {
		// don't add friends twice
		if(moxyfy.user_exists(id) && moxyfy.get_user(id).get('is_friend'))
		{
			return;
		}
		
		data.is_friend = true;
		moxyfy.add_user(id, data);

		if(data.status == 'offline')
		{
			moxyfy.add_user_to_list('offline', moxyfy.user_objects[moxyfy.user_index[id]]);
		}
		else
		{
			moxyfy.add_user_to_list('online', moxyfy.user_objects[moxyfy.user_index[id]]);
		}

		moxyfy.comet_subscriptions.push(moxyfy.comet.subscribe('/user/'+id, this, moxyfy.receive_friend_update));

		moxyfy.flist_idle_timeout[id] = setTimeout(function() { moxyfy.idle_friend(id); }, moxyfy.flist_time_to_idle);
	},
	user_exists : function(id) {
		if(typeof moxyfy.user_index[id] == 'number')
		{
			return true;
		}
		return false;
	},
	add_user : function(id, data) {
		// if the user already exists, update the data
		if(moxyfy.user_exists(id))
		{
			moxyfy.user_objects[moxyfy.user_index[id]].update(data);
		}
		// else, add the user
		else
		{
			moxyfy.user_index[id] = moxyfy.user_objects.length;
			moxyfy.user_objects.push(new User(id, data));
		}

		return moxyfy.user_objects[moxyfy.user_index[id]];
	},
	get_user : function(id) {
		// if the user doesn't exist, return an empty user
		if(typeof moxyfy.user_index[id] != 'number')
		{
			return {empty:true,data:{},id:id,get:function(){return '';},update:function(){}};
		}
		
		return moxyfy.user_objects[moxyfy.user_index[id]];
	}
});

var User = function(id, data) {
	this.init(id, data);
};
$.extend(User.prototype, {
	empty : false,
	init : function(id, data) {
		this.id   = id;
		this.data = data;
	},
	update : function(data) {
		for(key in data)
		{
			this.data[key] = data[key];
		}
	},
	get : function(field) {
		return this.data[field];
	},
	set : function(key, val) {
		this.data[key] = val;
	},
	is_friend : function() {
		return (typeof this.data.is_friend != 'undefined' && this.data.is_friend == 1) ? true : false;
	}
});
*/

$.extend(moxyfy, {
	Button : function(selector, settings) {
		var defaults = {
			enabled : true,
			onclick : null
		};
	
		this.selector = selector;
		this.settings = $.extend(defaults, settings);
		this.init();
	}
});

$.extend(moxyfy.Button.prototype, {
	init : function() {
		var self = this;
		this.element = $(this.selector)
			.addClass('button')
			.disable_selection()
			.bind('mouseenter mouseleave', function(e) {
				self.toggle_hover(e);
			});

		if(typeof this.settings.onclick == 'function')
		{
			var self = this;
			$(this.selector).bind('click', function() {
				if(self.settings.enabled)
				{
					self.settings.onclick.apply();
				}
			});
		}
		
		if(!this.settings.enabled)
		{
			this.disable();
		}
	},
	enable : function() {
		$(this.selector).removeClass('disabled');
		this.settings.enabled = true;
	},
	disable : function() {
		$(this.selector).addClass('disabled');
		this.settings.enabled = false;
	},
	toggle_hover : function(e) {
		if(!this.settings.enabled) return;

		if(e.type == 'mouseenter' && !$(this.selector).hasClass('hover'))
		{
			$(this.selector).addClass('hover');
		}
		else
		{
			$(this.selector).removeClass('hover');
		}
	},
	activate : function() {
		if(!this.settings.enabled) return;

		$(this.selector).addClass('active');
	},
	deactivate : function() {
		if(!this.settings.enabled) return;

		$(this.selector).removeClass('active');
	},
	onclick : function(func) {
		if(!this.settings.enabled) return;

		$(this.selector).bind('click', func);
	}
});

$.extend(moxyfy, {
	Yard : {
		last_post_id : 0,
		input_area_default_text : 'Have something to say?',
		container    : null,
		owner_id     : null,
		profile      : null,

		build : function(settings) {
			var defaults = {
				owner_id  : null,
				container : null,
				posts     : [],
				profile   : false
			};
			settings = $.extend(defaults, settings);
			
			moxyfy.Yard.owner_id = settings.owner_id;
			moxyfy.Yard.profile  = settings.profile;

			moxyfy.Yard.container = $(settings.container);

			// build yard post input
			moxyfy.Yard.post_input_container = $('<div></div>')
			.attr('id','yard_post_input_container')
			.append(
				$('<div></div>')
				.addClass('mini_profile')
				.html(moxyfy.Display.user_avatar_link(moxyfy.this_user,'small'))
			)
			.append(
				$('<div></div>')
				.addClass('content')
				.append(
					$('<div></div>')
					.addClass('input input_area_container')
					.append(
						$('<div></div>')
						.addClass('input_area')
						.html(moxyfy.Yard.input_area_default_text)
					)
				)
				.append('<input type="button" value="Post Message" onclick="moxyfy.Yard.post_message();" />')
			);

			// build yard post list
			moxyfy.Yard.post_list_container = $('<div></div>')
			.attr('id', 'yard_post_list')
			.append(
				$('<div></div>')
				.addClass('empty')
				.html((moxyfy.this_user.id==moxyfy.Yard.owner_id?'You have':'This user has')+' no sign posts in '+(moxyfy.this_user.id==moxyfy.Yard.owner_id?'your':'their')+' yard.')
				.hide()
			);

			// put it all together
			$(moxyfy.Yard.container)
			.append(
				moxyfy.Yard.post_input_container
			)
			.append(
				moxyfy.Yard.post_list_container
			);
			
			// enable input area
			$('.input_area', moxyfy.Yard.post_input_container).input_area({
				auto_expand    : {
					height        : 18,
					active_height : 36,
					max_height    : 180
				},
				default_text   : moxyfy.Yard.input_area_default_text,
				blur_safe_area : moxyfy.Yard.post_input_container
			});
		
			// add initial posts
			var posts_length = settings.posts.length;
			if(settings.posts.length > 0)
			{
				$('.empty', moxyfy.Yard.post_list_container).show();
			}
			for(var i=posts_length-1; i>=0; i--)
			{
				moxyfy.Yard.add_post(settings.posts[i]);
			}
		},
		add_post : function(post, is_new) {
			if(moxyfy.this_user.get('id') == moxyfy.Yard.owner_id && is_new)
			{
				moxyfy.Yard.set_status_text(post.message);
			}
			
			// add user if needed
			if(!moxyfy.UserList.user_exists(post.user_id))
			{
				moxyfy.UserList.add_user({
					id             : post.user_id,
					first_name     : post.first_name,
					last_name      : post.last_name,
					nickname       : post.nickname,
					avatar_version : post.avatar_version
				});
			}

			if(!moxyfy.Yard.profile && post.user_id != post.to_id && !moxyfy.UserList.user_exists(post.to_id))
			{
				moxyfy.UserList.add_user({
					id             : post.to_id,
					first_name     : post.to_first_name,
					last_name      : post.to_last_name,
					nickname       : post.to_nickname,
					avatar_version : post.to_avatar_version
				});
			}

			moxyfy.Yard.last_post_id = post.id;
			$('.empty', moxyfy.Yard.post_list_container).hide();

			var names = moxyfy.Display.user_link(moxyfy.UserList.get_user(post.user_id));
			if(!moxyfy.Yard.profile && post.user_id != post.to_id)
			{
				names += ' &rArr; '+moxyfy.Display.user_link(moxyfy.UserList.get_user(post.to_id));
			}

			$(moxyfy.Yard.post_list_container).prepend(
				$('<div></div>')
				.attr('id','post_'+post.id)
				.addClass('post')
				.append(
					$('<div></div>')
					.addClass('mini_profile')
					.append(moxyfy.Display.user_avatar_link(moxyfy.UserList.get_user(post.user_id), 'small'))
				)
				.append(
					$('<div></div>')
					.addClass('content')
					.append(
						$('<div></div>')
						.addClass('name')
						.html(names)
					)
					.append(
						$('<div></div>')
						.addClass('message')
						.html(moxyfy.Yard.format_post_message(post.message))
					)
					.append(
						$('<div></div>')
						.addClass('footer')
						.append(
							$('<div></div>')
							.addClass('time')
							.html(moxyfy.Display.time(post.created))
						)
						.append(
							$('<div></div>')
							.addClass('options')
							.html('comment')
							.hide()
						)
					)
				)
				.hide()
			);
		
			$('#post_'+post.id).slideDown();
		},
		post_message : function() {
			var message = $('.input_area', moxyfy.Yard.post_input_container).html();
	
			// must include message
			if(typeof message == 'undefined')
			{
				return;
			}
			
			// remove leading and trailing <br /> and spaces
			message = message.replace(/(<br\s{0,1}\/{0,1}>|&nbsp;|\s)+$/,'');
			message = message.replace(/^(<br\s{0,1}\/{0,1}>|&nbsp;|\s)+/,'');

			if(!$('.input_area', moxyfy.Yard.post_input_container).hasClass('active') && message == moxyfy.Yard.input_area_default_text)
			{
				return;
			}
			// cannot be blank or only spaces
			var tmp = message.replace(/\s*/g,'');
			tmp = tmp.replace(/\&nbsp;/g,'');
			tmp = tmp.replace(/\n/g,'');
			tmp = tmp.replace(/\r/g,'');
			tmp = tmp.replace(/<br>/gi,'');
			tmp = tmp.replace(/<br \/>/gi,'');
			if(tmp == '')
			{
				return;
			}
		
			message = message.replace(/<br\s{0,1}\/{0,1}>/ig,'\n');
		
			$.ajax_request({
				data : {
					app            : 'user',
					mod            : 'yard',
					act            : 'post',
					to_id          : moxyfy.Yard.owner_id,
					message        : message,
					last_post_id   : moxyfy.Yard.last_post_id,
					is_form_submit : 1
				},
				success : function(response) {
					var posts_length = response.posts.length;
					for(var i=posts_length-1; i>=0; i--)
					{
						moxyfy.Yard.add_post(response.posts[i], true);
					}
					
					$('.input_area', moxyfy.Yard.post_input_container).html('<br />');
					$('.input_area', moxyfy.Yard.post_input_container).focus();
				}
			});
		},
		format_post_message : function(text) {
			text = text.replace(/\n{3,}/, '\n\n');
			text = text.replace(/\n/g, '<br />');
			return text;
		},
		set_status_text : function(text) {
			text = text.replace(/\n{2,}/, '\n');
			text = text.replace(/\n/g, ' ');

			$('.content_title .container .personal .status_text').html(text);
		}
	}
});

$.extend(moxyfy, {
	menus_showing : [],
	menu_index    : 0,

	Menu : function(id, settings) {
		var defaults = {
			button        : null,
			type          : 'dropdown',
			parent        : null,
			opacity       : 1.0,
			flush         : 'left', // [left|right|mouse]
			flush_element : null,
			hide_callback : null,
			hide_check    : null
		};

		this.id       = id;
		this.settings = $.extend(defaults, settings);
		this.init();
	}
});
$.extend(moxyfy.Menu.prototype, {
	init : function(element) {
		this.hidden = true;
		this.locked = false;

		this.index = moxyfy.menu_index;
		moxyfy.menu_index++;

		// create menu
		this.element = $(''+
			'<div class="menu '+this.settings.type+'_menu menu_'+this.index+'">'+
				'<div class="body">'+
					'<div class="left">'+
						'<div class="right">'+
							'<div class="border_top"></div>'+
							'<div class="content"></div>'+
						'</div>'+
					'</div>'+
				'</div>'+
				'<div class="footer">'+
					'<div class="blc"></div>'+
					'<div class="bottom"></div>'+
					'<div class="brc"></div>'+
				'</div>'+
			'</div>'
		);

		// append menu to DOM
		$(document.body).append(this.element);
		// append content to menu
		$('.body .left. .right .content', this.element).append($('#'+this.id));

		// bind button
		if(typeof this.settings.button == 'object' && this.settings.button != null)
		{
			$(this.settings.button.element)
				.addClass('button_'+this.index);

			this.settings.flush_element = this.settings.button.element;

			var self = this;
			this.settings.button.onclick(function() {
				self.toggle();
			});
		}
	},
	toggle : function() {
		if(this.hidden)
		{
			this.show();
		}
		else
		{
			this.hide();
		}
	},
	show : function(e) {
		if(!this.hidden) return;
		if(this.locked) return;

		this.hidden = false;
		this.locked = true;

		// if this menu isn't part of the previously opened
		// menu chain, close each part of the chain if applicable
		var found = false;
		var menus_length = moxyfy.menus_showing.length;
		while(!found && menus_length > 0)
		{
			if(this.settings.parent != null && moxyfy.menus_showing[(moxyfy.menus_showing.length-1)].index == this.settings.parent.index)
			{
				found = true;
			}
			else
			{
				var menu = moxyfy.menus_showing.pop();
				menus_length--;
				menu.hide();
			}
		}

		var menu_width =
			$(this.element).width() +
			$.parse_int($(this.element).css('paddingLeft')) +
			$.parse_int($(this.element).css('paddingRight')) +
			$.parse_int($(this.element).css('borderRightWidth')) +
			$.parse_int($(this.element).css('borderLeftWidth'));

		// set location
		var pos;
		if(this.settings.flush == 'mouse')
		{
			pos = $.get_mouse_pos(e);
		}
		else if(this.settings.flush_element != null)
		{
			// get the button position
			pos = $.get_pos(this.settings.flush_element);

			var button_height =
				$(this.settings.flush_element).height() +
				$.parse_int($(this.settings.flush_element).css('paddingTop')) +
				$.parse_int($(this.settings.flush_element).css('paddingBottom')) +
				$.parse_int($(this.settings.flush_element).css('borderTopWidth')) +
				$.parse_int($(this.settings.flush_element).css('borderBottomWidth'));

			pos.y += button_height;

			// compensate for page scroll if the element is fixed
			var target = this.settings.flush_element;
			var is_fixed = false;
			while(typeof target != 'undefined' && !is_fixed && target.tagName != 'BODY')
			{
				if($(target).css('position') == 'fixed')
				{
					is_fixed = true;
				}
				target = target.parentNode;
			}
			if(is_fixed)
			{
				var page_scroll = $.get_page_scroll();
				pos.y -= page_scroll.top;
			}

			if(this.settings.flush == 'right')
			{
				// subtract the width of the menu
				pos.x -= menu_width;

				// add the width of the button
				var button_width =
					$(this.settings.flush_element).width() +
					$.parse_int($(this.settings.flush_element).css('paddingLeft')) +
					$.parse_int($(this.settings.flush_element).css('paddingRight')) +
					$.parse_int($(this.settings.flush_element).css('borderRightWidth')) +
					$.parse_int($(this.settings.flush_element).css('borderLeftWidth'));
				
				pos.x += button_width;
			}
		}

		// make sure the menu isn't off the page
		var page_size = $.get_page_size();
		if(pos.x < 5)
		{
			pos.x = 5;
		}
		else if(pos.x + menu_width > page_size['width']-5)
		{
			pos.x = page_size['width'] - menu_width-5;
		}
		
		// offset for shadow
		if(this.settings.flush == 'right')
		{
			pos.x += 4;
		}
		else if(this.settings.flush == 'left')
		{
			pos.x -= 4;
		}

		$(this.element).css({opacity: this.settings.opacity, top: pos.y+'px', left: pos.x+'px'});

		$(this.element).show();
		moxyfy.menus_showing.push(this);
		
		// activate button
		if(typeof this.settings.button == 'object' && this.settings.button != null)
		{
			this.settings.button.activate();
		}

		var self = this;
		// add escape event
		moxyfy.Event.Escape.register('menu_'+this.id, function() {
			self.hide();
		});
		
		this.locked = false;
		this.showing = true;
	},
	hide : function(e) {
		if(this.hidden) return;
		if(this.locked) return;

		// can this menu hide?
		if(typeof this.settings.hide_check == 'function')
		{
			if(!this.settings.hide_check.apply(this, [e]))
			{
				return;
			}
		}

		this.locked = true;

		$(this.element).hide();
		this.hidden = true;

		// deactivate button
		if(typeof this.settings.button == 'object' && this.settings.button != null)
		{
			this.settings.button.deactivate();
		}

		// del escape event
		moxyfy.Event.Escape.unregister('menu_'+this.id);

		this.locked = false;
		this.showing = false;

		// hide callback
		if(typeof this.settings.hide_callback == 'function')
		{
			this.settings.hide_callback.apply(this, [e]);
		}
	}
});

$(document.body).bind('mouseup', function(e) {
	e = e || window.event;

	var target = (e.target) ? e.target : e.srcElement;

	var sentinel = false;
	var type = null;
	while(!sentinel)
	{
		if(target == null || target.nodeName.toLowerCase() == 'html' || target.nodeName.toLowerCase() == 'body')
		{
			sentinel = true;
		}
		else if($(target).hasClass('dropdown_menu'))
		{
			type = 'menu';
			sentinel = true;
		}
		else if($(target).hasClass('button'))
		{
			type = 'button';
			sentinel = true;
		}
		else
		{
			target = target.parentNode;
		}
	}

	var found = false;
	var menus_length = moxyfy.menus_showing.length;
	while(!found && menus_length > 0)
	{
		if(
			(type == 'button' && $(target).hasClass('button_'+moxyfy.menus_showing[(moxyfy.menus_showing.length-1)].index)) ||
			(type == 'menu'   && $(target).hasClass('menu_'+moxyfy.menus_showing[(moxyfy.menus_showing.length-1)].index))
		)
		{
			found = true;
		}
		else
		{
			var menu = moxyfy.menus_showing.pop();
			menus_length--;
			menu.hide(e);
		}
	}
});

$.extend(moxyfy, {
	Chat : {
		button           : null,
		button_element   : null,
		overlay_settings : {
			opacity  : .4,
			duration : 200,
			zIndex   : 100
		},
		locks : {
			overlay       : false,
			conversations : false,
			list          : false
		},

		init : function() {
			// init cometd
			moxyfy.Chat.user_subscriptions = {};
			moxyfy.Chat.meta_subscriptions = [];
			moxyfy.Chat.user_idle_timeout  = {};
			moxyfy.Chat.time_to_idle       = 3600000; // 10 minutes to idle

			// create and resize overlay
			moxyfy.Chat.overlay = new Overlay({
				opacity       : moxyfy.Chat.overlay_settings.opacity,
				duration      : moxyfy.Chat.overlay_settings.duration,
				zIndex        : moxyfy.Chat.overlay_settings.zIndex,
				hide_callback : function() { moxyfy.Chat.List.hide(); }
			});

			// create chat button
			moxyfy.Chat.create_button();

			// init chat list
			moxyfy.Chat.List.init();
		},
		meta_handshake : function() {
			
		},
		meta_connect : function() {
			
		},
		receive_friend_update : function(update) {
			if(update.data.type == 'status')
			{
				// delay offline status change for a short duration (in case page reload)
				if(update.data.value == 'offline')
				{
					// offline
					moxyfy.flist_logout_timeout[update.data.id] = setTimeout("moxyfy.flist_update_friend_status("+update.data.id+", 'offline'); moxyfy.flist_set_friends_online();", moxyfy.flist_logout_delay);
				}
				else
				{
					if(update.data.value == 'online')
					{
						if(typeof moxyfy.flist_logout_timeout[update.data.id] != 'undefined')
						{
							clearTimeout(moxyfy.flist_logout_timeout[update.data.id]);
						}
					}
					moxyfy.flist_update_friend_status(update.data.id, update.data.value);
					moxyfy.flist_set_friends_online();
				}
			}
		},
		subscribe : function(id) {
			var user = moxyfy.UserList.get_user(id);

			// only subscribe if they are a friend
			if(!user.is_friend) return;
			// only subscribe if subscription doesn't exist
			if(typeof moxyfy.Chat.user_subscriptions[id] == 'undefined' || moxyfy.Chat.user_subscriptions[id] == null)
			{
				moxyfy.Comet.subscribe('/user/'+id, this, moxyfy.Chat.receive_friend_update);

				// create idle timeout
				moxyfy.Chat.user_idle_timeout[id] = setTimeout(function() { moxyfy.Chat.idle_user(id); }, moxyfy.Chat.time_to_idle);
				
				// add user to list
				if(user.get('status') != 'offline')
				{
					moxyfy.Chat.List.add_user(user);
				}
			}
		},
		unsubscribe : function(id) {
			var user = moxyfy.UserList.get(id);

			// only unsubscribe if subscription exists
			if(typeof moxyfy.Chat.user_subscriptions[id] != 'undefined' && moxyfy.Chat.user_subscriptions[id] != null)
			{
				 moxyfy.Comet.unsubscribe('/user/'+id);

				// clear idle timeout
				clearTimeout(moxyfy.Chat.user_idle_timeout[id]);
			}
		},
		create_button : function() {
			moxyfy.Chat.button_element = $('<div></div>')
			.attr('id', 'moxyfy_chat_list_button')
			.addClass('button last')
			.append(
				$('<div></div>')
				.addClass('icon')
				.html('<img src="skins/'+moxyfy.skin+'/images/icons/chat/status_online.png" />')
			)
			.append(
				$('<div></div>')
				.addClass('text friends_online')
				.html('Chat (<span class="total">0</span>)')
			)
			.append(
				$('<div></div>')
				.addClass('options_arrow')
			)
			.appendTo('#user_bar_controls');
		},
		
		Conversations : {
			objects       : [],
			index         : {},
			active_object : 0,
			
			init : function() {
				// load conversations from cookie
				var cookie_data = $.get_cookie('moxyfy/user_'+moxyfy.this_user.id+'/conversations');
				if(cookie_data == '') return;
	
				var conversation_data = JSON.parse(cookie_data);
				for(id in conversation_data)
				{
					var messages = JSON.parse($.get_cookie('moxyfy/user_'+moxyfy.this_user.id+'/conversation/'+id+'/messages'));
					moxyfy.Chat.Conversations.add(id, conversation_data[id], messages);
				}
			},
			add : function(id, settings, messages) {
				var defaults = {};
				settings = $.extend(defaults, settings);
				messages = $.extend([], messages);
	
				// if the conversation already exists, do nothing
				if(typeof moxyfy.Chat.Conversations.index[id] == 'number')
				{
					// do nothing
				}
				// else, add the conversation
				else
				{
					var index = moxyfy.Chat.Conversations.objects.length;
					moxyfy.Chat.Conversations.index[id] = index;
					moxyfy.Chat.Conversations.objects.push(new Chat(id, settings, messages));
		
					// set order, using stored order where applicable
					if(typeof settings.order != 'number')
					{
						moxyfy.Chat.Conversations.objects[index].order = index+1;
					}
					else
					{
						moxyfy.Chat.Conversations.objects[index].order = settings.order;
					}
					// set zIndex
					$(moxyfy.Chat.Conversations.objects[index].win.element).css({zIndex: (moxyfy.Chat.Conversations.objects[index].order+moxyfy.Chat.overlay_settings.zIndex)});
		
					// save conversations
					moxyfy.Chat.Conversations.save();
				}
				
				moxyfy.Chat.Conversations.set_active(id);
				return moxyfy.Chat.Conversations.objects[moxyfy.Chat.Conversations.index[id]];
			},
			remove : function(id) {
				// if the conversation doesn't exist, return
				if(typeof moxyfy.Chat.Conversations.index[id] != 'number')
				{
					return;
				}
		
				var conversation = moxyfy.Chat.Conversations.objects[moxyfy.Chat.Conversations.index[id]];
				var index = moxyfy.Chat.Conversations.index[id];
				// get this conversation's order
				var order = conversation.order;
		
				// reduce each conversation's order and conversation window's zIndex by 1 if greater than our order
				var conversation_length = moxyfy.Chat.Conversations.objects.length;
				for(var i=0;i<conversation_length;i++)
				{
					if(moxyfy.Chat.Conversations.objects[i].order > order)
					{
						moxyfy.Chat.Conversations.objects[i].order--;
						$(moxyfy.Chat.Conversations.objects[i].win.element).css({zIndex: ($(moxyfy.Chat.Conversations.objects[i].win.element).css('zIndex')-1)});
					}
				}
		
				moxyfy.Chat.Conversations.objects.splice(moxyfy.Chat.Conversations.index[id], 1);
				moxyfy.Chat.Conversations.index[id] = null;
				
				// reduce all indexes greater than this one by 1
				for(var key in moxyfy.Chat.Conversations.index)
				{
					if(moxyfy.Chat.Conversations.index[key] > index)
					{
						moxyfy.Chat.Conversations.index[key]--;
					}
				}
				
				if(moxyfy.Chat.Conversations.objects.length > 0)
				{
					moxyfy.Chat.Conversations.set_active(moxyfy.Chat.Conversations.objects[0].id);
				}
				else
				{
					moxyfy.Chat.Conversations.set_active(null);
				}
		
				// clear cached messages
				$.delete_cookie('moxyfy/user_'+moxyfy.this_user.id+'/conversation/'+id+'/messages');
				
				// save conversations
				moxyfy.Chat.Conversations.save();
			},
			save : function() {
				var conversation_data = {};
				var conversation_length = moxyfy.Chat.Conversations.objects.length;
				for(var i=0;i<conversation_length;i++)
				{
					conversation_data[moxyfy.Chat.Conversations.objects[i].id] = {
						top      : moxyfy.Chat.Conversations.objects[i].top,
						left     : moxyfy.Chat.Conversations.objects[i].left,
						order    : moxyfy.Chat.Conversations.objects[i].order
					};
		
					// save messages
					$.set_cookie('moxyfy/user_'+moxyfy.this_user.id+'/conversation/'+moxyfy.Chat.Conversations.objects[i].id+'/messages', JSON.stringify(moxyfy.Chat.Conversations.objects[i].messages));
				}
		
				// save conversations to cookie
				var cookie_data = JSON.stringify(conversation_data);
		
				$.set_cookie('moxyfy/user_'+moxyfy.this_user.id+'/conversations', cookie_data);
			},
			set_active : function(id) {
				if(moxyfy.Chat.Conversations.active_object != null)
				{
					$(moxyfy.Chat.Conversations.active_object.win.element).removeClass('active_window');
					$('div.window_head', moxyfy.Chat.Conversations.active_object.win.element).css({
						opacity: .6
					});
				}
				moxyfy.Chat.Conversations.active_object = moxyfy.Chat.Conversations.objects[moxyfy.Chat.Conversations.index[id]];
				$(moxyfy.Chat.Cconversation.active_object.win.element).addClass('active_window');
				$('div.window_head', moxyfy.Chat.Cconversation.active_object.win.element).css({
					opacity: .85
				});
			},
			update_order : function(id) {
				var conversation = moxyfy.Chat.Conversations.objects[moxyfy.Chat.Conversations.index[id]];
				// get this conversation's order
				var order = conversation.order;
		
				// reduce each conversation's order and conversation window's zIndex by 1 if greater than our order
				var top_zIndex = 0;
				var conversation_length = moxyfy.Chat.Conversations.objects.length;
				for(var i=0;i<conversation_length;i++)
				{
					if($(moxyfy.Chat.Conversations.objects[i].win.element).css('zIndex') > top_zIndex)
					{
						top_zIndex = $(moxyfy.Chat.Conversations.objects[i].win.element).css('zIndex');
					}
					
					if(moxyfy.Chat.Conversations.objects[i].order > order)
					{
						moxyfy.Chat.Conversations.objects[i].order--;
						$(moxyfy.Chat.Conversations.objects[i].win.element).css({zIndex: ($(moxyfy.Chat.Conversations.objects[i].win.element).css('zIndex')-1)});
					}
				}
		
				// set our conversation to the top order
				order = conversation_length;
				conversation.order = order;
				$(conversation.win.element).css({zIndex: top_zIndex});
		
				// save conversations
				moxyfy.Chat.Conversations.save();
			},
			send_message : function(id, text) {
				if (!text || !text.length) return;
		
				moxyfy.Comet.batch('start');
				if(moxyfy.get_user(id).get('status') == 'offline')
				{
					// let us know that the message cannot be sent
					var conversation = moxyfy.Chat.Conversations.objects[moxyfy.Chat.Conversations.index[id]];
					// remember scroll position
					var obj = $('div.messages', conversation.element)[0];
					var scrollHeight = obj.scrollHeight;
					var offsetHeight = obj.offsetHeight;
					var scrollTop    = obj.scrollTop;
		
					conversation.append_warning('Your message could not be sent because this user is not online.');
		
					// update scroll
					if(offsetHeight + scrollTop + 25 >= scrollHeight)
					{
						obj.scrollTop = obj.scrollHeight;
					}
				}
				else
				{
					// send to others
					moxyfy.Comet.publish('/conversation/'+id, {
						conversation_id : moxyfy.this_user.id,
						from_id         : moxyfy.this_user.id,
						text            : text
					});
			
					// send to us
					moxyfy.Chat.receive_message({
						data : {
							conversation_id : id,
							from_id         : moxyfy.this_user.id,
							text            : text
						}
					});
				}
		
				// publish "online" status update
				moxyfy.Comet.publish('/user/'+moxyfy.this_user.id, {
					id    : moxyfy.this_user.id,
					type  : 'status',
					value : 'online'
				});
				moxyfy.Comet.batch('end');
			},
			receive_message : function(message) {
				// see if the window containing the conversation box exists
				var conversation;
				if(typeof moxyfy.Chat.Conversations.index[message.data.conversation_id] == 'number')
				{
					conversation = moxyfy.Chat.Conversations.objects[moxyfy.Chat.Conversations.index[message.data.conversation_id]];
					// remember scroll position
					var obj = $('div.messages', conversation.element)[0];
					var scrollHeight = obj.scrollHeight;
					var offsetHeight = obj.offsetHeight;
					var scrollTop    = obj.scrollTop;
		
					conversation.add_message(message.data);
		
					// update scroll
					if(offsetHeight + scrollTop + 25 >= scrollHeight)
					{
						obj.scrollTop = obj.scrollHeight;
					}
				}
				else
				{
					// if the conversation box wasn't found, create a new window
					conversation = moxyfy.Chat.Conversations.add(message.data.conversation_id);
					conversation.add_message(message.data);
		
					// automatically scroll to the bottom
					var obj = $('div.messages', conversation.element)[0];
					obj.scrollTop = obj.scrollHeight;
				}
				
				moxyfy.Chat.Conversations.save();
				
				// if the overlay is hidden, show the new message alert
				if(moxyfy.Chat.overlay.hidden)
				{
					// create message alert object
					if(this.message_alert == null)
					{
						this.message_alert = $('<div id="message_alert">You have new messages.</div>')
						.hide()
						.appendTo(document.body);
					}
					$(this.message_alert)
					.stop()
					.css({
						opacity : 1.0
					})
					.show()
					.fadeTo(3000, 1.0, function() {
						$(this).fadeTo(2000, 0.0, function() {
							$(this).hide();
						});
					});
				}
			}
		},
		resize_overlay : function() {
			if(!moxyfy.Chat.overlay.hidden)
				moxyfy.Chat.List.hide({quick: true});
	
			var page_size = $.get_page_size();
			var height = page_size['window_height'];

			// resize back to page size
			$(moxyfy.Chat.overlay.element).css('width',  (page_size['window_width']-$(moxyfy.Chat.List.element).width()));
			$(moxyfy.Chat.overlay.element).css('height', height);
			$(moxyfy.Chat.overlay.element).css('top',    $(document.body).css('marginTop'));

			if(!moxyfy.Chat.overlay.hidden)
				moxyfy.Chat.List.show({quick: true});
		},

		List : {
			settings : {
				zIndex : 201
			},
			locked : false,
			hidden : true,
			lists  : {
				online : [],
				idle   : []
			},

			init : function() {
				// create and resize list
				moxyfy.Chat.List.create();
				moxyfy.Chat.List.resize();
				// resize overlay
				moxyfy.Chat.resize_overlay();

				// init list button
				moxyfy.Chat.button = new moxyfy.Button('#moxyfy_chat_list_button', {
					onclick : function() {
						moxyfy.Chat.List.toggle();
					}
				});

				// fix zIndex for button and list
				$(moxyfy.Chat.List.element).css({zIndex: (moxyfy.Chat.List.settings.zIndex)});

				// update number of friends online
				moxyfy.Chat.List.update_friends_online();

				// register resize function
				moxyfy.Event.register('resize', function() { moxyfy.Chat.resize_overlay(); moxyfy.Chat.List.resize(); });

				// register logout function
				moxyfy.Event.register('logout', function(){ moxyfy.Chat.List.logout(); });

				// register unload function
				$(window).bind('unload', function(){ moxyfy.Chat.List.save(); moxyfy.Chat.List.logout(); });

				// load windows
				moxyfy.Chat.Conversations.init();

				moxyfy.Comet.batch('start');
				// subscribe to our own conversation channel
				moxyfy.Comet.subscribe('/conversation/'+moxyfy.this_user.id, this, moxyfy.Chat.receive_message);
				// publish 'online' status update
				moxyfy.Comet.publish('/user/'+moxyfy.this_user.id, {
					id    : moxyfy.this_user.id,
					type  : 'status',
					value : 'online'
				});
				moxyfy.Comet.batch('end');
			},
			save : function() {
				
			},
			logout : function() {
				
			},
			create : function() {
				moxyfy.Chat.List.element = $('<div></div>')
				.attr('id', 'moxyfy_chat_list')
				.append(
					$('<div></div>')
					.addClass('options')
					.html('<a href="'+moxyfy.Display.url({app:'user',mod:'friend',act:'find'})+'"><img src="skins/'+moxyfy.skin+'/images/chat_list/find2.png" /></a>')
				)
				.append(
					$('<div></div>')
					.addClass('requests')
					.html('<img src="skins/'+moxyfy.skin+'/images/icons/alert.png" width="16" /> <a href="'+moxyfy.Display.url({app:'user',mod:'request',act:'list'})+'">You have <span class="total">0</span> friend request<span class="plural">s</span>.</a>')
				)
				.append(
					$('<div></div>')
					.addClass('title')
					.html('<img src="skins/'+moxyfy.skin+'/images/chat_list/online-header.png" />')
				)
				.append(
					$('<ul></ul>')
					.addClass('list')
					.append('<li id="coming_soon">Coming Soon</li>')
					.append('<li id="moxyfy_chat_list_online_marker"></li>')
					.append('<li id="moxyfy_chat_list_idle_marker"></li>')
				)
				.appendTo(document.body)
				.hide();
			},
			add_user : function(user) {
//				moxyfy.Chat.List.lists[user.get('status')].push(user);
				
				
				
//				$('list', moxyfy.Chat.List.element).append(
//					'<li id="moxyfy_chat_list_item_'+user.get('id')+'>'+moxyfy.Display.user_name(user)+'</li>'
//				);
			},
			remove_user : function(user) {
				
			},
			refresh_list : function() {
				
			},
			update_friend_requests : function() {
				if(moxyfy.UserList.num_pending_friend_requests > 0)
				{
					$('.requests .total', moxyfy.Chat.List.element).html(moxyfy.UserList.num_pending_friend_requests);
					if(moxyfy.UserList.num_pending_friend_requests == 1)
					{
						$('.requests .plural', moxyfy.Chat.List.element).hide();
					}
					else
					{
						$('.requests .plural', moxyfy.Chat.List.element).show();
					}
					$('.requests', moxyfy.Chat.List.element).show();
				}
				else
				{
					$('.requests', moxyfy.Chat.List.element).hide();
				}
			},
			update_friends_online : function(num) {
				$('div.friends_online span.total', moxyfy.Chat.button_element).html(moxyfy.Chat.List.lists['online'].length);
			},
			toggle : function() {
				if(moxyfy.Chat.List.hidden)
					moxyfy.Chat.List.show();
				else
					moxyfy.Chat.List.hide();
			},
			show : function(settings) {
				if(moxyfy.Chat.List.locked) return;
				if(!moxyfy.Chat.List.hidden) return;
				moxyfy.Chat.List.locked = true;
		
				if(typeof settings == 'object' && typeof settings.quick == 'boolean' && settings.quick)
				{
					// show chats
					var conversation_length = moxyfy.Chat.Conversations.objects.length;
					for(var i=0;i<conversation_length;i++)
					{
						moxyfy.Chat.Conversations.objects[i].win.check_position();
					}
					// resize list
					moxyfy.Chat.List.resize();
					moxyfy.Chat.List.locked = false;
					moxyfy.Chat.List.hidden = false;
				}
				else
				{
					// activate button
					moxyfy.Chat.button.activate();
					
					// setup locking levers
					moxyfy.Chat.locks['overlay'] = true;
					moxyfy.Chat.locks['conversations'] = true;
					moxyfy.Chat.locks['list'] = true;
			
					// show overlay
					moxyfy.Chat.overlay.show({
						width_diff  : (-1*$(moxyfy.Chat.List.element).width()),
						height_diff : (-1*$.parse_int($('#user_bar').css('lineHeight'))),
						callback    : function() {
							moxyfy.Chat.List.show_callback('overlay');
						}});
		
					// show list
					$(moxyfy.Chat.List.element).css({opacity: 0.0});
					$(moxyfy.Chat.List.element).show();
					moxyfy.Chat.List.resize();
					$(moxyfy.Chat.List.element).fadeTo(moxyfy.Chat.overlay_settings.duration, 1.0, function() {
						moxyfy.Chat.List.show_callback('list');
					});

					// show chats
					var conversation_length = moxyfy.Chat.Conversations.objects.length;
					for(var i=0;i<conversation_length;i++)
					{
						moxyfy.Chat.Conversations.objects[i].win.show({
							fade_to : {
								duration : moxyfy.Chat.overlay_settings.duration,
								opacity  : 1.0
							},
							callback : function() {
								moxyfy.Chat.List.show_callback('conversations');
							}
						});
					}
					if(conversation_length == 0)
					{
						moxyfy.Chat.List.show_callback('conversations');
					}
				}
			},
			hide : function(settings) {
				if(moxyfy.Chat.List.locked) return;
				if(moxyfy.Chat.List.hidden) return;
				moxyfy.Chat.List.locked = true;
		
				if(typeof settings == 'object' && typeof settings.quick == 'boolean' && settings.quick)
				{
					// hide conversations
					moxyfy.Chat.List.locked = false;
					moxyfy.Chat.List.hidden = true;
				}
				else
				{
					// deactivate button
					moxyfy.Chat.button.deactivate();
					
					// setup locking levers
					moxyfy.Chat.locks['overlay'] = true;
					moxyfy.Chat.locks['conversations'] = true;
					moxyfy.Chat.locks['list'] = true;
			
					// hide conversationss
					var conversation_length = moxyfy.Chat.Conversations.objects.length;
					for(var i=0;i<conversation_length;i++)
					{
						moxyfy.Chat.Conversations.objects[i].win.hide({
							fade_to : {
								duration : moxyfy.Chat.overlay_settings.duration,
								opacity  : 0.0
							},
							callback : function() {
								moxyfy.Chat.List.hide_callback('conversations');
							}
						});
					}
					if(conversation_length == 0)
					{
						moxyfy.Chat.List.hide_callback('conversations');
					}
					
					// hide list
					$(moxyfy.Chat.List.element).fadeTo(moxyfy.Chat.overlay_settings.duration, 0.0, function() {
						moxyfy.Chat.List.hide_callback('list');
						$(this).hide();
						$('img#inner_curve').hide();
					});
		
					// hide overlay
					moxyfy.Chat.overlay.hide({callback: function() { moxyfy.Chat.List.hide_callback('overlay'); }});
				}
			},
			show_callback : function(part) {
				if(part == 'overlay')
				{
					moxyfy.Chat.locks['overlay'] = false;
				}
				else if(part == 'conversations')
				{
					var num_showing = 0;
					var conversation_length = moxyfy.Chat.Conversations.objects.length;
					for(var i=0;i<conversation_length;i++)
					{
						if(moxyfy.Chat.Conversations.objects[i].win.hidden == false)
						{
							num_showing++;
						}
					}
					if(num_showing == conversation_length)
					{
						moxyfy.Chat.locks['conversations'] = false;
					}
				}
				else if(part == 'list')
				{
					moxyfy.Chat.locks['list'] = false;
				}
		
				if(!moxyfy.Chat.locks['overlay'] && !moxyfy.Chat.locks['conversations'] && !moxyfy.Chat.locks['list'])
				{
					// add escape event
					moxyfy.Event.Escape.register('chat', function() {
						moxyfy.Chat.List.hide();
					});

					moxyfy.Chat.List.locked = false;
					moxyfy.Chat.List.hidden = false;
				}
			},
			hide_callback : function(part) {
				if(part == 'overlay')
				{
					moxyfy.Chat.locks['overlay'] = false;
				}
				else if(part == 'conversations')
				{
					var num_hiding = 0;
					var conversation_length = moxyfy.Chat.Conversations.objects.length;
					for(var i=0;i<conversation_length;i++)
					{
						if(moxyfy.Chat.Conversations.objects[i].win.hidden == true)
						{
							num_hiding++;
						}
					}
					if(num_hiding == conversation_length)
					{
						moxyfy.Chat.locks['conversations'] = false;
					}
				}
				else if(part == 'list')
				{
					moxyfy.Chat.locks['list'] = false;
				}
		
				if(!moxyfy.Chat.locks['overlay'] && !moxyfy.Chat.locks['conversations'] && !moxyfy.Chat.locks['list'])
				{
					// del escape event
					moxyfy.Event.Escape.unregister('chat');
		
					moxyfy.Chat.List.locked = false;
					moxyfy.Chat.List.hidden = true;
				}
			},
			resize : function() {
				var page_size = $.get_page_size();

				var height = page_size['window_height'];

				var height = page_size['window_height']
					- $.parse_int($(moxyfy.Chat.List.element).css('borderTopWidth'))
					- $.parse_int($(moxyfy.Chat.List.element).css('borderBottomWidth'));

				$(moxyfy.Chat.List.element).css({height : height+'px'});
			}
		}
	}
});

$.extend(moxyfy.Chat, {
	Conversation : function(id, settings, messages) {
		this.id = id;
		var defaults = {
			top          : 100,
			left         : 100,
			width        : 300,
			height       : 40,
			corner_curve : 7
		};

		this.settings = $.extend(defaults, settings);
		this.messages = $.extend([], messages);

		this.top  = this.settings.top;
		this.left = this.settings.left;
		this.last_from_id = 0;
		this.last_date    = null;

		this.init();
	}
});
$.extend(moxyfy.Chat.Conversation.prototype, {
	init : function() {
		var self = this;
		this.win = new Window({
			name         : 'conversation_'+this.id,
			title        : moxyfy.UserList.get_user(this.id).get('nickname'),
			top          : this.settings.top,
			left         : this.settings.left,
			corner_curve : this.settings.corner_curve,
			draggable    : true,
			containment  : '#'+$(moxyfy.Chat.overlay.element).attr('id'),
			on_focus     : function() { self.on_focus(); },
			on_drag      : function(top, left) { self.on_drag(top, left); },
			on_close     : function() { self.on_close(); }
		});

		// extend right padding so the conversation windows can't overlap the friends list
		//$(self.win.element).css('paddingRight', ($.parse_int($(self.win.element).css('paddingRight'))+200)+'px');

		if(moxyfy.Chat.overlay.hidden)
		{
			this.win.hide();
		}

		this.create();
		
		var messages_length = this.messages.length;
		for(var i=0;i<messages_length;i++)
		{
			this.add_message(this.messages[i], true);
		}
		// automatically scroll to the bottom
		var obj = $('div.messages', this.element)[0];
		obj.scrollTop = obj.scrollHeight;
	},
	create : function() {
		this.element = $('<div></div>')
		.addClass('conversation')
		.append(
			$('<div></div>')
			.addClass('profile user_'+this.id)
			.append(
				$('<div></div>')
				.addClass('avatar')
				.html('<a href="profile/view/id/'+this.id+'"><img class="avatar small framed" src="'+moxyfy.get_user(this.id).data.avatar_url+'" /></a>')
			)
			.append(
				$('<div><div>')
				.addClass('info')
				.append('<div><span class="full_name">'+moxyfy.get_user(this.id).data.full_name+'</span></div>')
				.append('<div><span class="status_image"><img src="skins/'+moxyfy.skin+'/images/icons/chat/status_'+moxyfy.get_user(this.id).data.status+'.png" /></span> <span class="status">'+moxyfy.get_user(this.id).data.status+'</span></div>')
			)
			.append($('<div></div>').addClass('spacer').html('<!-- -->'))
		)
		.append(
			$('<div></div>')
			.addClass('messages')
		)
		.append(
			$('<div></div>')
			.css({display: 'none'})
			.addClass('warning')
		)
		.append(
			$('<div></div>')
			.addClass('conversation_input')
			.append('<textarea rows="1" cols="1"></textarea>')
		);
		
		// append to window body
		$('div.window_body', this.win.element).append(this.element);

		// wysiwyg it
		var self = this;
		$('textarea', this.element).wysiwyg({
			width       : this.settings.width-2,
			height      : this.settings.height,
			smilies     : [],
			show_footer : false,
			resizable   : false,
			scrolling   : true,
			key_funcs   : {
				'enter' : { clear : true, ubbc : true, ubbc_tags : ['b','i','u'], func : function(text) {
					moxyfy.Chat.send_message(self.id, text);
				}}
			}
		});
	},
	add_message : function(message, from_cache) {
		var date;
		// use stored date
		if(from_cache)
		{
			date = new Date(message.date);
		}
		// or use current date
		else
		{
			date = new Date();
			message.date = date.toUTCString();

			this.messages.push(message);
			if(this.messages.length > 5)
			{
				this.messages.shift();
			}
		}

		// keep track of the last date
		if(this.last_date == null || this.last_date.getDate() != date.getDate())
		{
			this.append_date(date);
			this.last_date = date;
		}

		// keep track of last message sender
		if(this.last_from_id != message.from_id)
		{
			this.append_user(message.from_id, (moxyfy.user.id==message.from_id?false:true));
		}
		this.last_from_id = message.from_id;

		// properly format minutes and seconds
		var hours   = date.getHours();
		var minutes = date.getMinutes();
		var ampm    = '';
		if(minutes < 10) minutes = '0'+minutes;
		if(hours < 12)
		{
			ampm = 'am';
		}
		else
		{
			ampm = 'pm';
			hours -= 12;
		}
		var time = hours+':'+minutes+ampm;

		// append message to message area
		this.append_message(message.text, time);
	},
	append_date : function(date) {
		$('div.messages', this.element).append('<div class="date">'+$.get_readable_date({today:true,day_of_week:true,no_year:true})+'</div>');
	},
	append_user : function(user_id, from_you) {
		$('div.messages', this.element).append('<div class="user from'+(from_you?'_you':'')+'">'+moxyfy.get_user(user_id).data.nickname+'</div>');
	},
	append_message : function(message, time) {
		$('div.messages', this.element).append('<div class="message"><div class="body">'+$.ubbc_filter(message,{tag_list:['b','i','u'],wrap_chars:20})+'</div><div class="time">'+time+'</div><div class="spacer"><!-- --></div></div>');
	},
	append_warning : function(message) {
		$('div.messages', this.element).append('<div class="warning">'+message+'</div>');
	},
	on_focus : function() {
		moxyfy.Chat.Conversations.set_active(this.id);
		moxyfy.Chat.Conversations.update_order(this.id);
	},
	on_drag : function(top, left) {
		this.top  = top;
		this.left = left;
		moxyfy.Chat.Conversations.save();
	},
	on_close : function() {
		moxyfy.Chat.Conversations.remove(this.id);
	}
});
/*

var Chat = function(id, settings, messages) {
	this.id = id;
	var defaults = {
		top          : 100,
		left         : 100,
		width        : 300,
		height       : 40,
		corner_curve : 7
	};
	
	this.settings = $.extend(defaults, settings);
	this.messages = $.extend([], messages);

	this.top  = this.settings.top;
	this.left = this.settings.left;
	this.last_from_id = 0;
	this.last_date    = null;

	this.init();
};
$.extend(Chat.prototype, {
	init : function() {
		var self = this;
		this.win = new Window({
			name         : 'chat_'+this.id,
			title        : moxyfy.get_user(this.id).data.nickname,
			top          : this.settings.top,
			left         : this.settings.left,
			corner_curve : this.settings.corner_curve,
			draggable    : true,
			containment  : '#'+$(moxyfy.flist_overlay.element).attr('id'),
			on_focus     : function() { self.on_focus(); },
			on_drag      : function(top, left) { self.on_drag(top, left); },
			on_close     : function() { self.on_close(); }
		});

		// extend right padding so the chat windows can't overlap the friends list
		//$(self.win.element).css('paddingRight', ($.parse_int($(self.win.element).css('paddingRight'))+200)+'px');

		if(moxyfy.flist_overlay.hidden)
		{
			this.win.hide();
		}

		this.create();
		
		var messages_length = this.messages.length;
		for(var i=0;i<messages_length;i++)
		{
			this.add_message(this.messages[i], true);
		}
		// automatically scroll to the bottom
		var obj = $('div.messages', this.element)[0];
		obj.scrollTop = obj.scrollHeight;
	},
	create : function() {
		this.element = $('<div></div>')
		.addClass('chat')
		.append(
			$('<div></div>')
			.addClass('profile user_'+this.id)
			.append(
				$('<div></div>')
				.addClass('avatar')
				.html('<a href="profile/view/id/'+this.id+'"><img class="avatar small framed" src="'+moxyfy.get_user(this.id).data.avatar_url+'" /></a>')
			)
			.append(
				$('<div><div>')
				.addClass('info')
				.append('<div><span class="full_name">'+moxyfy.get_user(this.id).data.full_name+'</span></div>')
				.append('<div><span class="status_image"><img src="skins/'+moxyfy.skin+'/images/icons/chat/status_'+moxyfy.get_user(this.id).data.status+'.png" /></span> <span class="status">'+moxyfy.get_user(this.id).data.status+'</span></div>')
			)
			.append($('<div></div>').addClass('spacer').html('<!-- -->'))
		)
		.append(
			$('<div></div>')
			.addClass('messages')
		)
		.append(
			$('<div></div>')
			.css({display: 'none'})
			.addClass('warning')
		)
		.append(
			$('<div></div>')
			.addClass('chat_input')
			.append('<textarea rows="1" cols="1"></textarea>')
		);
		
		// append to window body
		$('div.window_body', this.win.element).append(this.element);

		// wysiwyg it
		var self = this;
		$('textarea', this.element).wysiwyg({
			width       : this.settings.width-2,
			height      : this.settings.height,
			smilies     : [],
			show_footer : false,
			resizable   : false,
			scrolling   : true,
			key_funcs   : {
				'enter' : { clear : true, ubbc : true, ubbc_tags : ['b','i','u'], func : function(text) {
					moxyfy.send_chat(self.id, text);
				}}
			}
		});
	},
	add_message : function(message, from_cache) {
		var date;
		// use stored date
		if(from_cache)
		{
			date = new Date(message.date);
		}
		// or use current date
		else
		{
			date = new Date();
			message.date = date.toUTCString();

			this.messages.push(message);
			if(this.messages.length > 5)
			{
				this.messages.shift();
			}
		}

		// keep track of the last date
		if(this.last_date == null || this.last_date.getDate() != date.getDate())
		{
			this.append_date(date);
			this.last_date = date;
		}

		// keep track of last message sender
		if(this.last_from_id != message.from_id)
		{
			this.append_user(message.from_id, (moxyfy.user.id==message.from_id?false:true));
		}
		this.last_from_id = message.from_id;

		// properly format minutes and seconds
		var hours   = date.getHours();
		var minutes = date.getMinutes();
		var ampm    = '';
		if(minutes < 10) minutes = '0'+minutes;
		if(hours < 12)
		{
			ampm = 'am';
		}
		else
		{
			ampm = 'pm';
			hours -= 12;
		}
		var time = hours+':'+minutes+ampm;

		// append message to message area
		this.append_message(message.text, time);
	},
	append_date : function(date) {
		$('div.messages', this.element).append('<div class="date">'+$.get_readable_date({today:true,day_of_week:true,no_year:true})+'</div>');
	},
	append_user : function(user_id, from_you) {
		$('div.messages', this.element).append('<div class="user from'+(from_you?'_you':'')+'">'+moxyfy.get_user(user_id).data.nickname+'</div>');
	},
	append_message : function(message, time) {
		$('div.messages', this.element).append('<div class="message"><div class="body">'+$.ubbc_filter(message,{tag_list:['b','i','u'],wrap_chars:20})+'</div><div class="time">'+time+'</div><div class="spacer"><!-- --></div></div>');
	},
	append_warning : function(message) {
		$('div.messages', this.element).append('<div class="warning">'+message+'</div>');
	},
	on_focus : function() {
		moxyfy.set_active_chat(this.id);
		moxyfy.update_chat_order(this.id);
	},
	on_drag : function(top, left) {
		this.top  = top;
		this.left = left;
		moxyfy.save_chats();
	},
	on_close : function() {
		moxyfy.del_chat(this.id);
	}
});
*/

// ContextMenu object
$.extend(moxyfy, {
	context_menu_showing : null
});
var ContextMenu = function(settings) {
	var defaults = {
		options : {	},
		classes : [],
		opacity   : 1.0
	};

	this.settings = $.extend(defaults, settings);
	this.init();
};
$.extend(ContextMenu.prototype, {
	init : function(element) {
		this.hidden = true;
		this.locked = false;

		this.create();

		$('#'+this.id)
			.addClass('context_menu')
			.addClass('context_menu_'+this.index)
			.addClass(this.settings.type+'_context_menu');

		// bind button
		$('#'+this.button.id)
			.addClass('button_'+this.index);

		var self = this;
		this.button.onclick(function() {
			self.toggle();
		});
	},
	create : function() {
		this.element = $('<div></div>')
		.append('<ul></ul>')
		.appendTo(document.body);

		var options_length = this.settings.options.length;
		for(var i=0; i<options_length; i++)
		{
			var option = $('<li></li>');

			if(typeof this.settings.options[i].className == 'string')
			{
				option.addClass(this.settings.options[i].className);
			}

			if(typeof this.settings.options[i].text == 'string')
			{
				option.html(this.settings.options[i].text);
			}

			if(typeof this.settings.options[i].onclick == 'function')
			{
				option.bind('onclick', this.settings.options[i].onclick);
			}
			
			$('ul', this.element).append(option);
		}
		
		var self = this;
		moxyfy.reg_ev_func('mousedown', function(e) {
			console.log('clicked on');
			console.log(e.target);
			self.hide();
		});
		moxyfy.reg_ev_func('right_mouseup', function(e) {
			
		});
	},
	toggle : function() {
		if(this.hidden)
		{
			this.show();
		}
		else
		{
			this.hide();
		}
	},
	show : function() {
		if(!this.hidden) return;
		if(this.locked) return;

		this.hidden = false;
		this.locked = true;

		// if this menu isn't part of the previously opened
		// menu chain, close each part of the chain if applicable
		var found = false;
		var menus_length = (moxyfy.context_menu_showing != null) ? 1 : 0;
		while(!found && menus_length > 0)
		{
			if(this.settings.parent != null && moxyfy.context_menu_showing.index == this.settings.parent.index)
			{
				found = true;
			}
			else
			{
				var menu = moxyfy.context_menu_showing;
				menus_length--;
				menu.hide();
			}
		}

		var menu_width =
			$('#'+this.id).width() +
			$.parse_int($('#'+this.id).css('paddingLeft')) +
			$.parse_int($('#'+this.id).css('paddingRight')) +
			$.parse_int($('#'+this.id).css('borderRightWidth')) +
			$.parse_int($('#'+this.id).css('borderLeftWidth'));

		// set location
		var pos;
		if(this.settings.flush == 'mouse')
		{
			pos = $.get_mouse_pos(e);
		}
		else
		{
			// get the button position
			pos = $.get_pos('#'+this.button.id);
			
			var button_height =
				$('#'+this.button.id).height() +
				$.parse_int($('#'+this.button.id).css('paddingTop')) +
				$.parse_int($('#'+this.button.id).css('paddingBottom')) +
				$.parse_int($('#'+this.button.id).css('borderTopWidth')) +
				$.parse_int($('#'+this.button.id).css('borderBottomWidth'));

			pos.y += button_height;

			if(this.settings.flush == 'right')
			{
				// subtract the width of the menu
				pos.x -= menu_width;

				// add the width of the button
				var button_width =
					$('#'+this.button.id).width() +
					$.parse_int($('#'+this.button.id).css('paddingLeft')) +
					$.parse_int($('#'+this.button.id).css('paddingRight')) +
					$.parse_int($('#'+this.button.id).css('borderRightWidth')) +
					$.parse_int($('#'+this.button.id).css('borderLeftWidth'));

				pos.x += button_width;
			}
		}

		// make sure the menu isn't off the page
		var page_size = $.get_page_size();
		if(pos.x < 5)
		{
			pos.x = 5;
		}
		else if(pos.x + menu_width > page_size['width']-5)
		{
			pos.x = page_size['width'] - menu_width-5;
		}

		$('#'+this.id).css({opacity: this.settings.opacity, top: pos.y+'px', left: pos.x+'px'});

		$('#'+this.id).show();
		moxyfy.context_menu_showing = this;
		
		// activate button
		this.button.activate();
		
		this.locked = false;
	},
	hide : function() {
		if(this.hidden) return;
		if(this.locked) return;
		
		this.locked = true;

		$('#'+this.id).hide();
		this.hidden = true;

		// deactivate button
		this.button.deactivate();

		this.locked = false;
	}
});

$(document.body).bind('mouseup', function(e) {
	e = e || window.event;

	var target = (e.target) ? e.target : e.srcElement;

	var sentinel = false;
	var type = null;
	while(!sentinel)
	{
		if(target == null || target.nodeName.toLowerCase() == 'html' || target.nodeName.toLowerCase() == 'body')
		{
			sentinel = true;
		}
		else if($(target).hasClass('context_menu'))
		{
			type = 'context_menu';
			sentinel = true;
		}
		else if($(target).hasClass('button'))
		{
			type = 'button';
			sentinel = true;
		}
		else
		{
			target = target.parentNode;
		}
	}

	var found = false;
	var menus_length = (moxyfy.context_menu_showing != null) ? 1 : 0;
	while(!found && menus_length > 0)
	{
		if(
			(type == 'button'       && $(target).hasClass('button_'+moxyfy.context_menu_showing.index)) ||
			(type == 'context_menu' && $(target).hasClass('context_menu_'+moxyfy.context_menu_showing.index))
		)
		{
			found = true;
		}
		else
		{
			var menu = moxyfy.context_menu_showing;
			menus_length--;
			menu.hide();
		}
	}
});

$.extend(moxyfy, {
	auto_completer_index : 0,

	AutoCompleter : function(id, settings) {
		var defaults = {
			type      : 'default',
			opacity   : 1.0,
			flush     : 'left', // [left|right|mouse]
			data      : []
		};

		this.id       = id;
		this.settings = $.extend(defaults, settings);
		this.init();

		this.typer = null;
	}
});
$.extend(moxyfy.AutoCompleter.prototype, {
	init : function(element) {
		this.omitted_ids = [];

		this.create();
	},
	create : function() {
		var self = this;

		this.index = moxyfy.auto_completer_index;
		moxyfy.auto_completer_index++;

		// create menu
		$(document.body).append(
			$('<div></div>')
			.attr('id', this.id+'_menu')
			.addClass('auto_completer_menu')
		);

		// create auto_completer
		$('#'+this.id)
		.addClass('auto_completer input button')
		.append(
			$('<span></span>')
			.addClass('tab_catcher')
			.append(
				$('<input>')
				.bind('focus', function(e) {
					self.focus_callback(e);
				})
			)
		);

		this.menu = new moxyfy.Menu(this.id+'_menu', {
			type          : this.settings.type,
			opacity       : this.settings.opacity,
			flush         : this.settings.flush,
			flush_element : $('#'+this.id)[0],
			hide_check    : function(e) { return self.menu_hide_check(e); }
		});
		
		$('.result', this.menu.element).live('mouseover mouseout', function(e) {
			$(this).toggleClass('hover');
		});
		$('.result', this.menu.element).live('click', function(e) {
			$(this).hide();
			
			var parts = $(this).attr('id').split('auto_completer_'+self.index+'_result_');

			self.cancel();
			self.settings.select_callback.apply(this, [self.settings.data[parts[1]]]);
		});

		// create fake input field
		$('#'+this.id).bind('click', function(e) {
			self.focus_callback(e);
		});
	},
	focus_callback : function(e) {
		if(this.typer != null) return;

		var self = this;

		// add input field
		this.typer = $('<div></div>')
		.addClass('typer')
		.append(
			$('<input />')
			.attr('tabIndex', -1)
			.bind('keydown keyup focus', function(e) {
				self.typing_callback(e);
			})
		)
		.appendTo('#'+this.id);

		$('input', this.typer).focus();
	},
	undo_omit_id : function(id) {
		for(var i=0; i<this.omitted_ids.length; i++)
		{
			if(this.omitted_ids[i] == id)
			{
				this.omitted_ids.splice(i, 1);
				return;
			}
		}
	},
	omit_id : function(id) {
		if(!$.in_array(id, this.omitted_ids))
		{
			this.omitted_ids.push(id);
		}
	},
	menu_hide_check : function(e) {
		if(typeof e == 'undefined')
		{
			return true;
		}

		// don't cancel if click was on child of our auto completer
		if($(e.target).childOf($('#'+this.id)[0]))
		{
			return false;
		}
		return true;
	},
	cancel : function() {
		this.menu.hide();
		$('input', this.typer).attr('value','');
		$('input', this.typer).focus();
	},
	typing_callback : function(e) {
		console.log(e.type+': '+e.keyCode);
		if(e.type == 'keydown')
		{
			if($.keycode_equals(e, 'tab'))
			{
				this.cancel();
			}

			return;
		}

		var q = $('input', this.typer).val();
		q = q.trim();

		var results = [];

		if($.keycode_equals(e, 'enter'))
		{
			this.cancel();
			this.settings.select_callback.apply(this);
			return;
		}
		else if($.keycode_equals(e, 'escape'))
		{
			$('input', this.typer).attr('value','');
			q = '';
		}
		else if($.keycode_equals(e, 'tab'))
		{
			this.cancel();
			return;
		}

		// use existing data
		if(typeof this.settings.data == 'object')
		{
			if(q != '')
			{
				var data_length = this.settings.data.length;
				for(var i=0; i<data_length; i++)
				{
					// skip omitted ids
					if(!$.in_array(this.settings.data[i].id, this.omitted_ids))
					{
						var found = false;
						for(var key in this.settings.data[i].search_fields)
						{
							if(this.settings.data[i].search_fields[key].indexOf(q) != -1)
							{
								found = true;
							}
						}
						
						if(found)
						{
							var display = this.settings.data[i].display;
							for(var key in this.settings.data[i].search_fields)
							{
								display = display.replace('{'+key+'}', this.settings.data[i].search_fields[key].replace(''+q+'', '<span class="highlight">'+q+'</span>'));
							}
							results.push('<div class="result" id="auto_completer_'+this.index+'_result_'+i+'">'+display+'</div>');
						}
					}
				}
			}
		}
		// ajax query for data
		else
		{
			
		}

		// display results
		if(results.length > 0)
		{
			$(this.menu.element).html('');
			for(var i=0; i<results.length; i++)
			{
				$(this.menu.element).append(results[i]);
			}
			this.menu.show();
		}
		else
		{
			// if no query, show default text
			if(q == '')
			{
				$(this.menu.element).html(this.settings.prompt);
			}
			// else show no results
			else
			{
				$(this.menu.element).html('No Results');
			}
			this.menu.show();
		}
	}
});

$.extend(moxyfy, {
	prompt_id : null,
	prompt_locking : false,

	prompt_show : function(settings) {
		var defaults = {
			id      : null,
			data    : null,
			title   : null,
			content : null
		};

		settings = $.extend(defaults, settings);

		if(moxyfy.prompt_element == null)
		{
			moxyfy.prompt_create();
		}

		// if prompt_id is not null, restore old prompt content to document
		if(moxyfy.prompt_id != null)
		{
			$('#'+moxyfy.prompt_id).hide();
			$(document.body).append($('#'+moxyfy.prompt_id));
		}

		// show overlay
		moxyfy.prompt_overlay.show();
		moxyfy.prompt_loading();
		// lock hide
		moxyfy.prompt_locked = true;
		setTimeout(function() { moxyfy.prompt_locked = false; }, moxyfy.prompt_overlay_settings.duration);

		// set content
		if(settings.id != null)
		{
			moxyfy.prompt_update_id(settings.title, settings.id);
		}
		else if(settings.content != null)
		{
			moxyfy.prompt_update(settings.title, settings.content);
		}
		else
		{
			$.ajax_request({
				data    : settings.data,
				success : function(data) {
					moxyfy.prompt_update(data.title, data.content.stripScripts());
				},
				error : function(data) {
					var message = '';
					if(typeof data.errors == 'object')
					{
						for(var i=0; i<data.errors.length;i++)
						{
								message += data.errors[i]+'<br />';
						}
					}
					else
					{
						messages += '</strong>';
					}
					moxyfy.prompt_update('An Error Occurred', message);
				}
			});
		}
	},
	prompt_loading : function() {
		moxyfy.prompt_update('','<img src="skins/'+moxyfy.skin+'/images/loading_bar.gif" width="220" height="19" />', true);
	},
	prompt_update_id : function(title, id) {
		moxyfy.prompt_id = id;
		$('div.content', moxyfy.prompt_element).html('');
		$('div.content', moxyfy.prompt_element).append($('#'+id));
		$('#'+id).show();

		moxyfy.prompt_update(title);
	},
	prompt_update : function(title, html, loading) {
		if(typeof title == 'string' && title != '')
		{
			$('div.title', moxyfy.prompt_element).html(title);
			$('div.title', moxyfy.prompt_element).show();
		}
		else
		{
			$('div.title', moxyfy.prompt_element).hide();
		}

		if(typeof html == 'string')
		{
			// reset size and set content
			$('div.content', moxyfy.prompt_element).html(html);
		}

		// resize prompt
		moxyfy.prompt_resize();

		// don't allow escape if loading
		if(typeof loading != 'boolean' || loading == false)
		{
			// add escape functionality
			moxyfy.Event.Escape.register('prompt', function() {
				moxyfy.prompt_hide();
			});
		}
	},
	prompt_resize : function() {
		var page_size = $.get_page_size();

		// max out the width (75%) to allow the contents to stretch as they like
		$(moxyfy.prompt_element).width(page_size['window_width']*.75);

		// show prompt
		moxyfy.prompt_element.show();

		// set container width to content width
		$(moxyfy.prompt_element).width($.get_width($('div.content', moxyfy.prompt_element)));

		var left = page_size['window_width']/2 - ($.get_width(moxyfy.prompt_element)/2);
		var top  = page_size['window_height']/3 - ($.get_height(moxyfy.prompt_element)/2);
		
		// add body margin top
		top += $.parse_int($(document.body).css('marginTop'));

		$(moxyfy.prompt_element)
		.css({
			left : left+'px',
			top  : top+'px'
		});
	},
	prompt_hide : function() {
		if(moxyfy.prompt_locked) {
			if(moxyfy.prompt_locking) return;

			moxyfy.prompt_locking = true;

			setTimeout(function() { moxyfy.prompt_hide(); }, 100);
		}
		moxyfy.prompt_locking = false;
		if(moxyfy.prompt_element == null) return;

		// hide prompt
		moxyfy.prompt_element.hide();

		// hide overlay
		moxyfy.prompt_overlay.hide();
		
		// remove escape functionality
		moxyfy.Event.Escape.unregister('prompt');
	},
	prompt_create : function() {
		// create prompt element
		moxyfy.prompt_element = $('<div></div>')
		.css({
			position : 'absolute',
			display  : 'none',
			zIndex   : 101,
			overflow : 'visible'
		})
		.attr('id', 'prompt')
		.append(
			$('<div></div>')
			.addClass('title round')
			.css({
				display: 'none'
			})
		)
		.append(
			$('<div></div>')
			.addClass('content')
			.css({
				float: 'left'
			})
		)
		.append(
			$('<div></div>')
			.addClass('options')
			.append(
				$('<div></div>')
				.addClass('close')
				.bind('click', function() {
					moxyfy.prompt_hide();
				})
			)
		)
		.appendTo(document.body);

		// create prompt overlay
		moxyfy.prompt_overlay = new Overlay({
			opacity       : moxyfy.prompt_overlay_settings.opacity,
			duration      : moxyfy.prompt_overlay_settings.duration,
			zIndex        : moxyfy.prompt_overlay_settings.zIndex,
			hide_callback : function() { moxyfy.prompt_hide(); }
		});
	}
});

$.extend(moxyfy, {
	Display : {
		month_names : [
			'January',
			'February',
			'March',
			'April',
			'May',
			'June',
			'July',
			'August',
			'September',
			'October',
			'November',
			'December'
		],
		friend_request_link : function(user) {
			if(user.is_friend)
			{
				return '<span>Already friends</span>';
			}
			else if(moxyfy.UserList.is_friend_request(user))
			{
				return moxyfy.Request.sent_text['friend'];
			}
			else
			{
				return '<a href="'+moxyfy.Display.url({app:'user',mod:'friend',act:'add',id:user.id})+'" onclick="moxyfy.Request.show_friend_request_prompt('+user.get('id')+'); return false;">Add as Friend</a>';
			}
		},
		user_send_message_link : function(user) {
			
		},
		image : function(url, settings) {
			var defaults = {
				className : null,
				width     : null,
				height    : null,
				alt       : null,
				title     : null
			};
			settings = $.extend(defaults, settings);

			return '<img src="'+encodeURI(url)+'"'+(settings.className != null ? ' class="'+settings.className+'"' : '')+(settings.width != null ? ' width="'+settings.width+'"' : '')+(settings.height != null ? ' height="'+settings.height+'"' : '')+(settings.alt != null ? ' alt="'+encodeURI(settings.alt)+'"' : '')+(settings.title != null ? ' title="'+encodeURI(settings.title)+'"' : '')+' />';
		},
		user_link : function(user, text) {
			if(typeof text == 'undefined')
			{
				text = moxyfy.Display.user_name(user);
			}

			return '<a href="'+moxyfy.Display.url({app:'user',mod:'profile',act:'view',id:user.id})+'" title="'+user.get('first_name')+' '+user.get('last_name')+'">'+text+'</a>';
		},
		user_avatar_link : function(user, className) {
			return moxyfy.Display.user_link(user, moxyfy.Display.user_avatar(user, className));
		},
		user_avatar : function(user, className) {
			var url = moxyfy.Display.src()+'/scripts/user/get_avatar.php?q='+user.get('id')+'_'+user.get('avatar_version');

			return '<img src="'+url+'" class="avatar'+(typeof className == 'string'?' '+className:'')+'" />';
		},
		user_name : function(user) {
			var nickname = user.get('nickname','');
			return (nickname != '') ? nickname : user.get('first_name')+' '+user.get('last_name');
		},
		url : function(settings) {
			settings = $.extend({}, settings);
			if(typeof settings.app == 'undefined')
			{
				settings.app = 'www';
			}
			if(typeof settings.https == 'undefined')
			{
				settings.https = false;
			}
			var url = 'http'+(settings.https?'s':'')+'://'+settings.app+'.'+moxyfy.domain;
			if(typeof settings.mod != 'undefined')
			{
				url += '/'+settings.mod;
			}
			if(typeof settings.act != 'undefined')
			{
				url += '/'+settings.act;
			}
			
			for(var key in settings)
			{
				if(key != 'https' && key != 'app' && key != 'mod' && key != 'act')
				{
					url += '/'+key+'/'+settings[key];
				}
			}
			
			return url;
		},
		src: function(settings) {
			settings = $.extend({}, settings);
			if(typeof settings.app == 'undefined')
			{
				settings.app = 'www';
			}
			if(typeof settings.https == 'undefined')
			{
				settings.https = false;
			}
			var url = 'http'+(moxyfy.https || settings.https?'s':'')+'://'+settings.app+'.'+moxyfy.domain;
			if(typeof settings.mod != 'undefined')
			{
				url += '/'+settings.mod;
			}
			if(typeof settings.act != 'undefined')
			{
				url += '/'+settings.act;
			}
			
			for(var key in settings)
			{
				if(key != 'https' && key != 'app' && key != 'mod' && key != 'act')
				{
					url += '/'+key+'/'+settings[key];
				}
			}
			
			return url;
		},
		textarea : function(text) {
			text = text.replace(/\n{2,}/,"\n\n");
			return moxyfy.Display.nohtml(text).replace(/\n/g,'<br />');
		},
		nohtml : function(text) {
			return text.replace('>','&gt;').replace('<','&lt;');
		},
		time : function(timestamp) {
			var parts = timestamp.split(' ');
			var date  = parts[0].split('-');
			var time  = parts[1].split(':');

			var this_time = new Date();
			this_time.setYear($.parse_int(date[0]));
			this_time.setMonth($.parse_int(date[1])-1);
			this_time.setDate($.parse_int(date[2]));
			this_time.setHours($.parse_int(time[0])+moxyfy.time_offset);
			this_time.setMinutes($.parse_int(time[1]));
			this_time.setSeconds($.parse_int(time[2]));

			var today = new Date();

			// check if timestamp is today
			if(this_time.getFullYear() == today.getFullYear() && (this_time.getMonth()+1) == (today.getMonth()+1) && this_time.getDate() == today.getDate())
			{
				// how long ago?
				var hours_diff = today.getHours()-this_time.getHours();
				if(hours_diff > 0)
				{
					return hours_diff+' hours ago';
				}

				var minutes_diff = today.getMinutes()-this_time.getMinutes();
				if(minutes_diff > 0)
				{
					if(minutes_diff > 50)
					{
						return 'about an hour ago';
					}
					else
					{
						return minutes_diff+' minutes ago';
					}
				}

				return '1 minute ago';
			}
			else
			{
				var hours    = this_time.getHours();
				var minutes  = this_time.getMinutes();
				var meridiem = 'am';
				if(hours > 12)
				{
					hours -= 12;
					meridiem = 'pm';
				}
				if(minutes < 10)
				{
					minutes = '0'+minutes;
				}
				var time = ' at '+hours+':'+minutes+meridiem;

				// check if timestamp is yesterday
				today.setDate(today.getDate()-1);
				if(this_time.getFullYear() == today.getFullYear() && (this_time.getMonth()+1) == (today.getMonth()+1) && this_time.getDate() == today.getDate())
				{
					return 'yesterday'+time;
				}
				else
				{
					return moxyfy.Display.month_names[this_time.getMonth()]+' '+this_time.getDate()+time;
				}
			}
		}
	}
});

$.extend(moxyfy, {
	Comet : {
		cometd             : null,
		subscriptions      : {},
		meta_subscriptions : [],
		
		init : function() {
			moxyfy.Comet.cometd = $.cometd;
			
			// connect to cometd server
			moxyfy.Comet.subscribe('/meta/handshake', this, moxyfy.Comet.meta_handshake);
			moxyfy.Comet.subscribe('/meta/connect',   this, moxyfy.Comet.meta_connect);

			moxyfy.Comet.cometd.init(document.location.protocol+'//'+document.location.hostname+':'+document.location.port+'/cometd/cometd');

		},
		meta_handshake : function() {
			
		},
		meta_connect : function() {
			
		},
		subscribe : function(channel, scope, callback) {
			moxyfy.Comet.subscriptions[channel] = moxyfy.Comet.cometd.addListener(channel, scope, callback);
		},
		unsubscribe : function(channel) {
			moxyfy.Comet.cometd.unsubscribe(moxyfy.Comet.subscriptions[channel]);
		},
		publish : function(channel, data) {
			moxyfy.Comet.cometd.publish(channel, data);
		},
		batch : function(type) {
			if(typeof type == 'undefined') return;
			
			if(type == 'start')
			{
				moxyfy.Comet.cometd.startBatch();
			}
			else
			{
				moxyfy.Comet.cometd.endBatch();
			}
		}
	}
});

$.extend(moxyfy, {
	Event : {
		callbacks : {},

		register : function(event_name, callback) {
			if(typeof moxyfy.Event.callbacks[event_name] != 'object')
			{
				moxyfy.Event.callbacks[event_name] = new Array();
			}
			moxyfy.Event.callbacks[event_name].push(callback);
		},
		trigger : function(event_name, e) {
			if(typeof moxyfy.Event.callbacks[event_name] == 'object' && moxyfy.Event.callbacks[event_name] != null)
			{
				var callbacks_length = moxyfy.Event.callbacks[event_name].length;
				for(var i=0;i<callbacks_length;i++)
				{
					moxyfy.Event.callbacks[event_name][i].apply(this, [e]);
				}
			}
		},
		
		Escape : {
			stack : [],
			index : {},
			
			register : function(name, callback) {
				moxyfy.Event.Escape.index[name] = moxyfy.Event.Escape.stack.length;
				moxyfy.Event.Escape.stack.push(callback);
			},
			unregister : function(name) {
				if(typeof moxyfy.Event.Escape.index[name] == 'number' && moxyfy.Event.Escape.index[name] != -1)
				{
					moxyfy.Event.Escape.stack.splice(moxyfy.Event.Escape.index[name], 1);
					moxyfy.Event.Escape.index[name] = -1;
				}
			},
			trigger : function() {
				var index = moxyfy.Event.Escape.stack.length-1;
				if(index > -1)
				{
					moxyfy.Event.Escape.stack.pop().apply(this);
					// set index to -1 for func
					for(key in moxyfy.Event.Escape.index)
					{
						if(moxyfy.Event.Escape.index[key] == index)
						{
							moxyfy.Event.Escape.index[key] = -1;
						}
					}
				}
			}
		}
	}
});

$(document).ready(function() {
	// bind resize event
	$(window).bind('resize', function(e) {
		moxyfy.Event.trigger('resize');
	});

	// bind click events
	$(window).bind('mousedown mouseup click dblclick', function(e) {
		var click_type = $.get_click_type(e);

		// trigger both events (example: click and left_click)
		moxyfy.Event.trigger(e.type, e);
		moxyfy.Event.trigger(click_type+'_'+e.type, e);
	});

	// Kill all escape button presses as these can
	//   potentially cancel ajax calls.
	// Other escape event bindings should still proc.
	$(document).bind('keydown', function(e) {
		if($.keycode_equals(e, 'escape'))
		{
			moxyfy.Event.Escape.trigger();
			$.disable_event(e);
			return false;
		}
	});
});

var Window = function(settings) {
	var defaults = {
		name           : '',
		top            : 100,
		left           : 100,
		title          : '',
		content        : '',
		remote_content : {},
		corner_curve   : 5
	};

	this.settings = $.extend(defaults, settings);
	// if no name given, provide a random unique name
	if(this.settings.name == '')
		this.settings.name = (Math.round((Math.random()*99999)+1));
	this.init();
};
$.extend(Window.prototype, {
	init : function() {
		this.create();
	},
	create : function() {
		var self = this;

		if(!$.isIE())
		{
			this.element = $('<div></div>')
			.css({position: 'fixed', left: this.settings.left+'px', top: this.settings.top+'px'})
			.attr('id', 'window_'+this.settings.name)
			.addClass('window')
			.append(
				$('<div></div>')
				.addClass('window_head handle')
				.append(
					$('<div></div>')
					.addClass('title')
					.css({float: 'left'})
					.html(this.settings.title)
				)
				.append(
					$('<div></div>')
					.addClass('options')
					.css({float: 'right'})
					.append(
						$('<div></div>')
						.addClass('close')
					)
				)
				.append($('<div></div>').addClass('spacer').html('<!-- -->'))
			)
			.append(
				$('<div></div>')
				.addClass('window_body')
			)
			.append(
				$('<div></div>')
				.addClass('foot')
			)
			.appendTo(document.body);
		}
		// IE insists on being so completely backwards in how
		// it renders the page that we are forced to have 2 different
		// ways to display this window...
		else
		{
			this.element = $('<div></div>')
			.css({position: 'fixed', left: this.settings.left+'px', top: this.settings.top+'px'})
			.attr('id', 'window_'+this.settings.name)
			.addClass('window')
			.append(
				$('<div></div>')
				.addClass('head handle')
				.css({textAlign: 'right'})
				.append(
					$('<div></div>')
					.addClass('title')
					.css({float: 'left'})
					.html(this.settings.title)
				)
				.append(
					$('<div></div>')
					.addClass('options')
					.append(
						$('<div></div>')
						.addClass('close')
					)
				)
				.append($('<div></div>').addClass('spacer').html('<!-- -->'))
			)
			.append(
				$('<div></div>')
				.addClass('body')
			)
			.append(
				$('<div></div>')
				.addClass('foot')
			)
			.appendTo(document.body);
		}

		// on_close
		$('div.options div.close', this.element).bind('click', function() {
			self.on_close();
		});

		// enable drag
		if(this.settings.draggable)
		{
			// containment
			if(typeof this.settings.containment == 'string')
			{
				// handle
				$(this.element).draggable({
					handle      : '.handle',
					containment : this.settings.containment
				});
			}
			// no containment
			else
			{
				// handle
				$(this.element).draggable({
					handle      : '.handle'
				});
			}

			// on_start
			$(this.element).bind('dragstart', function() {
				self.on_start();
			});

			// on_drag
			$(this.element).bind('drag', function() {
				self.on_drag();
			});

			// on_stop
			$(this.element).bind('dragstop', function() {
				self.on_stop();
			});
		}
		
		// on_focus
		$(this.element).bind('mousedown', function(e) {
			// don't focus on close
			if(!$(e.target).attr('class').match('close'))
			{
				self.on_focus();
			}
		});

		// IE does not like transparency after a window is hidden
		// or even moved off screen (with negative 'left' value)
		// plus it mis-interprets widths and condenses the header
		// where the corners are going...
		// which botches any hope for rounded corners, sorry IE
/*		if(this.settings.corner_curve > 0 && !$.isIE())
		{
			// head must have the element's top border style
			// for head's rounded corners to work
			$('div.head', this.element).css({borderTopWidth: $(this.element).css('borderTopWidth')});
			$('div.head', this.element).css({borderTopColor: $(this.element).css('borderTopColor')});
			$('div.head', this.element).css({borderTopStyle: $(this.element).css('borderTopStyle')});

			$('div.head', this.element).corner({
				tl        : { radius: this.settings.corner_curve },
				tr        : { radius: this.settings.corner_curve },
				bl        : false,
				br        : false,
				antiAlias : true,
				autoPad   : true
			});
		}

		// fix top margin
		if(this.settings.corner_curve > 0 && !$.isIE())
		{
			$(this.element).css({marginTop: (this.settings.corner_curve-1)+'px'});
		}
*/
	},
	show : function(settings) {
		var defaults = {};
		settings = $.extend(defaults, settings);

		if(typeof settings.fade_to == 'object')
		{
			var self = this;
			$(this.element).css('opacity', 0);
			$(this.element).css({top: ($.parse_int($(this.element).css('top'))+10000)+'px'});
			$(this.element).css({left: ($.parse_int($(this.element).css('left'))+10000)+'px'});
			$(this.element).fadeTo(settings.fade_to['duration'], settings.fade_to['opacity'], function() {
				self.hidden = false;
				if(typeof settings.callback == 'function')
				{
					settings.callback.apply(this);
				}
			});
		}
		else
		{
			$(this.element).css('opacity', 1);
			$(this.element).css({top: ($.parse_int($(this.element).css('top'))+10000)+'px'});
			$(this.element).css({left: ($.parse_int($(this.element).css('left'))+10000)+'px'});
			if(typeof settings.callback == 'function')
			{
				settings.callback.apply(this);
			}
		}

		this.check_position();
	},
	hide : function(settings) {
		var defaults = {};
		settings = $.extend(defaults, settings);
		
		if(typeof settings.fade_to == 'object')
		{
			var self = this;
			$(this.element).fadeTo(settings.fade_to['duration'], settings.fade_to['opacity'], function() {
				self.hidden = true;
				$(this).css({top: ($.parse_int($(this).css('top'))-10000)+'px'});
				$(this).css({left: ($.parse_int($(this).css('left'))-10000)+'px'});
				if(typeof settings.callback == 'function')
				{
					settings.callback.apply(this);
				}
			});
		}
		else
		{
			$(this.element).css({top: ($.parse_int($(this.element).css('top'))-10000)+'px'});
			$(this.element).css({left: ($.parse_int($(this.element).css('left'))-10000)+'px'});
			if(typeof settings.callback == 'function')
			{
				settings.callback.apply(this);
			}
		}
	},
	// checks to make sure the window isn't off the screen
	check_position : function() {
		if(typeof this.settings.containment)
		{
			page_size = {
				window_width  : $(this.settings.containment).width(),
				window_height : $(this.settings.containment).height()
			};
		}
		else
		{
			var page_size = $.get_page_size();
		}

		var left  = $.parse_int($(this.element).css('left'));
		var width = $(this.element).width() + $.parse_int($(this.element).css('paddingLeft')) + $.parse_int($(this.element).css('paddingRight')) + $.parse_int($(this.element).css('marginLeft')) + $.parse_int($(this.element).css('marginRight')) + $.parse_int($(this.element).css('borderLeftWidth')) + $.parse_int($(this.element).css('borderRightWidth'));
		// if the window clips the right side of the window, move it
		var new_left = (width+left) > page_size['window_width'] ? page_size['window_width']-width : left;
		if(new_left < 0)
		{
			new_left = 0;
		}
		this.settings.left = new_left;
		$(this.element).css({left: new_left+'px'});

		var top    = $.parse_int($(this.element).css('top'));
		var height = $(this.element).height() + $.parse_int($(this.element).css('paddingTop')) + $.parse_int($(this.element).css('paddingBottom')) + $.parse_int($(this.element).css('marginTop')) + $.parse_int($(this.element).css('marginBottom')) + $.parse_int($(this.element).css('borderTopWidth')) + $.parse_int($(this.element).css('borderBottomWidth'));
		// if the window clips the right side of the window, move it
		var new_top = (height+top) > page_size['window_height'] ? page_size['window_height']-height : top;
		if(new_top < 0)
		{
			new_top = 0;
		}
		this.settings.top = new_top;
		$(this.element).css({top: new_top+'px'});

		// as good as a drag if it moved
		this.on_drag();
	},
	on_close : function() {
		if(typeof this.settings.on_close == 'function')
		{
			this.settings.on_close.apply(this, []);
		}

		$(this.element).hide();
		$(this.element).remove();
	},
	on_start : function() {
		if(typeof this.settings.on_start == 'function')
		{
			this.settings.on_start.apply(this, []);
		}
	},
	on_drag : function() {
		this.settings.top  = $.parse_int($(this.element).css('top'));
		this.settings.left = $.parse_int($(this.element).css('left'));

		if(typeof this.settings.on_drag == 'function')
		{
			this.settings.on_drag.apply(this, [this.settings.top, this.settings.left]);
		}
	},
	on_stop : function() {
		if(typeof this.settings.on_stop == 'function')
		{
			this.settings.on_stop.apply(this, []);
		}
	},
	on_focus : function() {
		if(typeof this.settings.on_focus == 'function')
		{
			this.settings.on_focus.apply(this, []);
		}
	}
});


