/*
	jQuery Chat
	A jQuery based integrated chat system
	Version 0.1
	Writen by Phil Bayfield
	Copyright 2009 Pimped Entertainment Limited
*/
(function($) {

	/* Private variables */
	var settings;
	var obj_bar;
	var obj_status;
	var obj_status_img;
	var obj_status_txt;
	var obj_status_menu;
	var obj_status_links;
	var obj_content;
	var obj_friends;
	var schedulers = new Array();
	var channels = new Array();
	var status;
	var active;
	var updatetime = 0;
	var idle_timer;
	var idle_status = 'normal';

	/* Main plugin function */
	$.chat = function(options) {
		/* Set options */
		setOptions(options);
		/* Render main components */
		renderMain();
		/* Set main event handlers */
		setHandlers();
		/* Get/set online status */
		getStatus(function(statustxt) {
			updateStatus(statustxt);
			if(statustxt != 'offline') {
				/* Go online, as this is probably a page reload lets force use of cookies for speed */
				goOnline(true, true);
			}
			/* Enable idle monitor if online */
			if(status == 'online') {
				startIdleTimer(settings.idle_timeout, function() {
					setStatus('away', function(statustxt) {
						updateStatus(statustxt);
					}, function() {});
				}, function() {
					setStatus('online', function(statustxt) {
						updateStatus(statustxt);
					}, function() {});
				});
			}
		}, function() {
			updateStatus('offline');
			alert('Unable to get your chat status, chat is now offline.');
		});
	};
	
	/* Default settings */
	$.chat.defaults = {
		obj_name:      'chatroom',           /* The ID of the container */
		cookie_prefix: 'JQCHAT',             /* The prefix used for any cookies */
		url_base:      '/ajax/chat/',        /* This is the URL that will be used for API calls */
		url_img:       '/images/',           /* The URL where most images will be found */
		url_avatar:    '/images/avatars/',   /* The URL where avatars are found */
		url_smilies:   '/images/smilies/',   /* The URL where smilies are found */
		idle_timeout:  120000,               /* The time to wait before automatically going away */
		url_friends:   '/friends/',          /* The URL that the user can manage their friends list */
		img_load:      'loader_sm.gif',      /* The filename of the small loading image */
		img_load_big:  'loader_big.gif',     /* The filename of the big loading image */
		img_online:    'status_online.png',  /* The filename of the online status image */
		img_offline:   'status_offline.png', /* The filename of the offline status image */
		img_away:      'status_away.png',    /* The filename of the away status image */
		img_busy:      'status_busy.png',    /* The filename of the busy status image */
		img_friends:   'user_green.png',     /* The filename of the friends image */
		img_faway:     'away.png',           /* The filename of the friends away image */
		img_fbusy:     'busy.png'            /* The filename of the friends busy image */
	};
	
	/* Smilies */
	$.chat.smilies = {
		smile_1: {text: ':)',   image: 'smile.gif'},
		smile_2: {text: ':-)',  image: 'smile.gif'},
		wink_1:  {text: ';)',   image: 'wink.gif'},
		wink_2:  {text: ';-)',  image: 'wink.gif'},
		happy_1: {text: ':D',   image: 'happy.gif'},
		happy_2: {text: ':-D',  image: 'happy.gif'},
		sad_1:   {text: ':(',   image: 'sad.gif'},
		sad_2:   {text: ':-(',  image: 'sad.gif'},
		conf_1:  {text: ':s',   image: 'confused.gif'},
		conf_2:  {text: ':-s',  image: 'confused.gif'},
		shock_1: {text: ':o',   image: 'shock.gif'},
		shock_2: {text: ':-o',  image: 'shock.gif'},
		razz_1:  {text: ':p',   image: 'razz.gif'},
		razz_2:  {text: ':p',   image: 'razz.gif'},
		cry:     {text: ':\'(', image: 'cry.gif'},
		redf_1:  {text: ':$',   image: 'redface.gif'},
		redf_2:  {text: ':-$',  image: 'redface.gif'},
		roll_1:  {text: '8)',   image: 'rolleyes.gif'},
		roll_2:  {text: '8-)',  image: 'rolleyes.gif'},
		neut_1:  {text: ':|',   image: 'neutral.gif'},
		neut_2:  {text: ':-|',  image: 'neutral.gif'},
		cool:    {text: '(H)',  image: 'cool.gif'}
	};

	/* Function to go online */
	function goOnline(allowcookies, noajax) {
		renderFriends();
		/* Get/update friends */
		getFriends(function(data) {
			updateFriends(data);
		}, function() {}, allowcookies, noajax);
		/* Get/update channels */
		getChannels(function(data) {
			updateChannels(data);
		}, function() {}, allowcookies, noajax);
		/* Get updates and set scheduler */
		getUpdates(function(data) {
			updateAll(data);
		}, function() {});
		startScheduler(function() {
			getUpdates(function(data) {
				updateAll(data);
			}, function() {});
		}, 5000);
	}
	
	/* Function to go offline */
	function goOffline() {
		stopScheduler();
		obj_content.empty();
		channels = new Array();
		updatetime = 0;
	}
	
	/* Schedule a task */
	function startScheduler(callback, delay, id) {
		if(id == undefined) {
			id = schedulers.length;
		}
		schedulers[id] = setTimeout(function() {
			callback();
			startScheduler(callback, delay, id);
		}, delay);
		return id;
	}
	
	/* Stop a task or all tasks */
	function stopScheduler(id) {
		if(id == undefined) {
			for(var i in schedulers) {
				clearTimeout(schedulers[i]);
			}
			schedulers = new Array();
		} else {
			clearTimeout(schedulers[id]);
			delete schedulers[id];
		}
	}
	
	/* Start idle timer */
	function startIdleTimer(timer, idlefunc, onlinefunc) {
		/* Set initial timer */
		idle_timer = setTimeout(function() {
			idle_status = 'idle';
			idlefunc();
		}, timer);
		/* Set events to handle idle/online functions */
		$(document).bind('mousemove keydown mousewheel mousedown', function() {
			if(idle_status == 'normal') {
				clearTimeout(idle_timer);
			} else {
				idle_status = 'normal';
				onlinefunc();
			}
			idle_timer = setTimeout(function() {
				idle_status = 'idle';
				idlefunc();
			}, timer);
		});
	}
	
	/* Stop idle timer */
	function stopIdleTimer() {
		clearTimeout(idle_timer);
		$(document).unbind('mousemove keydown mousewheel mousedown');
	}

	/* Merge options with default settings */
	function setOptions(options) {
		settings = jQuery.extend({}, $.chat.defaults, options);
	}
	
	/* Setup event handlers */
	function setHandlers() {
		/* Recenter on resize  */
		$(window).resize(function() {
			center();
		});
		/* Click event for status menu */
		obj_status.click(function(event) {
			if(obj_status_menu.is(':hidden')) {
				clearActiveChannel();
				obj_status_menu.fadeIn(200);
				obj_status.addClass('highlight');
			} else {
				obj_status_menu.fadeOut(200);
				obj_status.removeClass('highlight');
			}
			event.stopPropagation();
		});
		/* Click event on status menu */
		obj_status_menu.click(function(event) {
			event.stopPropagation();
		});
		/* Click events on status menu links */
		obj_status_links.each(function() {
			$(this).click(function() {
				obj_status_menu.fadeOut(200);
				obj_status.removeClass('highlight');
				obj_status_img.html('<img src="' + settings.url_img + settings.img_load + '" border="0" />');
				setStatus($(this).attr('rel'), function(statustxt) {
					if(status == 'offline') {
						goOnline();
					}
					updateStatus(statustxt);
					/* Enable idle monitor if online */
					if(status == 'online') {
						startIdleTimer(settings.idle_timeout, function() {
							setStatus('away', function(statustxt) {
								updateStatus(statustxt);
							}, function() {});
						}, function() {
							setStatus('online', function(statustxt) {
								updateStatus(statustxt);
							}, function() {});
						});
					} else {
						stopIdleTimer();
					}
					/* If now offline kill everything else */
					if(status == 'offline') {
						goOffline();
					}
				}, function() {
					updateStatus(status);
					alert('Unable to change status, please try again.');
				});
			});
		});
		/* Any click on page to close windows */
		$(document).click(function() {
			if(obj_status_menu.is(':visible')) {
				obj_status_menu.fadeOut(200);
				obj_status.removeClass('highlight');
			}
			clearActiveChannel();
		});
	}
	
	/* Set online status */
	function setStatus(status, callback, errorfunc) {
		ajax(settings.url_base + 'request/setstatus/data/' + status, function(data) {
			if(settings['img_' + data.status] !== undefined) {
				callback(data.status);
			} else {
				errorfunc();
			}
		}, function() {
			errorfunc();
		});
	}
	
	/* Set active channel */
	function setActiveChannel(obj_info) {
		active = obj_info;
	}
	
	/* Clear active channel */
	function clearActiveChannel() {
		if(active != undefined) {
			active.content.fadeOut(200);
			active.internal.removeClass('highlight');
			active = undefined;
		}
	}
	
	/* Get online status */
	function getStatus(callback, errorfunc) {
		var statuscookie = jQuery.cookie(settings.cookie_prefix + '_STATUS');
		/* If there is cookie data validate it */
		if(statuscookie != null) {
			var curstatus = convToJSON(statuscookie);
			if(settings['img_' + curstatus.status] !== undefined) {
				callback(curstatus.status);
				return;
			}
		}
		/* If no cookie data or invalid data send ajax request */
		ajax(settings.url_base + 'request/getstatus', function(data) {
			if(settings['img_' + data.status] !== undefined) {
				callback(data.status);
			} else {
				errorfunc();
			}
		}, function() {
			errorfunc();				
		});
	}
	
	/* Get friends list */
	function getFriends(callback, errorfunc, allowcookie, noajax) {
		if(allowcookie === true) {
			var friendscookie = jQuery.cookie(settings.cookie_prefix + '_FRIENDS');
			/* If there is cookie data validate it */
			if(friendscookie != null) {
				callback(convToJSON(friendscookie));
				return;
			}
		}
		/* If no cookie data or invalid data send ajax request */
		if(noajax !== true) {
			ajax(settings.url_base + 'request/getfriends', function(data) {
				callback(data);
			}, function() {
				errorfunc();
			});
		}
	}
	
	/* Get chatroom channels */
	function getChannels(callback, errorfunc, allowcookie, noajax) {
		if(allowcookie === true) {
			var channelscookie = jQuery.cookie(settings.cookie_prefix + '_CHANNELS');
			/* If there is cookie data validate it */
			if(channelscookie != null) {
				callback(convToJSON(channelscookie));
				return;
			}
		}
		/* If no cookie data or invalid data send ajax request */
		if(noajax !== true) {
			ajax(settings.url_base + 'request/getchannels', function(data) {
				callback(data, allowcookie);
			}, function() {
				errorfunc();
			});
		}
	}
	
	/* Get channel updates */
	function getUpdates(callback, errorfunc) {
		ajax(settings.url_base + 'request/getupdates/last/' + updatetime, function(data) {
			callback(data);
		}, function() {
			errorfunc();
		});
	}
	
	/* Update status */
	function updateStatus(newstatus) {
		obj_status_img.html('<img src="' + settings.url_img + settings['img_' + newstatus] + '" border="0" />');
		obj_status_txt.html(ucfirst(newstatus));
		jQuery.cookie(settings.cookie_prefix + '_STATUS', '{status:\'' + newstatus + '\'}', {expires: 1, path: '/'});
		status = newstatus;
	}
	
	/* Update friends */
	function updateFriends(data) {
		if(data.fields == undefined || data.fields == 0) {
			obj_friends.html('<div style="padding: 5px; text-align: center;">No friends are online</div>');
		} else {
			var list_html = '<ul>';
			for(var i in data) {
				if(data[i].name != undefined) {
					list_html += '<li><a><img src="' + settings.url_avatar + data[i].filename + '" class="avatar" />';
					if(data[i].chat_status == 'away') {
						list_html += '<img src="' + settings.url_img + settings.img_faway + '" class="status" />';
					}
					if(data[i].chat_status == 'busy') {
						list_html += '<img src="' + settings.url_img + settings.img_fbusy + '" class="status" />';
					}
					list_html += ' ' + data[i].name + '</a></li>';
				}
			}
			list_html += '</ul>';
			obj_friends.html(list_html);
		}
		var friendstring = convToString(data);
		if(friendstring.length < 2000) {
			jQuery.cookie(settings.cookie_prefix + '_FRIENDS', friendstring, {expires: 1, path: '/'});
		} else {
			jQuery.cookie(settings.cookie_prefix + '_FRIENDS', null, {path: '/'});
		}
	}
	
	/* Update channels */
	function updateChannels(data, allowcookie) {
		if(data.fields == undefined || data.fields == 0) {
			return;
		} else {
			for(var i in data) {
				if(data[i].id != undefined) {
					id = data[i].id;
					if(channels[id] == undefined) {
						channels[id] = renderChannel(id, data[i].name, data[i].image, allowcookie);
					}
				}
			}
		}
		jQuery.cookie(settings.cookie_prefix + '_CHANNELS', convToString(data), {expires: 1, path: '/'});
	}
	
	/* Update messages */
	function updateMessages(data) {
		if(data.fields == undefined || data.fields == 0) {
			return;
		} else {
			for(var i in data) {
				if(channels[i] != undefined && channels[i].messages != undefined) {
					channels[i].messages.html(renderMessages(data[i])).scrollTop(500);
					if(updatetime != 0 && channels[i].content.is(':hidden')) {
						channels[i].internal.addClass('alert');
					}
				}
			}
		}
	}
	
	/* Update everything! */
	function updateAll(data) {
		for(var i in data) {
			switch(i) {
				case 'friends':
					updateFriends(data[i]);
					break;
				case 'channels':
					updateChannels(data[i]);
					break;
				case 'messages':
					updateMessages(data[i]);
					break;
				case 'time':
					dotimeupdate = data[i];
					break;
			}
		}
		/* Do update time last */
		if(dotimeupdate != undefined) {
			updatetime = dotimeupdate;
		}
	}

	/* Render chat bar and components and create object references */
	function renderMain() {
		/* Render the bar */
		var holder = '<div id="' + settings.obj_name + '"></div>';
		$('body').append(holder);
		obj_bar = $('#' + settings.obj_name);
		/* Render the status button */
		var status = '<div id="' + settings.obj_name + '_status"><div id="' +
		              settings.obj_name + '_status_img"><img src="' + settings.url_img +
					  settings.img_load + '" border="0" /></div><div id="' +
					 settings.obj_name + '_status_txt">Loading</div></div>';
		obj_bar.append(status);
		obj_status = $('#' + settings.obj_name + '_status');
		obj_status_img = $('#' + settings.obj_name + '_status_img');
		obj_status_txt = $('#' + settings.obj_name + '_status_txt');		
		/* Render the status menu */
		var statusmenu = '<div id="' + settings.obj_name + '_status_menu"><span class="' +
		                 settings.obj_name + '_menu_heading">Status</span><ul class="' +
						 settings.obj_name + '_menu_list"><li class="' + settings.obj_name +
						 '_menu_item"><a rel="online"><img src="' + settings.url_img +
						 settings.img_online + '" /> Online</a></li><li class="' + settings.obj_name +
						 '_menu_item"><a rel="busy"><img src="' + settings.url_img +
						 settings.img_busy + '" /> Busy</a></li><li class="' + settings.obj_name +
						 '_menu_item"><a rel="away"><img src="' + settings.url_img +
						 settings.img_away + '" /> Away</a></li><li class="' + settings.obj_name +
						 '_menu_item"><a rel="offline"><img src="' + settings.url_img +
						 settings.img_offline + '" /> Offline</a></li></ul></div>';
		obj_bar.append(statusmenu);
		obj_status_menu = $('#' + settings.obj_name + '_status_menu');
		obj_status_links = $('#' + settings.obj_name + '_status_menu a');
		/* Add a content div for everything else */
		var content = '<div id="' + settings.obj_name + '_content"></div>';
		obj_bar.append(content);
		obj_content = $('#' + settings.obj_name + '_content');
		/* Call center function to setup initial sizes etc */
		center();
	}
	
	/* Render friends list */
	function renderFriends() {
		var friends = '<div id="' + settings.obj_name + '_friends"><div id="' +
		              settings.obj_name + '_friends_img"><img src="' + settings.url_img +
					  settings.img_friends + '" border="0" /></div><div id="' + settings.obj_name +
		              '_friends_txt">Friends</div></div>';
		obj_content.append(friends);
		var friendslist = '<div id="' + settings.obj_name + '_friends_list"><span class="' +
		                 settings.obj_name + '_friends_heading">Friends</span><div id="' + 
						 settings.obj_name + '_friends_content"><div style="padding: 3px; text-align: center;"><img src="' +
						 settings.url_img + settings.img_load_big + '" border="0" /><br />Loading friends...</div></div><span class="' + 
						 settings.obj_name + '_friends_footer"><a href="' + settings.url_friends +
						 '">Add/remove friends</a></span></div>';
		obj_content.append(friendslist);
		/* Apply hidden/visible state from cookie */
		var statecookie = jQuery.cookie(settings.cookie_prefix + '_FRIENDS_STATE');
		if(statecookie == 'visible') {
			$('#' + settings.obj_name + '_friends_list').show(0);
			$('#' + settings.obj_name + '_friends').addClass('highlight');
		}
		/* Add click function of friends button */
		$('#' + settings.obj_name + '_friends').click(function(event) {
			if($('#' + settings.obj_name + '_friends_list').is(':hidden')) {
				$('#' + settings.obj_name + '_friends_list').fadeIn(200);
				$('#' + settings.obj_name + '_friends').addClass('highlight');
				jQuery.cookie(settings.cookie_prefix + '_FRIENDS_STATE', 'visible', {expires: 1, path: '/'});
			} else {
				$('#' + settings.obj_name + '_friends_list').fadeOut(200);
				$('#' + settings.obj_name + '_friends').removeClass('highlight');
				jQuery.cookie(settings.cookie_prefix + '_FRIENDS_STATE', 'hidden', {expires: 1, path: '/'});
			}
		});
		obj_friends = $('#' + settings.obj_name + '_friends_content');
	}
	
	/* Render a new channel */
	function renderChannel(id, name, image, allowcookie) {
		/* Render placeholder and button divs */
		var channel = '<div id="' + settings.obj_name + '_channel_' + id + '" class="' + settings.obj_name +
		              '_channel"><div id="' + settings.obj_name + '_channel_' + id + '_int" class="' +
					  settings.obj_name + '_channel_int"><div class="' + settings.obj_name +
					  '_channel_img"><img src="' + settings.url_img + image + '" border="0" /></div><div class="'
					  + settings.obj_name + '_channel_txt">' + name + '</div></div></div>';
		obj_content.append(channel);
		var obj_btn = $('#' + settings.obj_name + '_channel_' + id);
		var obj_int = $('#' + settings.obj_name + '_channel_' + id + '_int');
		/* Check for message history */
		var messages = null;
		if(allowcookie === true) {
			var msgcookie = jQuery.cookie(settings.cookie_prefix + '_MESSAGES_CH' + id);
			if(msgcookie != null) {
				messages = renderMessages(convToJSON(msgcookie));
			}
		}
		if(messages == null) {
			messages = '<div style="padding: 20px; text-align: center;"><img src="' + settings.url_img +
					   settings.img_load_big + '" border="0" /><br />Loading messages...</div>';
		}
		/* Render message box */
		var content = '<div id="' + settings.obj_name + '_channel_' + id + '_content" class="' + settings.obj_name +
					  '_channel_content"><div class="' + settings.obj_name + '_channel_title">Chatroom: ' + name +
					  '</div><div id="' + settings.obj_name + '_channel_' + id + '_messages" class="' +
					  settings.obj_name + '_channel_messages">' + messages + '</div><div class="' +
					  settings.obj_name + '_channel_send"><form id="' + settings.obj_name + '_channel_' + id +
					  '_form"><input id="' + settings.obj_name + '_channel_' + id +
					  '_message" type="text" name="message" class="' + settings.obj_name +
					  '_channel_input" autocomplete="off" /><input id="' + settings.obj_name + '_channel_' + id +
					  '_send" type="submit" value="Send" class="' + settings.obj_name + '_channel_button" /></form></div></div>';
		obj_btn.append(content);
		var obj_cont = $('#' + settings.obj_name + '_channel_' + id + '_content');
		var obj_msgs = $('#' + settings.obj_name + '_channel_' + id + '_messages');
		/* Add click function for channel button */
		obj_btn.click(function(event) {
			if(obj_cont.is(':hidden')) {
				if(obj_status_menu.is(':visible')) {
					obj_status_menu.fadeOut(200);
					obj_status.removeClass('highlight');
				}
				clearActiveChannel();
				setActiveChannel({internal: obj_int, content: obj_cont});
				obj_cont.fadeIn(200);
				obj_int.addClass('highlight');
				obj_int.removeClass('alert');
				obj_msgs.scrollTop(500);
				event.stopPropagation();
			} else {
				clearActiveChannel();
			}
		});
		obj_cont.click(function(event) {
			event.stopPropagation();
		});
		$('#' + settings.obj_name + '_channel_' + id + '_form').submit(function(event) {
			$('#' + settings.obj_name + '_channel_' + id + '_send').attr('disabled', true);
			sendMessage({channel: id, message: $('#' + settings.obj_name + '_channel_' + id + '_message').val()}, function(data) {
				$('#' + settings.obj_name + '_channel_' + id + '_message').val('');
				$('#' + settings.obj_name + '_channel_' + id + '_send').attr('disabled', false);
			}, function() {
				alert('Sending message failed, please try again.');
				$('#' + settings.obj_name + '_channel_' + id + '_send').attr('disabled', false);
			});
			return false;
		});
		return {internal: obj_int, content: obj_cont, messages: obj_msgs};
	}
	
	/* Render messages */
	function renderMessages(data) {
		var html = '';
		/* Compile HTML */
		for(var i in data) {
			if(data[i].message != undefined) {
				if(data[i].name != undefined) {
					html += data[i].name + ': ';
				}
				html += data[i].message + '<br />';
			}
		}
		/* Replace smilies */
		for(var j in $.chat.smilies) {
			var search_str = $.chat.smilies[j].text.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g, "\\$1");
			var re = new RegExp(search_str, 'gi');
			html = html.replace(re, '<img src="' + settings.url_smilies + $.chat.smilies[j].image + '" border="0" />')
		}
		return html;
	}
	
	/* Center/resize chat bar and content */
	function center() {
		var width = $(window).width() - 20 -
		            obj_bar.css('border-left-width').replace('px', '') -
					obj_bar.css('border-right-width').replace('px', '');
		var margin = 10 - $('body').css('margin-left').replace('px', '');
		obj_bar.css({'width': width + 'px', 'margin-left': margin + 'px'});
		obj_content.css({'width': width - 85 + 'px'});
	}
	
	/* AJAX request */
	function ajax(url, callback, errorfunc) {
		jQuery.ajax({
			type:     'GET',
			url:      url,
			dataType: 'json',
			error:    function() {
				errorfunc();				
			},
			success:  function(data) {
				callback(data);
			},
			timeout:  5000
		});
	}
	
	/* Send message */
	function sendMessage(msgdata, callback, errorfunc) {
		jQuery.ajax({
			type:     'POST',
			url:      settings.url_base + 'request/send',
			data:     msgdata,
			dataType: 'json',
			error:    function() {
				errorfunc();			
			},
			success:  function(data) {
				callback(data);
			},
			timeout:  5000
		});
	}
	
	/* Convert to JSON */
	function convToJSON(string) {
		return eval('(' + string + ')')
	}
	
	/* Convert to string */
	function convToString(obj) {
		var output = '{';
		for(var i in obj) {
			if(output.charAt(output.length - 1) != '{') {
				output += ',';
			}
			output += '"' + i + '":';
			if(typeof obj[i] == 'object') {
				output += convToString(obj[i]);
			} else if(typeof obj[i] == 'string') {
				output += '"' + obj[i] + '"';
			} else {
				output += obj[i];
			}
		}
		output += '}';
		return output;
	}
	
	/* Emulate PHP ucfirst */
	function ucfirst(str) {
		var first = str.charAt(0).toUpperCase();
		return first + str.substr(1);
	}

})(jQuery);