/** [TODO] User - comment properly
 * User object constructor
 * @param {user_name} The user's name in Roman letters
 * @param {user_jid} System Internal representation of the user
 * @param {user_jid} The Jabber ID of the user
 * @param {user_domain} The Domain part of the user's JID
 * @param {subscription}  "both" if someone is friend, "none" if not
 * @param {flag} Two letter country Code for the user
 * @param {thumbnail} the user's thumbnail
 * @param {interests} an array of the user's profile tags
 */
function User(user_name, user_id, user_jid, user_domain, subscription, flag, thumbnail) {

	this.user_name = user_name;
	this.user_id = user_id;
	this.user_jid = user_jid;
	this.user_domain = "venueone.com";
	this.subscription = subscription;
	this.flag = flag;
	this.thumbnail = thumbnail || Utility.getBaseURL() + "/images/avatars/avatar_100_gy.jpg";
	this.resources = {};

	this.set_user_name = function (user_name) {
		this.user_name = user_name;
	}
	this.set_user_id = function (user_id) {
		this.user_id = user_id;
	}
	this.set_user_jid = function (user_jid) {
		this.user_jid = user_jid;
	}
	this.set_user_domain = function (user_domain) {
		this.user_domain = user_domain;
	}
	this.set_subscription = function (subscription) {
		this.subscription = subscription;
	}
	this.set_flag = function (flag) {
		this.flag = flag;
	}
	this.set_thumbnail = function (thumbnail) {
		this.thumbnail = thumbnail;
	}


	this.get_user_name = function () {
		return this.user_name;
	}
	this.get_user_id = function () {
		return this.user_id;
	}
	this.get_user_jid = function () {
		return this.user_jid;
	}
	this.get_user_domain = function () {
		return this.user_domain;
	}
	this.get_subscription = function () {
		return this.subscription;
	}
	this.get_flag = function () {
		return this.flag;
	}
	this.get_thumbnail = function () {
		return this.thumbnail;
	}
	this.get_resources = function () {
		return this.resources;
	}

	this.add_resource = function (resource) {
		this.resources[resource] = resource;
	}

	this.delete_resource = function (resource) {
		delete this.resources[resource];
	}
	this.get_resources = function () {
		return this.resources;
	}

	this.is_online = function () {
		var is_online = false; //avoid multiple userobjs for the same person
		
		for (var i in this.resources) {
			return true;
		};
	}
}


function Item (affiliation, role) {
    this.affiliation = affiliation;
    this.role = role;
}

Item.prototype = {
    affiliation: null,
    role: null
}



function Resource (occupant_jid, items) {
    this.occupant_jid = occupant_jid;
    this.items = items;
}

Resource.prototype = {

    occupant_jid: null,

    items: [],

    is_owner: function () {
        for (var i = 0; i < this.items.length; i++) {
            if (this.items[i].affiliation == "owner") { return true; };
        }
        return false;
    },

    is_moderator: function () {
        return this.is_owner() ? true : function (resource) {
            for (var i = 0; i < resource.items.length; i++) {
                if (resource.items[i].role == "moderator") { return true; };
            }
            return false;
        }(this);
    }
} 


function _User (bare_jid) {
    this.bare_jid = bare_jid;
    this.resources = {};
}

_User.prototype = {

    bare_jid: null,

    resources: {},
    
    is_online: function () {
        for (var i in this.resources) {
            return true;
        }
        return false;
    },

    is_moderator: function () {
        for (var i in this.resources) {
            if (this.resources[i].is_moderator()) {
                return true;
            }
        }
        return false;
    },


    delete_resource: function (resource, delete_cb) {
        var was_moderator = this.is_moderator();
        delete this.resources[resource];
        return delete_cb(this, this.is_online(), was_moderator);
    },
    
 
    add_resource: function (resource, add_cb) {
        this.resources[Strophe.getResourceFromJid(resource.occupant_jid)] = resource;
        return add_cb(this, this.is_moderator());
    },

}



/** [TODO] List - comment properly
 * This List gets Users from the database based on the event Type.
 * 
 * The User can have several lists which are referred to a specific type, 
 * i.e: Friend, Public Party, Private Party
 * 
 * [Friend]: those are represented by acceptable Friend Request Invitation;
 * 
 * [Public Party]: a list of people who are attending to a specific Public Party,
 * they necessarily can/cannot be part of your [Friend] list.
 * 
 * [Private Party]: a list of people who are attending to a specific Private Party.
 * In order to be invited to a Private Party, the person whose inviting you needs
 * to have you as a friend on [Friend] list.
 * In order to create a Private Party, you can only invite people from your [Friend] list.
 */
function List(type) {

	this.type = type; // 0 : Friend, 1 : Party
	this.users = new Array();

	this.get_type = function () {
		return (this.type == 0) ? "Friend" : "Party";
	}

	this.add_user = function (user) {
		this.users.push(user);
	}

	this.remove_user = function (user_jid) {
		var index = null
		$.each(this.users, function (iterator, value) {
			if (value.user_jid == user_jid) {
				index = iterator;
			}
		});
		if (typeof index != null) {
			this.users.splice((index - 1), 1);
		} else {
			//console.log("User not found!");
		}
	}

	this.find_user = function (user_jid) {
		try {
			for (var i = 0; i < this.users.length; i++) {
				if (this.users[i].user_jid == user_jid) {
					return this.users[i];
				}
			}
			return false;
		} catch (e) {
			//console.log(e);
		}
	}

	this.get_users = function () {
		return this.users;
	}

	/**
	 * Checks if a user is online based on the number of resources attached to a user object
	 * @param {jid} The JID of the user
	 * @return True if the user has a resource
	 */

	this.is_online = function (user_jid) {

		var online = false;

		for (var i = 0; i < this.users.length; i++) {
			if (this.users[i].get_user_jid() == user_jid) {
				for (var k in this.users[i].get_resources()) {
					if (this.users[i].resources[k] == "online") {
						online = true;
					}
				}
			}
		}
		return online;
	}

}



/**
 * Constructor for chat
 */

function Chat(node, domain, url) {
	this.node = node;
	this.domain = domain;
	this.my_user_jid = node + "@" + domain;
	this.my_thumbnail = null;
	this.my_user_name = null;
	this.reconnect_timeout = null;
	this.connection = new Strophe.Connection(url);
	//this.connection.xmlInput = function (xmlInput) { console.info("INPUT!"); //console.log(xmlInput)};
	//this.connection.xmlOutput = function (xmlOutput) { console.info("OUTPUT!"); //console.log(xmlOutput)};
	this.connection.addHandler(this.on_message, null, "message", "chat");
	this.connection.addHandler(this.on_presence, null, "presence", null);

	this.lists = {
		"friends": new List(0),
		"system": new List(1)
	};



	var me = new User("me", null, this.my_user_jid, null, null, null, null);
	this.lists.friends.add_user(me);
	this.rooms = [];
	this.party = (new Room(null, null, null, null));
	this.party.partyroom = new Partyroom();

	this.ui_friendsonline = $('#friendsOnline ul.friends');
	
	$(document).trigger('chat_ready', { chat: this });
}

function Partyroom() {
	this.roster = {};
	this.moderator = false;
	this.counter = 0;
	this.queue = [];
	this.queue_timeout = null;
	this.update_bot_interval = null;
}

Partyroom.prototype = {
		
	counter: 0,
		
	room_jid_resource: null,
	
	moderator: false,
	
	joined: false,
	
	
	
	config: {
		moderated: false,				//
		subject: ""
	},
	
	
	update_subject: function () {
		if ($('.partyTitle > h1').length) {
			$('.partyTitle > h1').text(this.config.subject);
			$('#hostYouTubeName').val(this.config.subject);
		}
	},
	
	configure: function (config) {
		
		this.config = config ;
		this.update_subject();
	},
	
	change_moderation: function (is_moderated) {
		this.config.moderated= is_moderated;
		this.send_room_config();
	},
	
    change_subject: function (subject, connection) {
        connection.send($msg({to: chat.party.room_jid, type: "groupchat"}).c('subject').t(subject));
        this.config.subject = subject;
        this.send_room_config();
        this.update_subject();
    },
    
    send_room_config: function () {			//may need to be directed at the moderator bot TODO
    	if (this.is_moderator()) {
    	chat.connection.send(
				$msg({
					to: chat.party.room_jid,
					type: "groupchat"
				}).c('config').t(JSON.stringify(this.config)));
    	}
    },
	

	
    add_occupant: function (user, add_cb) {
        
        this.roster[user.bare_jid] = user;
        
        return add_cb(this.roster[user.bare_jid]);
    },

    get_occupant: function (bare_jid) {
        return this.roster[user.bare_jid];
    },

    delete_occupant: function (bare_jid) {
        delete this.roster[bare_jid];
    },


    resolve_youtube_query: function (data, query) {
    	
    	this.send_youtube_data(data, null, null, query.timestamp, query.code);
    	
    },
    
	send_youtube_data: function (data, to, reply, timestamp, status) {
		

		
		if  (typeof data == "string") {
			var youtube_data = data;
		} else {
			var youtube_data = Base64_utf8.encode(JSON.stringify(data));
		}
		
		
		
		
		if (to && this.is_moderator() ) {
			//directed messages, sent in response to requests by joining users by the moderator
			//or as an acknowledgement of a message sent by a moderator
			
			//sent by human moderator to bot in some cases
			
			chat.connection.send(
					$msg({
						to: chat.party.room_jid,
						type: "groupchat"
					}).c('youtube', {
						timestamp: new Date().getTime(),
						to: to
					}).c('data').t(youtube_data).up().c('command').t('command'));
			
	
		}  else if (status) {
			
			chat.connection.send(
					$msg({
						to: chat.party.room_jid,
						type: "groupchat"
					}).c('youtube', {
						timestamp: new Date().getTime()
					}).c('data', {
						timestamp: timestamp //timestamp sent accompanying data so we can confirm it is ours when it is rejected or denied
					}).t(youtube_data).up().c('counter').t(this.counter).up().c('status').t(status));
			
		} else {
				//messages sent by ordinary people, e.g. dave telling me to shut up
			
			
				chat.connection.send(
					$msg({
						to: chat.party.room_jid,
						type: "groupchat"
					}).c('youtube', {
						timestamp: new Date().getTime()
					}).c('data', {
						timestamp: timestamp //timestamp sent accompanying data so we can confirm it is ours when it is rejected or denied
					}).t(youtube_data).up().c('counter').t(this.counter));
		}
		
	},
	
	queue_youtube_data: function (data) {
		
		//notification("Your request was queued.");
		var index = this.queue.push ({ 
				data: data,
				sent: false,
				repeat: false,
				timestamp: new Date().getTime()
		});
		
		var that = this;
		
		
		
		
		this.queue_timeout = setTimeout ( function () {
			var counter = 0;
			for (var i = 0; i < that.queue.length; i++) {
				
				var i_delete = (i - counter);
				
				if (that.queue[i_delete].sent == true) {
					that.queue.splice(i_delete, 1);
					that.process_queue();
					counter++;
				}
					
			} 
			clearTimeout(that.queue_timeout);
		}, 20000);
		
					
		this.process_queue();
	},
	
	process_queue: function () {
		
		
		if (this.queue.length) {
			if (this.queue[0].sent == true) {
				if  (this.queue[0].repeat == true) {
					this.queue[0].repeat == false;
					
					this.send_youtube_data(this.queue[0].data, null, null, this.queue[0].timestamp);
				} else {
					//there's something pending i.e. items in the queue sent but not yet confirmed
				}
			} else {
				//notification("Your request was sent to the moderator.");
				this.send_youtube_data(this.queue[0].data, null, null, this.queue[0].timestamp);
				this.queue[0].sent = true;
			}
		} else {
			//nothing to process
		}
		
	},
	
	get_queue_item_by_timestamp: function (timestamp) {
		for (var i = 0; i < this.queue.length; i++) {
			//console.log(this.queue[i].timestamp);
			if (this.queue[i].timestamp == timestamp) {
				//console.log("this is one of my queue items!");
				return this.queue[i];
			}
		}
		return false;
	},

	receive_youtube_data: function (data, timestamp) {
		try {
			obj = JSON.parse(Base64_utf8.decode(data));
			ytHandler.receive_data(obj, timestamp);
		} catch (e) {
			//console.log(e);
		}
	},

	
	request_counter: function () {
		chat.connection.send(
				$msg({
					to: chat.party.room_jid,
					type: "groupchat"
				}).c('youtube', { from: chat.party.partyroom.room_jid_resource, timestamp: new Date().getTime() }).c('counter').t("request"));
	},
	

	send_youtube_request: function (type) {
		chat.connection.send(
				$msg({
					to: chat.party.room_jid,
					type: "groupchat"
				}).c('youtube', { from: chat.party.partyroom.room_jid_resource, timestamp: new Date().getTime() }).c('request').t(type));
	},
	

	join_party: function (room_jid) {
 		
		var room_nick = chat.my_user_jid + "-" + Utility.get_random_number();
		this.room_jid_resource = room_jid + "/" + room_nick;
		this.counter = 0;
		
		if (this.pres_handler_ref && this.msg_handler_ref) {
			chat.connection.deleteHandler(this.pres_handler_ref);
			chat.connection.deleteHandler(this.msg_handler_ref);
		}
		
		this.pres_handler_ref = chat.connection.addHandler(this.party_pres_handler.bind(this), null, "presence", null, null, room_jid, { matchBare: true });
        	this.msg_handler_ref = chat.connection.addHandler(this.party_msg_handler.bind(this), null, "message", null, null, room_jid, { matchBare: true });
		
		var unique_id = chat.connection.getUniqueId();
		
		chat.connection.send($pres({
            to: this.room_jid_resource,
            id: unique_id
        }));

		
		chat.party.room_jid = room_jid;
		
	//	var replystring = "<iq type='set' to='";
		//replystring += chat.party.room_jid;
		//replystring += "'><query xmlns='http://jabber.org/protocol/muc#owner'><x xmlns='jabber:x:data' type='submit'/></query></iq>";
		//iq_reply = $(replystring);
		//chat.connection.send(iq_reply);
		
	},
	
	is_moderator: function (jid) {
		if (!this.roster) {
			return;
		}
		if (jid) {
			var user_jid = jid;
		} else {
			var user_jid = chat.my_user_jid;
		}
		if (this.roster[user_jid].is_moderator()) {
			return true;
		} else {
			return false;
		}
	},
	
	get_moderator: function () {
		for (var i in this.roster) {
			var user = this.roster[i];
			if (user.is_moderator() && user.bare_jid != "info@venueone.com") {
				return user;
			}
		}
	},

	party_pres_handler: function (pres) {

		
		try {
			if ($(pres).find('error').length < 0) {
				//console.log("error");
				return true;
			}
			var ptype = $(pres).attr("type") || "joined";
			var from = $(pres).attr("from");
			var user_jid = chat.get_user_jid(from);

	        var x_items = $(pres).find('item');

	        var items = [];
	        if (x_items.length) {

	            for (var i = 0; i < x_items.length; i++) {
	                items.push(new Item($(x_items[i]).attr('affiliation'), $(x_items[i]).attr('role')));
	                var bare_jid = Strophe.getBareJidFromJid($(x_items[i]).attr('jid')); //we'll take the last jid, there should only be one
	            }

	        }

	        if (ptype != "unavailable") {
	            //is this a new user, or an existing user?
	            
	            var resource = new Resource(from, items);
	            var user = this.roster[bare_jid];

	            if (!user) {
	                //someone new
	                user = this.add_occupant(new _User(bare_jid), function (user) {
	                    return user;
	                }.bind(this));
	            }

	            //either way, add a resource
	            user.add_resource(resource, function (resource, moderator) {
	                //console.log(resource);
	            });

	        } else {
	            if(this.roster[bare_jid]) {
	                this.roster[bare_jid].delete_resource(Strophe.getResourceFromJid(from), function (user, online, was_moderator) {
	                    if (user && !online) {
	                        this.delete_occupant(user.bare_jid);
	                    }
	                }.bind(this));
	            }
	        }

	        
			if (user_jid == chat.my_user_jid) {
				
								
				if (x_items.length > 0) {
					$.each(x_items, function () {

						var jid = $(this).attr('jid');
						var affiliation = $(this).attr('affiliation');
						var role = $(this).attr('role');
					
						if (jid == chat.connection.jid) {
							if (role == "moderator") {
								$(document).trigger('is_moderator');
								
								chat.party.partyroom.moderator = true;
								
								var that = this;
								clearInterval(this.update_bot_interval);
								this.update_bot_interval = setInterval(
										chat.party.partyroom.update_bot
								, 25000);
								
								
							} else if (role == "participant") {
								if (chat.party.partyroom.moderator) {
									$(document).trigger('not_moderator');
								}
								
								clearInterval (this.update_bot_interval);
								
								chat.party.partyroom.moderator = false;
							} else if (role == "none") {
								notification("You were removed from or left the party!");
							}
						}
					});
				}
				
				//if this presence is from ME and is not unavailable, i.e. I have just joined the room, 
				
				
				if (ptype != "unavailable" && ( 	(	chat.replace_at_symbol(from).split("-")[0]	 	== 		chat.party.partyroom.room_jid_resource.split("-")[0]	)	) ) {
					
					//also if this resource is definitely mine
					if (from.split("-")[1] == chat.party.partyroom.room_jid_resource.split("-")[1] && !this.joined) {
						//then request the state of the room from the moderator
						this.joined = true;
						chat.party.partyroom.send_youtube_request('playlist');
						chat.party.partyroom.send_youtube_request('iterator');
						chat.party.partyroom.send_youtube_request('on_player');
						
					}
					
				}
			}

			if (!chat.lists.friends.find_user([user_jid])) {

				var stranger = new User(
				$(pres).find('user_name').text(), //name
				null, //user_id
				user_jid, //user_jid
				null, //domain
				"none", //subscription
				null, //flag
				$(pres).find('thumbnail'), //thumbnail
				null);
				chat.lists.friends.add_user(stranger);
				
			}



			switch (ptype) {
			case "joined":
				chat.party.add_member(user_jid);
				break;
			case "unavailable":
				chat.party.remove_member(user_jid);
				break;
			}
		} catch (e) {
			//console.log("error in presence handler");
			//console.log(e);
		}
		return true;
	},
	
	leave: function () {
		//console.log("Leaving party.")
		clearInterval(this.update_bot_interval);
		
		var unique_id = chat.connection.getUniqueId();
		
        chat.connection.send($pres({
        	to: this.room_jid_resource,
            id: unique_id,
            type: "unavailable"
        }));
		
		if (this.pres_handler_ref && this.msg_handler_ref) {
			chat.connection.deleteHandler(this.pres_handler_ref);
			chat.connection.deleteHandler(this.msg_handler_ref);
		}
		
		this.roster = {};
		this.moderator = false;
		this.counter = 0;
		this.queue = [];
		this.queue_timeout = null;
		this.update_bot_interval = null;
		this.joined = false;
	},
	
	update_bot: function () {
		ytHandler.receive_data({join_command:'request_on_player', callback: function (on_player) {
			chat.party.partyroom.send_youtube_data(on_player, "info@venueone.com");
		}});
	},

	party_msg_handler: function (msg) {
			//console.log("party_msg_handler");
			//console.log(msg);
			youtube = $(msg).find('youtube');
			youtube_data = $(msg).find('youtube > data');
			youtube_request = $(msg).find('youtube > request');
			youtube_counter = $(msg).find('youtube > counter');
			youtube_setcounter = $(msg).find('youtube > setcounter');
			youtube_command = $(msg).find('youtube > command');
			youtube_query = $(msg).find('youtube > query');
			youtube_subject = $(msg).find('subject');
			
			if (youtube_subject.length) {
				this.config.subject = youtube_subject.text();
				this.update_subject();
			}
			
			config = $(msg).find('config');
			
			var reply = $(msg).find('youtube > reply');
			
			if (reply.length) {
				var status = reply.attr('code');
				var reply_counter = reply.attr('counter');
			} else {
				var status = false;
				var reply_counter = 0;
			}
			
		if (config.length) {
			this.configure(JSON.parse(config.text()) );
		}

		
		try {
			if (youtube.attr('to')) {
				
				var intended_recipient = chat.replace_at_symbol(youtube.attr('to'), true);
				var my_nick = this.room_jid_resource;
				
				if (intended_recipient != my_nick) {
					return true;
				} else {
					if (youtube_query.length) {
						
						data = youtube_data.text();
						command_data = JSON.parse(Base64_utf8.decode(data));
						command_timestamp = youtube_query.attr('timestamp');
						
						var data_string = data + ""; 
						

						
						command_text = "Somebody wants to do something";
						command_image = false;
						
						if(command_data["youtube_thumbnail"]){
							command_image = command_data["youtube_thumbnail"];
						}
						
						if(command_data["youtube_command"]){
							command_text = "Somebody wants to <span class='green'>"+command_data["youtube_command"]+'</span>';
							if (!Utility.is_undefined(command_data["youtube_title"])){
								command_text = "Somebody wants to "+command_data["youtube_command"]+" <span class='green'>"+command_data["youtube_title"]+'</span>';
							}
							if (command_data["youtube_command"]=="load" || command_data["youtube_command"]=="delete"){
								command_text = "Somebody wants to "+command_data["youtube_command"]+" <span class='green'>"+ytPlaylist.playlist[command_data["id"]]["youtube_title"]+'</span>';
								command_image = ytPlaylist.playlist[command_data["id"]]["youtube_thumbnail"];
							}
							
						}
						
							
						
						notification(command_text, {
							persist: true,
							image: command_image,
							button2: {
								text: "Approve",
								action: function () {
									this.resolve_youtube_query(data_string,
											{ counter: youtube_counter.text(),
												timestamp: command_timestamp,
												code: 200 }
										) //error = false
								}.bind(this)
							},
							//},
							button1: {
								text: "Deny",
								action: function () {
									this.resolve_youtube_query(data_string,
											{ counter: youtube_counter.text(),
												timestamp: command_timestamp,
												code: 403 }
									) //error = false
							}.bind(this)
							}
						});
						
						
					}
				}
			} 
		} catch (e) {
			//console.log(e);
			if (command_data) {
				//console.log(command_data);
			}
		}
		
		try {
			if (youtube_setcounter.length > 0 ) {
					this.counter = youtube_setcounter.text();
			}

		} catch (e) {
			//console.log(e);
		}
		
		try {

			
			if (reply.attr('timestamp')) {
				var timestamp = reply.attr('timestamp'); 
			}
			
			//test if this message refers to an item in my queue

			
			var queue_item = this.get_queue_item_by_timestamp(timestamp);

			if(queue_item && status) {
				//if it does,

					if (status == "500") {
						//and it has an error, tell it to repeat when I process the queue
						//notification("One of your messages was received out of order and has been resent.")
						queue_item.repeat = true;	
						setTimeout(this.process_queue.bind(this), 500);
						return true;
					} else if (status == "403") {
						//notification("Your request was rejected!");
						this.queue.splice(this.queue.indexOf(queue_item), 1);
						setTimeout(this.process_queue.bind(this), 500);
						return true;
					} else {
						//but if it doesn't, remove it from the queue. it's over now
						//notification("Your request was accepted!");
						this.queue.splice(this.queue.indexOf(queue_item), 1);
						setTimeout(this.process_queue.bind(this), 500);
					}
					//process the next queue item
					
					
			} else if (status == "500" || status == "403") {
				//this is a failure message that isn't for me
				return true;
			}
		} catch (e) {
			//console.log(e);
		}



	

		

		try {
			if (youtube_data.length > 0 && youtube_command.length > 0 ) {
				data = youtube_data.text();
				this.receive_youtube_data(data, timestamp);
				return true;
			}
		} catch (e) {
			//console.log(e);
		}
		

		

		
		return true;
	}
}

Chat.prototype = {

	opened: null,

	get_online_users: function () {
		var online_users = [];
		for (var i = 0; i < this.lists.friends.users.length; i++) {
			if (this.lists.friends.is_online(this.lists.friends.users[i].user_jid)) {
				online_users.push(this.lists.friends.users[i]);
			}
		}
		return online_users;
	},

	update_storage: function () {
		$(document).trigger("update_storage");
	},
	/**
	 * Sets my_user_jid
	 * Called on attach to session
	 * @param {user_jid} - My JID
	 * 
	 */
	set_my_user_jid: function (user_jid) {
		this.my_user_jid = user_jid;
	},

	/**
	 * Calls a script which creates a BOSH connection and attaches to it
	 * @param {jid} The JID of the user
	 * @return True if the user has a resource
	 */

	send_presence: function () {
		this.connection.send($pres().c('thumbnail').t(chat.my_thumbnail).up().c('user_name').t(chat.my_user_name));
	},

	/**
	 * Restores the rooms from HTML5 Local Storage, if they are there
	 * 
	 */

	restore: function () {



		if (typeof (localStorage) == 'undefined') {
			//no support 
		} else {

			chat.restored = true;

			if (!localStorage.getItem("pageLoadCount")) {
				localStorage.setItem("pageLoadCount", 0);
			}
			localStorage.pageLoadCount = parseInt(localStorage.pageLoadCount) + 1;
			if (localStorage.pageLoadCount && localStorage.rooms) {
				var restoredRooms = JSON.parse(localStorage.getItem("rooms"));
				chat.rooms = [];

				$.each(restoredRooms, function (iterator) {

					chat.rooms.push(new Room(this.room_jid, this.room_key, iterator));

					for (var i = 0; i < $(this.msg_log).length; i++) {
						var stanza = $(this.msg_log[i])[0];
						$(stanza).wrapInner("<body></body>");
						chat.rooms[iterator].msg_log.push(stanza);

					}
					var room_nick = chat.my_user_jid + "-" + Utility.get_random_number();

					chat.connection.muc.join(this.room_jid, room_nick, chat.muc_msg_router, null);

				});


				$(document).trigger("reboot_rooms", function () {
					chat.restored = false;
				});
			}
		}



	},

	attach: function () {
		$.getJSON('index.php?r=global/prebind', {
			node: this.node
		}, function (data) {
			if (data.result == "authFailed") {
				//console.error("Catastrophic error: Pre-bind authentication failure");
			} else {


				chat.connection.attach(data.jid, data.sid, data.rid, function (status) {
					if (status === Strophe.Status.ATTACHED) {
						chat.set_my_user_jid(chat.get_user_jid(data.jid)); //for joining room
						try {
							setTimeout(chat.restore, 2000);
						} catch (e) {
							chat.rooms = [];
							$(document).trigger("reboot_rooms");
							//console.log(e);
						}

					} else if (status === Strophe.Status.DISCONNECTED || status === Strophe.Status.DISCONNECTING) {

						//console.log("disconnected :(");

						if (!chat.reconnect_timeout) {
							chat.reconnect_timeout = setTimeout(function () {
								$(document).trigger('disconnected');
							}, 3000);
						}
						//console.log("2: condition " + status);
						// TODO at this point, try reconnecting
					}
				});

			}
		}).error(function () {
			setTimeout(chat.attach(), 3000);
		});
	},

	/**
	 * Flushes and syncs the connection
	 */

	disconnect: function () {
		localStorage.clear();
		this.connection.sync = true;
		this.connection.flush();
		return this.connection.disconnect();
	},

	/** [TODO] - comment properly
	 * Returns a bare JID from a full JID 
	 * For example: yuri@venueone.com/laptop -> yuri@venueone.com
	 * @param {user_jid_resource}
	 * @return {user_jid} 
	 *
	 * Returns a bare JID from a room JID following the convention used that
	 * yuri@venueone.com joins chat_room@conference.venueone.com
	 * gets room JID of chat_room@conference.venueone.com/yuri@venueone.com-9999
	 * where 9999 is a random number
	 * this function returns yuri@venueone.com
	 * @param {room_user_nick}
	 * @return {user_jid} 
	 */
	get_user_jid: function (data) {

		if (data.count("conference") > 0) {
			var new_data = this.replace_at_symbol(data); //only useful in some circumstances
		} else {
			new_data = data;
		}


		switch (new_data.count('@')) { // utility.js
		case 1:
			return Strophe.getBareJidFromJid(new_data);
			break;
		case 2:
			return this.get_user_resource(new_data).split("-")[0];
			break;
		}
	},

	/**
	 * replaces the ascii code for "@", "\40", with "@"
	 * there is some bug preventing .count() from working with .replace() in IE
	 * @param {data} - the data, for example 1133788499@conference.venueone.com/9\40venueone.com-51794
	 * @return {user_jid} - for example 9@venueone.com
	 */

	replace_at_symbol: function (data, full) {
		try {

			var id = data.split("-")[1];
			var numberbit = data.split(chat.domain)[1];

			var pattern = /[0-9]+/g;
			var match = numberbit.match(pattern);
			var match_first_number = match[0];

			new_user_jid = match_first_number + "@" + chat.domain;

			var room_jid = data.split("/")[0];


			new_data = room_jid + "/" + new_user_jid;
			if (full) {
				return new_data + "-" + id;
			} else {
				return new_data;
			}
		} catch (e) {
			//console.log(e);
		}

	},

	/**
	 * Returns the resource part of a full JID
	 * 
	 * @param {user_jid_resource} - for example, yuri@venueone.com/laptop
	 * @return {resource} - for example, laptop
	 */
	get_user_resource: function (user_jid_resource) {
		try {
			return Strophe.getResourceFromJid(user_jid_resource);
		} catch (e) {
			//console.log (e);
		}
	},

	/**
	 * Returns the resource part of a room JID
	 * 
	 * @param {room_user_nick} - for example, room-node@conference.venueone.com/user-node@venueone.com-9999
	 * @return {room_jid} - for example, room-node@conference.venueone.com
	 */

	get_room_jid: function (room_user_nick) {

		return Strophe.getBareJidFromJid(room_user_nick).replace("\40", "@");
	},

	/**
	 * Loops through a roster and returns a User object for that JID
	 * 
	 * @param {list} a List object
	 * @param {user_jid}
	 * @return {User} 
	 */
	get_user: function (list, user_jid) {
		return list.find_user(user_jid);
	},

	/**
	 * Checks if a user is in any of my rooms on his own apart from me 
	 * @param {jid} The JID I'm looking for
	 * @return {boolean} True if I am in a solo chat with a person
	 */
	check_if_in_solo_chat: function (user_jid) {
		for (i in chat.rooms) {
			if (chat.rooms[i].only_member_in_room(user_jid)) {
				return true;
			}
			return false;
		}
	},

	/**
	 * Global presence handler 
	 * @param {xmpp} The XMPP stanza for this presence
	 * @return Handlers always return true or they die
	 */
	on_presence: function (presence) {
		//console.log(presence);
		try {
			var user_jid_resource = ($(presence).attr('from'));

			var user_jid = chat.get_user_jid(user_jid_resource);

			var user = chat.lists.friends.find_user(user_jid);

			var user_resource = chat.get_user_resource(user_jid_resource);





			if (!user) { //this user is not my friend
				if ($(presence).find('thumbnail').length > 0) {

					chat.lists.friends.users.push(
					new User(
					$(presence).find('user_name').text(), //name
					null, //user_id
					user_jid, //user_jid
					null, //domain
					"none", //subscription
					null, //flag
					$(presence).find('thumbnail').text()

					));
					var user = chat.lists.friends.find_user(user_jid);

				} else {

				}
			} else {


				if ($(presence).find('thumbnail').length > 0) {
					user.set_thumbnail($(presence).find('thumbnail').text());
				}

				if ($(presence).find('user_name').length > 0) {
					user.set_user_name($(presence).find('user_name').text());
				}
			}
		} catch (e) {
			//console.log(e);
		}

		try {
			var xq = $(presence).find("x");
			if (xq.length > 0) {
				for (var i = 0; i < xq.length; i++) {
					var xmlns = xq.attr("xmlns");
					if (xmlns && xmlns.match(Strophe.NS.MUC)) {
						return chat.muc_pres_router(presence);
					}
				}
			}
		} catch (e) {
			//console.log(e);
		}


		var ptype = $(presence).attr("type") || "available";
		if (ptype == "subscribed") {
			return true;
		}

		var room = (!user_resource.split(".")[0] == "jaxl"); //room == true indicates that this is a presence for a room
		if (room) {
			return true;
		}






		if (ptype != "error") {
			if (user_jid != this.my_user_jid) { //this presence is not from me
				if (ptype == "unavailable") { //user is leaving			 
					user.delete_resource(user_resource);
				} else {


					var already_online;
					if (user.is_online()) {
						already_online = true;
					} else {
						var user_name = $(presence).find('user_name');
						if (user_name.length > 0) {
							setTimeout(function(){notification(user_name.text() + " is online", {jid: chat.get_user_jid( $(presence).attr('from') ) })}, 1000);
						}
					}

					user.add_resource(user_resource);
				}
			}
		}


		chat.update_list(); //update list now that we know someone knew is offline/online
		return true;

	},

	/**
	 * Notifies you that a chat invitation has failed
	 */
	failed: function () {
		if (!chat.opened) {
			notification("Your friend is busy, why not write them a message?");
			chatAdvice();
		}
	},


	/**
	 * Global message handler 
	 * @param {xmpp} The XMPP stanza for this message
	 * @return Handlers always return true or they die
	 */

	on_message: function (xmpp) {
		try {
			var from = chat.get_user_jid($(xmpp).attr('from')); //user_jid
			var body = $(xmpp).find('body').text();
			var delay = $(xmpp).find('delay').text();
			var room_key = $(xmpp).find('key').text();
			var room_jid = $(xmpp).find('data').text(); //room to join, if there is one
			if (delay.length >= 1 && from != "info@venueone.com") {
				return true;
			} else if (body == "invite") { //This is an invitation, not an RSVP
				try {
					joined = chat.join_room(room_jid, room_key, true); //Join the room if possible
					chat.reply_to_invitation(from, joined); //Send an RSVP to the user who invited me
					return true;
				} catch (e) {}
			} else if (body == "nothanks") {
				notification("Your friend could not start a chat with you :(");
				//TODO inform the user that the remote user could not join the room
			} else if (body == "thanks") { //This is an RSVP, so I need to check if I'm already in that room, and if I am not, join it
				var already_joined = false;
				if (chat.rooms.length > 0) {
					$.each(chat.rooms, function () {
						if (this.room_jid == room_jid) {
							already_joined = true;
						}
					});
				}
				if (already_joined == false) {
					joined = chat.join_room(room_jid, room_key);
				}
			} else if (from == "info@venueone.com") {
				try {
					var update = $(xmpp).find('update').text();
					if (delay.length >= 1) {
						alert("delayed message received");
						return true;
					} else if (update.length >= 1) {

						notification("New " + update + " received.");
						if (update == "Friend") {
						} else {
							newUpdate();
						}
					}
					return true;
				} catch (e) {
					//console.log(e);
				}
			}

		} catch (e) {
			//console.log(e);
		}

		return true;

	},

	/**
	 * Get the room I am currently in
	 * @return {Chatroom} The currently open (visible) room
	 */

	get_current_room: function () {
		return current_room;
	},


	/**
	 * Find Room JID of room with this index //TODO UI-dependence
	 * @param {index} the room UI index 
	 */

	find_room_jid: function (ui_index) {
		var room_jid = "not found";
		$.each(this.rooms, function () {
			if (chat.rooms.indexOf(this) == ui_index) {
				room_jid = this.room_jid;
			}
		});
		return room_jid;
	},

	/**
	 * Find Room JID of room with this index //TODO UI-dependence
	 * @param {index} the room UI index 
	 */

	find_room_key: function (ui_index) {
		var room_key = "not found";
		$.each(this.rooms, function () {
			if (chat.rooms.indexOf(this) == ui_index) {
				room_key = this.room_key;
			}
		});
		return room_key;
	},

	/**
	 * Create a new chat room to invite a specific user
	 * @param {invite_user_jid} the user I am inviting to a chat
	 * @return sends the invitation
	 */
	create_room: function (invite_user_jid) {
		if (!chat.check_if_in_solo_chat(invite_user_jid)) {
			var room_node = Utility.get_random_number() + "" + Utility.get_random_number();
			var room_key = Utility.get_random_number() + "";
			var room_jid = room_node + "@conference." + this.domain;
			return this.invite_to_room(invite_user_jid, room_jid, room_key);
		} else {
			notification("You are already chatting!");
			return false;
		}
	},

	/**
	 * Invite a user to an existing room
	 * @param {invite_user_jid} the user I am inviting to a chat
	 * @param {room_jid} the JID of the room I am inviting the user to
	 * @param {key} the encryption key of the room
	 * @return sends the invitation
	 */

	invite_to_room: function (invite_user_jid, room_jid, room_key) {

		room_object = chat.get_room(room_jid);
		if (room_object) {
			if (room_object.in_room(invite_user_jid)) {
				return notification("This person is already chatting there!");
			}
		}

		notification("You've invited a friend to chat.")
		chat.opened = false;
		setTimeout(chat.failed, 3000);
		return this.connection.send($msg({
			to: invite_user_jid,
			"type": "chat"
		}).c('body').t("invite").up().c('data').t(room_jid).up().c('key').t(room_key));


	},

	/**
	 * Send an RSVP to a user
	 * @param {host_user_jid} the user who invited me to chat
	 * @param {room} - the room_setup I have just joined or not joined, including its key, JID and message text
	 * @return send the message
	 */

	reply_to_invitation: function (host_user_jid, room_setup) {
		var rsvp = ($msg({
			"to": host_user_jid,
			"type": "chat"
		}).c('body').t(room_setup.message_text).up().c('data').t(room_setup.room_jid).up().c('key').t(room_setup.room_key));
		return this.connection.send(rsvp);
	},

	/**
	 * Join a conference
	 * @param {room_name} the conference we are joining
	 * @param {key} the encryption key of the room
	 * @return send the message
	 */

	join_room: function (room_jid, room_key, new_room) { //join the room with the received room_jid
		$('#dragInAdvice').remove();


		if (!chat.get_room(room_jid)) {
			var room = this.create_room_object(room_jid, room_key);

			if (typeof room != "undefined") { //if create room  returns an active room, i.e. I don't have too many rooms already
				var room_nick = this.my_user_jid + "-" + Utility.get_random_number();
				this.connection.muc.join(room.room_jid, room_nick, this.muc_msg_router, null);

				if (new_room) {
					var replystring = "<iq type='set' to='";
					replystring += room.room_jid;
					replystring += "'><query xmlns='http://jabber.org/protocol/muc#owner'><x xmlns='jabber:x:data' type='submit'/></query></iq>";
					iq_reply = $(replystring);
					this.connection.send(iq_reply);
				}

				create_new_room();
				$(document).trigger("room_activated", room);
				var text = "thanks";
			} else {
				var text = "nothanks";
			}

			var room_setup = {
				room_jid: room_jid,
				message_text: text,
				room_key: room_key
			};

			return room_setup;
		} else {
			return true;
		}

	},

	/**
	 * Get the room object from the JID for the room
	 */

	get_room: function (room_jid) {
		for (var i = 0; i < chat.rooms.length; i++) {
			if (chat.rooms[i].room_jid == room_jid) {
				return chat.rooms[i];
			}
		}
		return false;
	},

	/**
	 * Routes room messages to the correct room
	 * @param {xmpp} the message stanza
	 * @return always true for handlers
	 */

	muc_msg_router: function (xmpp) {
		var from = $(xmpp).attr('from'); //room_user_jid
		var room_jid = chat.get_room_jid(from); //
		$.each(chat.rooms, function () { //TODO: how do we loop through rooms
			if (room_jid == this.room_jid) {
				this.msg_handler(xmpp);
			}
		});
		return true;
	},

	/**
	 * Routes room presences to the correct room
	 * @param {xmpp} the presence stanza
	 * @return always true for handlers
	 */

	muc_pres_router: function (xmpp) {
		var from = $(xmpp).attr('from'); //room_user_jid
		var room_jid = chat.get_room_jid(from);
		$.each(chat.rooms, function () { //TODO: how do we loop through rooms
			if (room_jid == this.room_jid) {
				this.pres_handler(xmpp);
			}
		});
		if (room_jid == chat.party.room_jid) {
			chat.party.partyroom.party_pres_handler(xmpp);
		} 
		return true;
	},


	/**
	 * Gets the current time in a format suitable for display in the chat log
	 * @return {string} the time
	 */
	get_display_time: function () {
		var currentTime = new Date();
		var hours = currentTime.getHours();
		var minutes = currentTime.getMinutes();
		var seconds = currentTime.getSeconds();
		if (minutes < 10) minutes = "0" + minutes;
		return "" + hours + ":" + minutes;
	},

	/**
	 * Creates a new room object
	 * @param room_jid
	 * @param key
	 * @return {Room} the new room object
	 */
	create_room_object: function (room_jid, room_key) {


		//push returns new array length
		number_of_rooms = chat.rooms.length;
		if (chat.rooms.length < 6) {
			try {
				new_number_of_rooms = chat.rooms.push(new Room(room_jid, room_key, number_of_rooms)); //TODO what about deleted rooms
				return chat.rooms[new_number_of_rooms - 1];
			} catch (e) {
				//console.log(e);
			}
		} else {
			return "undefined";
		}
	},

	/*
	 * Creates a friend
	 * If only life were so easy
	 * @param (user) The user draggable to be created
	 */

	create_friend_html_for_list: function (user) {
		var html = "<li class='chatDragger ui-draggable' data-user_jid='";
		html += user.get_user_jid();
		html += "' data-user_name='";
		html += user.get_user_name();
		html += "' data-user_id='";
		html += user.get_user_id();
		html += "'>";
		html += "<img src='" + user.get_thumbnail() + "'/>";
		html += "<span class='username'>" + user.get_user_name() + "</div>";
		return html;
	},

	/*
	 * 
	 */

	update_list: function () {
		chat.ui_friendsonline.empty();
		try {

			var items = this.lists.friends.users;

			items.sort(function (a, b) {
				var keyA = a.user_name;
				var keyB = b.user_name;

				if (keyA < keyB) return -1;
				if (keyA > keyB) return 1;
				return 0;
			});

			for (var i = 0; i < items.length; i++) {
				var user = items[i];
				var online = user.is_online();
				if (online == true && (user.user_jid != chat.my_user_jid)) {
					chat.ui_friendsonline.append($(chat.create_friend_html_for_list(items[i])));
				}
			}


		} catch (e) {
			//console.log(e);
		}

	},

	/**
	 * Sets my_user_name which is used for shoutout and also for announcing my identity to users in chats that I have joined
	 * @param {user_name}
	 */

	set_my_user_name: function (user_name) {
		this.my_user_name = user_name;
	},

	/**
	 * Sets my_thumbnail which is used for shoutout and also for announcing my identity to users in chats that I have joined
	 * @param {user_name}
	 */

	set_my_thumbnail: function (thumbnail) {
		this.my_thumbnail = thumbnail;
	},

	/**
	 * Gets an array of chat rooms and names
	 */

	get_rooms_and_name: function () {
		var chat_room_list = [];
		for (var i = 0; i < chat.rooms.length; i++) {
			if (chat.rooms[i].get_a_name()) {
				var room_item = {
					index: chat.rooms.indexOf(chat.rooms[i]),
					name: chat.rooms[i].get_a_name()
				}
				chat_room_list.push(room_item);
			}

		}
		return chat_room_list;
	}


};



/**
 * 
 */

function Room(room_jid, room_key, room_index) {

	this.room_jid = room_jid;
	this.room_key = room_key;
	this.members = [];
	this.msg_log = [];

	this.ui_list = $('#chat' + room_index).find('ul.mixColorList'); //FIXME numeric UI dependence
	this.ui_members = $('#chat' + room_index).find('ul.users');
	this.ui_bar = $('#chatbar' + room_index);
	this.ui_input = $('#input' + room_index);
	this.ui_icon_send = $('#chat' + room_index).find('.smallChatIcon');
	this.init_input();
	this.ui_list.empty();
	this.ui_close = $('#chat' + room_index).find('li.ui-icon-close'); //FIXME
	this.ui_orangebox = $('#chatbar' + room_index).parent().find('.orangeBox');
	this.ui_orangebox.hide();

	this.ui_bar.click(function () {
		flash_orange_box(room_index, true);
	});

	this.ui_list.click(function () {
		flash_orange_box(room_index, true);
	});

	this.ui_close.bind("click", function () {
		var grandparent_id = $(this).parent().parent().attr("id");
		var index = grandparent_id.charAt(grandparent_id.length - 1);
		chat.rooms[index].leave();
		chatAdvice();
	});

	this.add_member = function (user_jid) {
		for (var i = 0; i < this.members.length; i++) {
			if (this.members[i] == user_jid) {
				return false;
			}
		}
		if (chat.my_user_jid == user_jid) {
			return false;

		}
		this.members.push(user_jid);
	}

	this.reinitialize = function () {

		var room_index = chat.rooms.indexOf(this);

		this.ui_list = $('#chat' + room_index).find('ul.mixColorList'); //FIXME numeric UI dependence
		this.ui_members = $('#chat' + room_index).find('ul.users');
		this.ui_bar = $('#chatbar' + room_index);
		this.ui_input = $('#input' + room_index);
		this.ui_icon_send = $('#chat' + room_index).find('.smallChatIcon');
		this.ui_list.empty();
		this.ui_close = $('#chat' + room_index).find('li.ui-icon-close'); //FIXME
		this.ui_orangebox = $('#chatbar' + room_index).parent().find('.orangeBox');
		this.ui_orangebox.show();
		this.init_input();

		this.ui_bar.click(function () {
			flash_orange_box(room_index, true);
		});

		this.ui_list.click(function () {
			flash_orange_box(room_index, true);
		});


	}

	this.in_room = function (user_jid) {
		for (var i = 0; i < this.members.length; i++) {
			if (this.members[i] == user_jid) {
				return true;
			}
		}
		return false;
	}

	this.only_member_in_room = function (user_jid) {
		if (this.in_room(user_jid) && this.members.length == 1) {
			return true;
		} else {
			return false;
		}
	}

	this.remove_member = function (user_jid) {

		var delete_index = null;

		for (var i = 0; i < this.members.length; i++) {
			if (this.members[i] == user_jid) {
				delete_index = i;
			}
		}

		if (delete_index != null) {
			this.members.splice(delete_index, 1);
		}

		$.each(this.members, function (counter) {
			if (this == user_jid) {
			}
		});

		if (chat.rooms.indexOf(this) > -1) {
			$.each(chat.rooms[0].ui_members.children(), function () {
				if ($(this).data("user_jid") == user_jid) {
					$(this).remove();
				}
	
			});
		}
	}

	this.get_total_members = function () {
		return this.members.length;
	}

	this.get_a_name = function () {
		if (this.members.length > 0) {
			return (chat.lists.friends.find_user(this.members[0]).get_user_name());
		}
		return false;
	}
}

Room.prototype = {
	restored: false,

	/**
	 * Routes room presences to the correct room
	 * @param {xmpp} the presence stanza
	 * @return always true for handlers
	 */

	init_input: function () {
		this.ui_input.unbind();
		var that = this;
		this.ui_input.keypress(function (ev) {
			var body = null;
			if (ev.which === 13 && $(this).val != '') {
				ev.preventDefault();
				var body = $(this).val();
				$(this).val('');
				if (body != '') {
					if (that.members.length > 0) {
						that.send_message(body);
					} else {
						that.send_message(body);
					}
				}
			}

		});
		this.ui_icon_send.click(function () {
			var body = null;
			body = that.ui_input.val();
			that.ui_input.val('');
			if (body != '') {
				if (that.members.length > 0) {
					that.send_message(body);
				} else {
					that.send_message(body);
				}
				body = '';
			}
		});
	},

	/**
	 * Leaves the room
	 */

	leave: function () {
		chat.connection.muc.leave(this.room_jid, chat.my_user_jid, function (data) {
			notification("You have left the chat");
		});

		if (chat.rooms.indexOf(this) > -1) {
			chat.rooms.splice(chat.rooms.indexOf(this), 1);
			chat.update_storage();
			$(document).trigger("reboot_rooms");
		}


	},

	/**
	 * Notifies the room that someone has joined
	 * @param jid the JID of the user who has joined
	 * @return adds the notice
	 */

	joined: function (user_jid) {
		chat.opened = true;
		if (user_jid != chat.my_user_jid) {
			var user = chat.get_user(chat.lists.friends, user_jid);
			//TODO: trigger the updated list
			notification(user.get_user_name() + " has started chatting.");
			this.add_notice(user, "sys", "has joined the chat.");
			return true;
		}
	},


	rejoin: function () {
		var room_nick = chat.my_user_jid + "-" + Utility.get_random_number();
		chat.connection.muc.join(this.room_jid, room_nick, null, null);
	},

	/**
	 * Notifies the room that someone has left
	 * @param {jid} the JID of the user who has left
	 * @return adds the notice
	 */

	left: function (user_jid) {
		if (chat.rooms.indexOf(this) > -1) {
			if (user_jid != chat.my_user_jid) {
				var user = chat.get_user(chat.lists.friends, user_jid);
				notification(user.get_user_name() + " has left the chat.");
				this.add_notice(user, "sys", "has left the chat.");
			}
		}

	},



	/**
	 * Sends a message to the room
	 * @param {text} the text to send to the room
	 */

	send_message: function (text) {
		cipher_text = Tea.encrypt(text, this.room_key)
		chat.connection.send(
		$msg({
			to: this.room_jid,
			type: "groupchat"
		}).c('body').t(cipher_text));
	},

	/**
	 * Updates the member display for this room
	 */

	update_member_display: function () {
		this.ui_members.find('.chatDragger').remove();

		if (this.members.length > 0) {
			this.ui_orangebox.show();
			this.ui_orangebox.text(this.members.length);
		} else {
			this.ui_orangebox.hide();
		}
		var that = this;

		try {
			$.each(this.members, function () {
				var user_element = $(chat.create_friend_html_for_list(chat.lists.friends.find_user(this))); //FIXME numeric UI dependence
				ui_add_user_to_room(user_element, "chat" + chat.rooms.indexOf(that));

				var user_jid = this + "";
				if (chat.my_user_jid != user_jid) {

					that.ui_bar.find('img').attr("src", $(user_element).find('img').attr('src'));
				} else {

				}
			});
		} catch (e) {
			//console.log(e);
		}
	},

	/**
	 * Presence  Handler
	 * @param {xmpp} the incoming XMPP stanza
	 */


	pres_handler: function (xmpp) {
		try {
			if ($(xmpp).find('error').length < 0) {
				//console.log("error");
				return true;
			}
			var ptype = $(xmpp).attr("type") || "joined";
			var from = $(xmpp).attr("from");
			var user_jid = chat.get_user_jid(from);

			if (!chat.lists.friends.find_user([user_jid])) {

				var stranger = new User(
				$(xmpp).find('user_name').text(), //name
				null, //user_id
				user_jid, //user_jid
				null, //domain
				"none", //subscription
				null, //flag
				$(xmpp).find('thumbnail'), //thumbnail
				null);
				chat.lists.friends.add_user(stranger);
			}



			switch (ptype) {
			case "joined":
				this.add_member(user_jid);
				this.joined(user_jid);
				this.update_member_display();
				break;
			case "unavailable":
				this.remove_member(user_jid);
				this.left(user_jid);
				this.update_member_display();
				break;
			}
		} catch (e) {
			//console.log("error in presence handler");
			//console.log(e);
		}


		return true;


	},

	/**
	 * Message  Handler
	 * @param {xmpp} the incoming XMPP stanza
	 */


	msg_handler: function (xmpp) {

		error = $(xmpp).find('error'); //TODO: better error handling - get list of error codes from XEP-0045
		identify = $(xmpp).find("thumbnail");




		if (!error.length > 0 && !identify.length > 0) {
			this.msg_log_add(xmpp);
			setTimeout(chat.update_storage, 1000);
			try {
				var message = this.msg_log_parse(xmpp);
				var user = chat.get_user(chat.lists.friends, message.sender_user_jid);
				var text = message.message_text;
				if (!chat.restored) {
					notification(user.user_name + ": " + text);
				}
				flashTitle(user.user_name + " has sent a message");
				this.add_notice(user, "msg", text);
			} catch (e) {
				//console.log(e);
			}
		} else {
			//console.log("error in room message handler");
		}
		return true;
	},

	/**
	 * This adds a message stanza to the room log
	 * @param {xmpp} the XMPP stanza to be added 
	 */

	msg_log_add: function (xmpp) {
		try {
			//console.log("chat.rooms.msg_log_add()");
			this.msg_log.push(xmpp);
		} catch (e) {
			//console.log(e);
		}
	},

	/*
	 * Parses a message and adds it to the GUI
	 * @param {xmpp} the XMPP stanza to be parsed
	 */

	msg_log_parse: function (xmpp) {
		var text = Tea.decrypt($(xmpp).find('body').text(), this.room_key);
		var from = $(xmpp).attr('from');
		var sender_user_jid = chat.get_user_jid(from);
		var message = {
			sender_user_jid: sender_user_jid,
			message_text: text
		}
		return message;

	},

	/**
	 *  This function parses messages sent for the purposes of identification
	 *  It gives us their user_jid
	 *  @param {xmpp} the XMPP stanza to be parsed
	 *  @return {user_jid}
	 */

	msg_log_identify_parse: function (xmpp) {
		var from = $(xmpp).attr('from');
		var sender_user_jid = chat.get_user_jid(from);
		return sender_user_jid;
	},

	/** display all message in the current message log
	 * @param {delay} - whether or not to show delayed messages.
	 */

	parse_and_display: function () {
		this.ui_list.empty();
		var that = this;
		try {
			$.each(this.msg_log, function () {
				try {

					if ($(this).find('delay').text() && (!chat.restored)) {

					} else {

						if ($(this).find('thumbnail').length == 0) {
							var message = that.msg_log_parse(this);

							var user = chat.get_user(chat.lists.friends, message.sender_user_jid);
							var text = message.message_text;
							that.add_notice(user, "msg", text);

						} else {
							var stranger = new User(
							$(this).find('user_name'), //name
							null, //user_id
							that.msg_log_identify_parse(this), //user_jid
							null, //domain
							"none", //subscription
							null, //flag
							$(this).find('thumbnail'), //thumbnail
							null);
						}
					}
				} catch (e) {
					//console.log(e);
				}
			});

		} catch (e) {
			//console.log (e);
		}
		if($("#chatPanelWrap").is(":visible")){
			//console.log();
		}else{
			toggleElement($("#chatPanelWrap"));
		}
		$(document).trigger("update_scroll");
	},

	add_notice: function (user, type, text) {

		switch (type) {
		case "msg":
			this.ui_list.append(this.generate_msg_html(user, type, text));
			if (!this.ui_list.is(":visible")) {
				flash_orange_box(chat.rooms.indexOf(this));
			}
			break;
		case "sys":
			this.ui_list.append(this.generate_sys_html(user, text));
			break;
		}
		setTimeout(chat_scroll_bottom, 500);
	},

	/*
	 * Generates HTML for a message to be added to a chat room
	 * @param {user} User object (sender)
	 * @param {text} The message body (decrypted)
	 */

	generate_msg_html: function (user, type, text) {
		var html = "<li>";
		html += "<div class='userName green'>"
		html += Utility.sanitize(user.get_user_name());
		html += "</div>";
		html += "<div class='messageBody'>";
		html += Utility.sanitize(text);
		html += "</div>";
		html += "</li>";
		return html;
	},

	/*
	 * Generates HTML for a system message to be added to a chat room
	 * @param {user} User object (person who has joined or left the room)
	 * @param {text} The message (e.g. "has left the room")
	 */

	generate_sys_html: function (user, text) {
		var html = "<li>";
		html += "<div class='datePosted'>posted @";
		html += chat.get_display_time();
		html += "</div>";
		html += "<div class='orange'>"
		html += "Message"
		html += "</div>";
		html += "<div class='messageBody'>";
		html += user.get_user_name() + " " + text;
		html += "</div>";
		html += "</li>";
		return html;
	}



}


$(document).bind('update_scroll', function () {
	chat_scroll_bottom();
});

$(document).bind('reboot_rooms', function () {


	$('.chatBox').not('.empty').each(function () {
		ui_empty_room($(this).attr("id").charAt($(this).attr("id").length - 1));
	});

	$.each(chat.rooms, function () {
		this.reinitialize();
		create_new_room();
		this.parse_and_display();
		this.update_member_display();
	});

	try {
		chatAdvice();
	} catch (e) {

	}

});


$(document).bind('disconnected', function () {
	//then get the old service url
	var url = chat.connection.service;
	//kill the connection
	chat.connection = null;
	//restart:
	chat.connection = new Strophe.Connection(url);
	chat.connection.addHandler(chat.on_message, null, "message", "chat");
	chat.connection.addHandler(chat.on_presence, null, "presence", null);
	chat.attach();
	$.each(chat.rooms, function () {
		this.rejoin(); // basically chat.connection.muc.join(this.room_jid, room_nick, chat.muc_msg_router, null);
	});
	chat.reconnect_timeout = null;
});


var chat = null;
var type = null;
var roster = null;


$(window).unload(function () {
	chat.reconnect_timeout = "blocked";
	chat.connection.sync = true;
	chat.connection.flush();
	chat.connection.disconnect();
});

$(document).bind("ready_to_chat", function (ev, data) {
	
	chat = new Chat(data.node, data.domain, data.url); //node, domain, url
	chat.attach(data.node); //node
	
	$(document).bind("client_roster", function (ev, data) {
		$.each(data.roster, function () {
			if (!chat.lists.friends.find_user(this.user_jid)) {
				chat.lists.friends.add_user(this);
			}
		});

		$(document).trigger("get_real_friends");
	});

	$(document).bind("get_real_friends", function () {
		try {
			$.getJSON('index.php?r=mystuff/getfriends_chat', function (data) {
				chat.set_my_thumbnail(data['my_thumbnail']);
				chat.set_my_user_name(data['my_name']);
				chat.send_presence(); //I'm here!
			});
		} catch (e) {
			//console.log(e);
		}
		chat.update_list();
	});

});





/**
 * Add a plugin called "Roster"
 *	This stores a user list
 *	Which we use to bring our initial list "roster" to life
 *	with XMPP presences
 */

Strophe.addConnectionPlugin('roster', {
	init: function (connection) {
		this.connection = connection;
		this.roster = {};
		Strophe.addNamespace('ROSTER', 'jabber:iq:roster');
	},
	/**
	 * statusChanged, called when the status of chat.connection changes
	 * This sends an IQ stanza which gets my full roster
	 * Is this necessary? 
	 * @param {Strophe.Status} status - the status of the strophe connection
	 * 
	 */


	statusChanged: function (status) {
		if (status === Strophe.Status.ATTACHED) {
			this.roster = {};

			// set up handlers for updates
			this.connection.addHandler(this.roster_changed.bind(this), Strophe.NS.ROSTER, "iq", "set");

			// build and send initial roster query
			//console.log("building initial roster query!");
			var roster_iq = $iq({
				type: "get"
			}).c('query', {
				xmlns: Strophe.NS.ROSTER
			});

			var that = this;
			this.connection.sendIQ(roster_iq, function (iq) {

				$(iq).find("item").each(function () {
					var jid = $(this).attr('jid');
					// User(user_name, user_id, jid, domain, subscription, username, flag, thumbnail, interests)
					var contact = new User($(this).attr('name'), null, jid, $(this).attr('jid').split("@")[1], null, null, null);
					contact.set_subscription($(this).attr('subscription') || "none");
					that.roster[jid] = contact;
				});


				$(document).trigger('client_roster', that);

			});


		} else if (status === Strophe.Status.DISCONNECTED) {
			//
		}
	},

	/**
	 * roster_changed
	 * This is called when there is an actual change to your roster
	 * i.e. a new friend is added
	 * @param {iq} the IQ stanza for a roster update
	 * @return true as with all handlers
	 */

	roster_changed: function (iq) {
		try {

			var item = $(iq).find('item');
			var subscription = $(iq).find('subscription');
			var jid = $(item).attr('jid');

			if (item == null || item == undefined) {
				//console.log (iq);
				return true;
			}

			if (subscription === "remove") {
				// removing contact from roster
				delete this.roster[jid];
			} else if (subscription === "none") {
				// adding contact to roster
				var contact = new User(item.attr('name'), null, item.attr('jid'), item.attr('jid').split("@")[1], null, null, null);
				contact.set_subscription(item.attr('subscription') || "none");
				item.contacts[jid] = contact;

			} else {
				//new subscription
				var contact = new User(item.attr('name'), null, item.attr('jid'), item.attr('jid').split("@")[1], null, null, null);
				contact.set_user_name(item.attr('name') || contact.name);
				//contact.set_subscription = ( subscription || contact.get_subscription );
				this.roster[jid] = contact;

			}

			// acknowledge receipt
			this.connection.send($iq({
				type: "result",
				id: $(iq).attr('id')
			}));

			// notify user code of roster changes
			$(document).trigger("client_roster", this);
			//$(document).trigger("get_real_friends");
			chat.send_presence();
		} catch (e) {
			//console.log(e);
		}

		return true;
	}


});

$(document).bind("update_storage", function () {
	localStorageRooms = new Array();

	$.each(chat.rooms, function () {
		var localRoom = {
			room_key: this.room_key,
			room_jid: this.room_jid,
			members: this.members,
			msg_log: new Array(),
			timestamp: new Date().getTime()

		}

		$(this.msg_log).each(function () {
			if ($(this).find('delay').length < 0) {
				localRoom.msg_log.push(Strophe.serialize(this));
			}
		});

		localStorageRooms.push(localRoom);
	});

	localStorageRooms = JSON.stringify(localStorageRooms);

	if (typeof (localStorage) == 'undefined') {
		//no support
	} else {
		try {
			localStorage.setItem("rooms", localStorageRooms)
		} catch (e) {
			//console.log(e);
		}
	}
});


/** 
 * TODO please find homes for me :(
 * 
in_room: function (codusys) {//checks if a user is in the room
closeRoom: function () {//close a chatRoom with the X button

//page notifications, e.g. sound, flashing title

window.onblur = function () {//console.log("blur");winFocus = false; pageTitle = $("title").html(); }
window.onfocus = function () {//console.log("focus");winFocus = true; flashTitle(false); }
$(document).bind("flashtitle")

$($.each(chatRoom), function () {
	this.input.click(function () { $('.numberInChat').unblink();} );
	this.area.mouseover(function () { $('.numberInChat').unblink();} );
}

*/

/*
Plugin to implement the MUC extension. http://xmpp.org/extensions/xep-0045.html
*/
/* jslint configuration: */
/* global document, window, setTimeout, clearTimeout, console,
	XMLHttpRequest, ActiveXObject,
	Base64, MD5,
	Strophe, $build, $msg, $iq, $pres
*/


Strophe.addConnectionPlugin('muc', {
	_connection: null,
	_roomMessageHandlers: [],
	_roomPresenceHandlers: [],
	// The plugin must have the init function
	/***Function
	Initialize the MUC plugin. Sets the correct connection object and
	extends the namesace.
	*/
	init: function (conn) {
		this._connection = conn;
		/* extend name space
		 *  NS.MUC - XMPP Multi-user chat namespace
		 *			  from XEP 45.
		 *
		 */
		Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner");
		Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin");
	},
	/***Function
	Join a multi-user chat room
	Parameters:
	(String) room - The multi-user chat room to join.
	(String) nick - The nickname to use in the chat room. Optional
	(Function) msg_handler_cb - The function call to handle messages from the
	specified chat room.
	(Function) pres_handler_cb - The function call back to handle presence
	in the chat room.
	(String) password - The optional password to use. (password protected
	rooms only)
	*/
	join: function (room, nick, msg_handler_cb, pres_handler_cb, password) {
		var room_nick = this.test_append_nick(room, nick);
		var msg = $pres({
			from: this._connection.jid,
			to: room_nick
		}).c("x", {
			xmlns: Strophe.NS.MUC
		}).up().c('thumbnail').t(chat.my_thumbnail).up().c('user_name').t(chat.my_user_name);
		if (password) {
			var password_elem = Strophe.xmlElement("password", [], password);
			msg.cnode(password_elem);
		}
		if (msg_handler_cb) {
			this._roomMessageHandlers[room] = this._connection.addHandler(function (stanza) {
				var from = stanza.getAttribute('from');
				var roomname = from.split("/");
				// filter on room name
				if (roomname.length > 1 && roomname[0] == room) {
					return msg_handler_cb(stanza);
				} else {
					return true;
				}
			}, null, "message", null, null, null);
		}

		this._connection.send(msg);
	},
	/***Function
	Leave a multi-user chat room
	Parameters:
	(String) room - The multi-user chat room to leave.
	(String) nick - The nick name used in the room.
	(Function) handler_cb - Optional function to handle the successful leave.
	Returns:
	iqid - The unique id for the room leave.
	*/
	leave: function (room, nick, handler_cb) {

		this._connection.deleteHandler(this._roomMessageHandlers[room]);
		this._connection.deleteHandler(this._roomPresenceHandlers[room]);
		var room_nick = this.test_append_nick(room, nick);
		var presenceid = this._connection.getUniqueId();
		var presence = $pres({
			type: "unavailable",
			id: presenceid,
			from: this._connection.jid,
			to: room_nick
		}).c("x", {
			xmlns: Strophe.NS.MUC
		});
		this._connection.addHandler(handler_cb, null, "presence", null, presenceid, null);
		this._connection.send(presence);
		return presenceid;
	},
	/***Function
	Parameters:
	(String) room - The multi-user chat room name.
	(String) nick - The nick name used in the chat room.
	(String) message - The message to send to the room.
	Returns:
	msgiq - the unique id used to send the message
	*/
	message: function (room, nick, message) {
		var room_nick = this.test_append_nick(room, nick);
		var msgid = this._connection.getUniqueId();
		var msg = $msg({
			to: room_nick,
			from: this._connection.jid,
			type: "groupchat",
			id: msgid
		}).c("body", {
			xmlns: Strophe.NS.CLIENT
		}).t(message);
		msg.up().c("x", {
			xmlns: "jabber:x:event"
		}).c("composing");
		this._connection.send(msg);
		return msgid;
	},
	/***Function
	Start a room configuration.
	Parameters:
	(String) room - The multi-user chat room name.
	Returns:
	id - the unique id used to send the configuration request
	*/
	configure: function (room) {
		//send iq to start room configuration
		var config = $iq({
			to: room,
			type: "get"
		}).c("query", {
			xmlns: Strophe.NS.MUC_OWNER
		});
		var stanza = config.tree();
		return this._connection.sendIQ(stanza, function () {}, function () {});
	},
	/***Function
	Cancel the room configuration
	Parameters:
	(String) room - The multi-user chat room name.
	Returns:
	id - the unique id used to cancel the configuration.
	*/
	cancelConfigure: function (room) {
		//send iq to start room configuration
		var config = $iq({
			to: room,
			type: "set"
		}).c("query", {
			xmlns: Strophe.NS.MUC_OWNER
		}).c("x", {
			xmlns: "jabber:x:data",
			type: "cancel"
		});
		var stanza = config.tree();
		return this._connection.sendIQ(stanza, function () {}, function () {});
	},
	/***Function
	Save a room configuration.
	Parameters:
	(String) room - The multi-user chat room name.
	(Array) configarray - an array of form elements used to configure the room.
	Returns:
	id - the unique id used to save the configuration.
	*/
	saveConfiguration: function (room, configarray) {
		var config = $iq({
			to: room,
			type: "set"
		}).c("query", {
			xmlns: Strophe.NS.MUC_OWNER
		}).c("x", {
			xmlns: "jabber:x:data",
			type: "submit"
		});
		for (var i = 0; i >= configarray.length; i++) {
			config.cnode(configarray[i]);
		}
		var stanza = config.tree();
		return this._connection.sendIQ(stanza, function () {}, function () {});
	},
	/***Function
	Change the current users nick name.
	Parameters:
	(String) room - The multi-user chat room name.
	(String) user - The new nick name.
	*/
	changeNick: function (room, user) {
		var room_nick = this.test_append_nick(room, user);
		var presence = $pres({
			from: this._connection.jid,
			to: room_nick
		}).c("x", {
			xmlns: Strophe.NS.MUC
		});
		this._connection.send(presence.tree());
	},

	test_append_nick: function (room, nick) {
		var room_nick = room;
		if (nick) {
			room_nick += "/" + Strophe.escapeNode(nick);
		}
		return room_nick;
	}
});
