function PrevNext(item_root, click_root, prev_next_root, num_per_page, prev_next_areas) {
    this.MAX_VISIBLE_PARTS = num_per_page;
	this.setRoots(item_root, click_root);
	this.current_index = 0;
	this.next_elem = document.createElement('div');
	Tools.setBox(this.next_elem, prev_next_areas.next);
	this.prev_elem = document.createElement('div');
	Tools.setBox(this.prev_elem, prev_next_areas.prev);
	$(this.next_elem).addClass('button');
	Tools.addHoverEffect(this.next_elem, 'images/metal/metal_button_page_next.png', '42');
	$(this.prev_elem).addClass('button');
	Tools.addHoverEffect(this.prev_elem, 'images/metal/metal_button_page_prev.png', '42');
	var prev_next = this;
	$(this.next_elem).click(function() {
		prev_next.next();
	});
	$(this.prev_elem).click(function() {
		prev_next.previous();
	});
	Tools.append(prev_next_root, this.prev_elem);
	Tools.append(prev_next_root, this.next_elem);

	this.page_text_elem = document.createElement('div');
	$(this.page_text_elem).addClass('page-counter');
	Tools.setBox(this.page_text_elem, prev_next_areas.page);
	Tools.append(prev_next_root, this.page_text_elem);

	this.adjustAll();
}

PrevNext.prototype.setRoots = function(item_root, click_root) {
	this.root = item_root;
	this.click_root = click_root;
}

PrevNext.prototype.next = function() {
	if (!this.canNext())
		return;
	this.current_index = this.current_index + this.MAX_VISIBLE_PARTS;
	this.adjustAll();
};

PrevNext.prototype.previous = function() {
	if (!this.canPrevious())
		return;
	this.current_index = this.current_index - this.MAX_VISIBLE_PARTS;
	this.adjustAll();
};

PrevNext.prototype.adjustVisibility = function(index, elem, click) {
	if (index >= this.current_index + this.MAX_VISIBLE_PARTS || index < this.current_index) {
		Tools.hide(elem);
		Tools.hide(click);
	} else {
		Tools.show(elem);
		Tools.show(click);
	}
};

PrevNext.prototype.enableButton = function(button) {
	Tools.enableHoverEffect(button, '42');
};

PrevNext.prototype.disableButton = function(button) {
	Tools.disableHoverEffect(button, '42');
};

PrevNext.prototype.canPrevious = function() {
	return this.current_index > 0;
};

PrevNext.prototype.canNext = function() {
	return this.current_index < this.getMaxIndex() - (this.MAX_VISIBLE_PARTS - 1);
};

PrevNext.prototype.getMaxIndex = function() {
	if (!this.root)
		return 0;
	var parts = $(this.root).children();
	return parts.size() - 1;
};

PrevNext.prototype.adjustAll = function() {
	this.current_index = Math.min(Math.max(0, this.current_index), this.getMaxIndex());
	if (this.root) {
		var parts = $(this.root).children();
		var click_parts = $(this.click_root).children();
		for (var i = 0; i < parts.size(); i++) {
			this.adjustVisibility(i, parts[i], click_parts[i]);
		}
	}
	if (!this.canNext())
		this.disableButton(this.next_elem);
	else
		this.enableButton(this.next_elem);
	if (!this.canPrevious())
		this.disableButton(this.prev_elem);
	else
		this.enableButton(this.prev_elem);
	var current_page = Math.ceil((this.current_index + 1)/this.MAX_VISIBLE_PARTS);
	current_page = current_page > 0 ? current_page : 1;
	var page_count = Math.ceil((this.getMaxIndex() + 1)/this.MAX_VISIBLE_PARTS);
	page_count = page_count > 0 ? page_count : 1;
	$(this.page_text_elem).html("<p>PAGE " + current_page + " OF " + page_count + "</p>");
	if (page_count > 1) {
		$(this.page_text_elem).addClass("multiple-pages");
	}
};

var Tools = {
	TOWN_WIDTH: 960,
	TOWN_HEIGHT: 600,
	MENU_WIDTH: 160,
	TOPBAR_HEIGHT: 32,
	favorite_cache: {},

	init_favorite_cache: function(user_ids) {
		Tools.favorite_cache = {};
		$.each(user_ids, function() {
			Tools.favorite_cache[this] = 1;
		});
	},

	is_favorite: function(user_id) {
		return !!Tools.favorite_cache[user_id];
	},

	toggle_favorite: function(user_id) {
		var rpc_name;
		if (Tools.is_favorite(user_id)) {
			rpc_name = 'remove_favorite_user';
			delete Tools.favorite_cache[user_id];
		} else {
			rpc_name = 'add_favorite_user';
			Tools.favorite_cache[user_id] = 1;
		}
		Tools.rpc(rpc_name, {user_id: user_id});
	},

	log: function(msg) {
		if (window.console && window.console.log)
			window.console.log(msg);
	},
	trace: function() {
//		if (window.console && window.console.trace)
//			window.console.trace();
	},
	do_set_user: function(user) {
		Tools._user = user;
	},
	set_user: function(user) {
		Tools.do_set_user(user);
		Events.doHandleEvent({event: 'user_updated', params: user});
	},
	get_user: function() {
		return Tools._user;
	},
	rpc: function(action, args, success_func, error_func) {
		return Tools.do_rpc('/rpc/' + action, args, success_func, error_func);
	},
	do_rpc: function(action, args, success_func, error_func) {
		if (!args)
			args = {}
		Tools.log('Starting rpc + ' + action);
		return $.ajax({url: action, type: 'POST', data: args, error: error_func, success: function(data, text_unused, xhr) {
			Tools.log('Completed rpc + ' + action);
			if (!xhr.status) {
				error_func(xhr);
				return;
			}
			if (data) {
				var piggyback_user = data.piggyback_user;
				if (piggyback_user) {
					Tools.set_user(piggyback_user);
					data = data.result;
				}
			}
			if (success_func)
				success_func(data);
		}});
	},
	getAppletHtml: function(code, archive, width, height, param_list) {
		var params = "<param name=\"initial_focus\" value=\"false\"/>";
//		String attrs = "";
		$(param_list).each(function() {
			params += "<param name=\""+this[0]+"\" value=\""+this[1]+ "\" />";
		});
		//params += '<param name="separate_jvm" value="true" />';
		params += '  <param name="code" value="' + code + '" />';
		params += '  <param name="archive" value="' + archive + '" />';
		params += '  <param name="mayscript" value="true" />';
		var result = "";

		code = code.replace(/\./g, '/') + '.class';
		result += '<!--[if !IE]> -->';
		result += '<object id="applet" name="the_applet" ';
		result += '			  type="application/x-java-applet;version=1.6"';
		result += '			  archive="' + archive + '" ';
		result += '			  height="' + height + '" width="' + width + '" >';
		result += params;
/*		result += "<applet code=\""+code+"\" archive=\""+archive+"\" width='"+width+"' height='"+height+"' mayscript>";
		result += params;
		result += "</applet>";*/
		result += '</object>';
		result += '<!--<![endif]-->';
		result += '<!--[if IE]>';
		result += '  <object id="applet" name="applet" type="application/x-java-applet;version=1.6" classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" ';
		result += '  	codebase="http://java.sun.com/update/1.6.0/jinstall-6u23-windows-i586.cab#Version=1,6,0,10"';
		result += '  	height="' + height + '" width="' + width+ '" > ';
		result += params;
		result += '</object>';
		result += '<![endif]-->';

/*		result += "<object classid=\"clsid:8AD9C840-044E-11D1-B3E9-00805F499D93\" type=\"application/x-java-applet;version=1.6\"";
		//result += "codebase=\"http://javadl.sun.com/webapps/download/AutoDL?BundleId=19588#Version=1,5,0,0\"";
		result += "codebase=\"http://java.sun.com/update/1.6.0/jinstall-6u23-windows-i586.cab#Version=1,6,0,10\"";
		result += "height=\""+height+"\" width=\""+width+"\" > ";
		result += "<param name=\"code\" value=\""+code+ "\" />";
		result += "<param name=\"archive\" value=\""+archive+ "\" />";
		result += "<param name=\"mayscript\" value=\"true\" />";

		result += params;
		result += "<comment>";

		result += "<applet code=\""+code+"\" archive=\""+archive+"\" width='"+width+"' height='"+height+"' mayscript>";
		result += params;
		result += "</applet>";
		result += "</comment>";
		result += "</object> ";*/
		return result;
	},
	createAppletFrame: function(code, archive, width, height, params) {
		var iframe = document.createElement('iframe');
		$(iframe).css('width', width + "px");
		$(iframe).css('height', height + "px");
		$(iframe).css('border', "0");
		$(iframe).attr('src', '/files/empty_frame.html');
		$(iframe).attr('frameBorder', '0');
		$(iframe).attr('border', '0');
		$(iframe).attr('allowtransparency', 'true');
		$(iframe).attr('scrolling', 'no');
		$(iframe).load(function() {
			$(iframe).unbind();
			var oDoc = iframe.contentWindow || iframe.contentDocument;
			if (oDoc.document) {
				oDoc = oDoc.document;
			}
			setTimeout(function() { // Wait a bit to add the applet to the frame to avoid hangs in some browsers
				var html = Tools.getAppletHtml(code, archive, width, height, params);
				$(oDoc.body).html(html);
			}, 1000);
		});
		return iframe;
	},
	getURLVar: function(urlVarName) {
		var urlHalves = String(document.location).split('?');
		var urlVarValue = '';
		if(urlHalves[1]){
			var urlVars = urlHalves[1].split('&');
			for(var i=0; i<=(urlVars.length); i++){
				if(urlVars[i]){
					var urlVarPair = urlVars[i].split('=');
					if (urlVarPair[0] && urlVarPair[0].toLowerCase() == urlVarName.toLowerCase()) {
						urlVarValue = decodeURIComponent(urlVarPair[1]);
					}
				}
			}
		}
		return urlVarValue;
	},
	getAnalyticsTracker: function() {
		return _gaq;
	},
	registerEnter: function(element, callback) {
		$(element).keyup(function(e) {
			return Tools.getKeyCode(e) != 13;
		});
		$(element).keydown(function(e) {
			if (Tools.getKeyCode(e) == 13) {
				callback();
				return false;
			}
		});
		$(element).keypress(function(e) {
			return e.which != 13;
		});
	},

	createElement: function(tag, parent_element, text, classes) {
		var element = document.createElement(tag);
		if (text) {
			$(element).html(text);
		}
		if (classes) {
			$(element).addClass(classes);
		}
		Tools.append(parent_element, element);
		return element;
	},

	createNumberInput: function(num_digits) {
		var input_element = document.createElement('input');
		$(input_element).attr('type', 'text');
		$(input_element).keypress(function(evt) {
			evt = (evt) ? evt : window.event;
			if (evt.which) {
				return (evt.which >= 48 && evt.which <= 57 && $(input_element).val().length < num_digits) || evt.which == 8 || evt.which == 13;
			} else {
				return evt.keyCode == 9 || evt.keyCode == 37 || evt.keyCode == 39 || evt.keyCode == 46 || evt.keyCode == 13;
			}
		});
		return input_element;
	},

	createRadioElement: function(name, checked) {
		var rdo;
		try {  
			rdo = document.createElement('<input type="radio" name="' + name + '"' + (checked ? ' checked="true" ' : '') + ' />');
		} catch(err) {
			rdo = document.createElement('input');
			if (checked)
				$(rdo).attr('checked', true);
		}
		$(rdo).attr('type', 'radio');
		$(rdo).attr('name', name);
		return rdo;
	},

	createLink: function(pre, a, post, f) {
		var link_elem = document.createElement('div');
		var pre_elem = document.createElement('span');
		$(pre_elem).text(pre);
		Tools.append(link_elem, pre_elem);
		var a_elem = document.createElement('a');
		$(a_elem).text(a);
		$(a_elem).attr('href', '#');
		$(a_elem).click(function() {
			f();
			return false;
		});
		Tools.append(link_elem, a_elem);
		var post_elem = document.createElement('span');
		$(post_elem).text(post);
		Tools.append(link_elem, post_elem);
		return link_elem;
	},

	switchTypeSafely: function(elem, type) {
		try {
			elem.type = type;
		} catch(e) { 
		}
	},

	setupDynamicInlineInput: function(elem, inline_text) {
		var allow_type_change = $(elem).attr('type') == 'password';
		var cleared;
		var on_blur = function() {
			cleared = $(elem).val() == '';
			if (cleared) {
				$(elem).val(inline_text);
				if (allow_type_change)
					Tools.switchTypeSafely(elem, 'text');
			}
		};
		on_blur();
		$(elem).focus(function() {
			if (cleared) {
				$(elem).val('');
				if (allow_type_change)
					Tools.switchTypeSafely(elem, 'password');
			}
		});
		$(elem).blur(on_blur);
	},

	setupInlineInput: function(fake_elem, real_elem) {
		$(fake_elem).focus(function() {
			$(fake_elem).css("display", "none");
			$(real_elem).css("display", "inline");
			$(real_elem).focus();
		});
		$(real_elem).blur(function() {
			if ($(real_elem).val() == '') {
				$(fake_elem).css("display", "inline");
				$(real_elem).css("display", "none");
			}
		});
	},

	createClearElement: function() {
		var clear_elem = document.createElement('div');
		$(clear_elem).css('clear', 'both');
		return clear_elem;
	},

	createListenerElement: function() {
		var listener_elem = document.createElement('div');
		$(listener_elem).css('display', 'none');
		return listener_elem;
	},
	createDynamicUserElement: function(avatar_size) {
		var user_info = Tools.createUserElement(avatar_size);
		Tools.addUserListener(user_info.user_elem, function(user) {
			user_info.rebuilder(user);
		});
		return user_info.user_elem;
	},
	createUserElement: function(avatar_size) {
		var user_elem = document.createElement('div');
		var avatar_elem = document.createElement('div');
		var name_elem = document.createElement('div');
		var msg_icon_elem;
		var caps = Tools.get_user().capabilities;
		var canChat = jQuery.inArray("chat", caps) != -1;
		if (canChat) {
			msg_icon_elem = $('<div class="msg_icon" title="Send message"></div>')[0];
		}
		var favorite_elem = $('<div class="favorite_icon" title="Add to friends"></div>')[0];
		$(user_elem).addClass('user');
		$(name_elem).addClass('username');
		var avatar_info = Tools.insertDynamicAvatar(avatar_elem, avatar_size);
		var rebuilder = function(user) {
			$(name_elem).text(user.name);
			avatar_info.rebuilder(user);
			if (user.id != Tools.get_user().id) {
				if (Tools.is_favorite(user.id))
					$(favorite_elem).addClass('active');
				else
					$(favorite_elem).removeClass('active');
				Tools.append(user_elem, favorite_elem);
				$(user_elem).unbind('hover').hover(
					function() {$(favorite_elem).fadeIn(150);},
					function() {$(favorite_elem).fadeOut(150);}
				);
				$(favorite_elem).unbind("click").click(function() {
					$(favorite_elem).toggleClass('active');
					Tools.toggle_favorite(user.id);
				});
				if (canChat) {
					$(msg_icon_elem).unbind("click").click(function() {
						Tools.rebuildMessageInputBox(user);
					});
					Tools.append(user_elem, msg_icon_elem);
					$(user_elem).hover(
						function() {$(msg_icon_elem).fadeIn(150);},
						function() {$(msg_icon_elem).fadeOut(150);}
					);
				}
			}
		};
		Tools.append(user_elem, avatar_elem);
		Tools.append(user_elem, name_elem);
		return {user_elem: user_elem, rebuilder: rebuilder};
	},
	rebuildMessageInputBox: function(user) {
		var hideBox = function() {
			$("#messageInputContainer .closeButton").unbind("click");
			$("#messageInputContainer .messageForm").unbind("submit");
			$("#messageInputContainer input").val("");
			$("#messageInputContainer").hide();
			$("#messageInputContainer .username").text("");
		};

		$("#messageInputContainer .messageForm").submit(function() {
			var msg = $("#messageInputContainer input").val();
			if (msg != "") {
				Tools.rpc("send_chat_to_user", {user_id:user.id, msg:msg});
				hideBox();
			}
			return false;
		});
		
		$("#messageInputContainer .closeButton").click(function() {
			hideBox();
		});

		$("#messageInputContainer .username").text(user.name);
		$("#messageInputContainer").show();
		$("#messageInputContainer input").focus();
	},
	displayMessage: function(user, msg, parent_elem) {
		var container_elem = document.createElement("div");
		var msg_elem = document.createElement("span");
		var avatar_elem = document.createElement("div");
		$(container_elem).addClass("messageBlock");
		$(msg_elem).addClass("messageText");
		$(msg_elem).text(user.name + ": " + msg);
		$(avatar_elem).addClass("avatar");
		var avatar_info = Tools.insertDynamicAvatar(avatar_elem, 'small');
		avatar_info.rebuilder(user);
		Tools.append(container_elem, msg_elem);
		Tools.append(container_elem, avatar_elem);
		Tools.append(parent_elem, container_elem);
		window.setTimeout(function() {
			parent_elem.removeChild(container_elem);	
		}, 10000);
	},
	getFontSize: function(text, min_size, max_size) {
		if (text.length > 18) {
			return min_size;
		} else {
			return max_size
		}
	},
	getAvatarMaps: function(user, instances) {
		if (!instances)
			instances = user.instances;
		var instance_map = {};
		for (var i = 0; i < instances.length; i++)
			instance_map[i] = instances[i];
		var parts_map = {};
		for (var i = 0; i < user.types.length; i++)
			parts_map[user.types[i]] = user.type_values[i];
		var color_map = {};
		for (var i = 0; i < user.color_groups.length; i++)
			color_map[user.color_groups[i]] = user.color_values[i];
		return {'instance_map': instance_map, 'parts_map': parts_map, 'color_map': color_map};
	},
	insertAvatar: function(parent_elem, size) {
		var avatar_info = Tools.insertDynamicAvatar(parent_elem, size);
		Tools.addUserListener(avatar_info.listener_elem, function(user) {
			avatar_info.rebuilder(user);
		});
	},
	insertDynamicAvatar: function(parent_elem, size) {
		Tools.empty(parent_elem);
		var listener_elem = document.createElement('div');
		var color_tracker;
		var rebuilder = function(user) {
			Tools.empty(listener_elem);
			var avatar_maps = Tools.getAvatarMaps(user);
			var color_tracker = GWTTools.insertAvatar(listener_elem, avatar_maps.parts_map, avatar_maps.instance_map, size);
			for (var i = 0; i < user.color_groups.length; i++)
				color_tracker(user.color_groups[i], user.color_values[i]);
		};
		Tools.append(parent_elem, listener_elem);
		return {rebuilder: rebuilder, listener_elem: listener_elem};
	},

	trackSetValue: function(elem) {
		return {
			onremove: function() {
			   $(elem).text('');
			},
			onset: function(value) {
			   $(elem).text(String(value));
			}
		};
	},
	trim: function(str) {
	    return str.replace(/^\s+|\s+$/g, ''); 
	},
	appendInput: function(element, input_text, input_type) {
		var input_element = document.createElement('input');
		var input_label = document.createElement('div');
		$(input_element).attr('type', input_type);
		$(input_label).text(input_text);
		$(input_label).addClass('label');
		$(element).append(input_label);
		$(element).append(input_element);
		return input_element;
	},

	appendInputRowToTable: function(element, input_text, input_type) {
		var tr_elem = document.createElement('tr');
		$(element).append(tr_elem);

		var td_elem = document.createElement('td');
		$(tr_elem).append(td_elem);
		var input_label = document.createElement('div');
		$(input_label).text(input_text);
		$(input_label).addClass('label');
		$(td_elem).append(input_label);

		td_elem = document.createElement('td');
		$(tr_elem).append(td_elem);
		var input_element = document.createElement('input');
		$(input_element).attr('type', input_type);
		$(td_elem).append(input_element);
		return input_element;
	},

	setSize: function(element, width, height) {
		$(element).css({width:width+"px", height:height+"px"});
	},

	setPos: function(element, x, y) {
		$(element).css({position:"absolute", left:x+"px", top:y+"px"});
	},

	toDownloadURL: function(url) {
		var hostname = window.location.host;
		if (!hostname.match(/appspot\.com$/)) {
			var dot_idx = hostname.indexOf('.');
			if (dot_idx != -1)
				hostname = "download." + hostname.substring(dot_idx + 1);
		}
		return window.location.protocol + '//' + hostname + '/files/' + url;
	},

	setAbsoluteCentered: function(element, width, height) {
		Tools.setPos(element, (Tools.TOWN_WIDTH - width)/2, (Tools.TOWN_HEIGHT - height)/2);
	},

	createScrollPiece: function(popup, name, width, height, x, y) {
		var piece = document.createElement('div');
		$(piece).css({background: "transparent url("+Tools.toDownloadURL("images/scroll/scroll_gui_"+name+".png")+")", position:"absolute"});
		$(piece).css({width:width+"px", height:height+"px"});
		$(piece).css({left:x+"px", top:y+"px"});
		$(popup).append(piece);
	},

	createScroll: function(popup, width, height) {
		var left_width = 53;
		var right_width = 60;
		var top_height = 88;
		var buttom_height = 67;
		var buttom_left_height = 125;
		var buttom_right_height = 125;
		Tools.createScrollPiece(popup, "TL", left_width, height-buttom_left_height, 0, 0);
		Tools.createScrollPiece(popup, "TR", right_width, height-buttom_right_height, width - right_width, 0);
		Tools.createScrollPiece(popup, "BL", left_width, buttom_left_height, 0, height - buttom_left_height);
		Tools.createScrollPiece(popup, "BR", right_width, buttom_right_height, 0 + width - right_width, height - buttom_left_height);
		Tools.createScrollPiece(popup, "T", width - left_width - right_width, top_height, left_width, 0);
		Tools.createScrollPiece(popup, "B", width - left_width - right_width, buttom_height, left_width, height - buttom_height);
		Tools.createScrollPiece(popup, "C", width - left_width - right_width, height - top_height - buttom_height, left_width, top_height);
		var content = document.createElement('div');
		var left_offset = 60;
		var right_offset = 60;
		var top_offset = 50;
		var buttom_offset = 20;
		$(content).css({position:"absolute", left:left_offset + "px", top:top_offset + "px", width:(width - left_offset - right_offset)+"px", height:(height - top_offset - buttom_offset)+"px"});
		Tools.append(popup, content);
		return content;
	},

	createPopup: function(width, height) {
		var popup_parent = document.createElement('div');
		$(popup_parent).addClass('ghosted');
		var popup = document.createElement('div');
		Tools.setAbsoluteCentered(popup, width, height);
		var content = Tools.createScroll(popup, width, height);
		Tools.append(popup_parent, popup);
		return {popup_parent: popup_parent, popup: content};
	},

	setCentered: function(element, height) {
		$(element).css('margin', (Tools.TOWN_HEIGHT - height)/2 + 'px auto 0 auto');
	},

	getCookie: function(c_name) {
		if (document.cookie.length>0) {
			c_start=document.cookie.indexOf(c_name + "=");
			if (c_start!=-1) { 
				c_start=c_start + c_name.length+1; 
				c_end=document.cookie.indexOf(";",c_start);
				if (c_end==-1) c_end=document.cookie.length;
				return unescape(document.cookie.substring(c_start,c_end));
			} 
		}
		return null;
	},

	push_interest: function(element, name, interest_id, listener, error_listener) {
		Tools.addHierarchyListener(element,
			function() {
				Interest.push_interest(name, interest_id, listener, error_listener);
			},
			function() {
				Interest.pop_interest(name, interest_id);
			}
		);
	},

	addEventListener: function(element, event_name, listener) {
		Tools.addHierarchyListener(element,
			function() {
				Events.addEventListener(event_name, listener);
			},
			function() {
				Events.removeEventListener(event_name, listener);
			}
		);
	},

	addUserListener: function(element, listener) {
		Tools.addEventListener(element, 'user_updated', function(evt) {
			listener(evt.params);
		});
		listener(Tools.get_user());
	},
	getAffiliate: function() {
		return Tools.getURLVar('affiliate');
	},

	setCookie: function(c_name,value,expiredays) {
		var exdate = new Date();
		exdate.setDate(exdate.getDate() + expiredays);
		document.cookie = c_name + "=" + escape(value)+
			((expiredays==null) ? "" : "; expires="+exdate.toGMTString()) + "; path=/";
	},

	getKeyCode: function(e) {
		return (window.event) ? event.keyCode : e.keyCode;
	},

	format_time_delta: function(seconds) {
		var conversions = [
			{seconds: 86400, units: 'day'},
			{seconds: 3600, units: 'hour'},
			{seconds: 60, units: 'minute'},
			{seconds: 1, units: 'second'}
		];
		for (var i = 0; i < conversions.length; i++) {
			if (seconds >= conversions[i].seconds) {
				var val = Math.ceil(seconds/conversions[i].seconds);
				if (val == 1.0)
					return val + " " + conversions[i].units;
				else
					return val + " " + conversions[i].units + "s";
			}
		}
	},

	hide: function(element) {
		$(element).css("display", "none");
	},

	show: function(element) {
		$(element).css("display", "");
	},

	empty: function(element) {
		$(element).children().each(function() {
			Tools.remove(this);
		});
	},

	after: function(elem1, elem2) {
		var had_html_parent = Tools.hasHTMLParent(elem2);
		$(elem1).after(elem2);
		if (!had_html_parent)
			Tools.callHierarchyAdded(elem2);
	},

	insertBefore: function(p, element) {
		var had_html_parent = Tools.hasHTMLParent(element);
		$(element).insertBefore(p);
		if (!had_html_parent)
			Tools.callHierarchyAdded(element);
	},

	prepend: function(p, element) {
		var had_html_parent = Tools.hasHTMLParent(element);
		$(p).prepend(element);
		if (!had_html_parent)
			Tools.callHierarchyAdded(element);
	},

	append: function(p, element) {
		var had_html_parent = Tools.hasHTMLParent(element);
		$(p).append(element);
		if (!had_html_parent) {
			Tools.callHierarchyAdded(element);
		}
	},

	loadScript: function(url, load_func) {
		var e = document.createElement('script');
		e.src = url;
		e.type = "text/javascript";
		$(e).load(load_func);
		Tools.append(document.body, e);
	},

	callHierarchyAdded: function(element) {
		if (Tools.hasHTMLParent(element)) {
			Tools.traverseChildren(element, function() {
				if (this.onadd != null) {
					this.onadd();
				}
			});
		}
	},

	hasHTMLParent: function(element) {
		var parents = $(element).parents();
		return parents.length > 0 && parents[parents.length - 1].tagName == "HTML";
	},

	callHierarchyRemoved: function(element) {
		if (Tools.hasHTMLParent(element)) {
			Tools.traverseChildren(element, function() {
				if (this.onremove != null)
					this.onremove();
			});
		}
	},

	remove: function(element) {
		Tools.callHierarchyRemoved(element);
		$(element).detach();
	},

	addHierarchyListener: function(part_div, add_listener, remove_listener) {
		var listeners = part_div.hierarchylisteners;
		if (listeners == null) {
			listeners = [];
			part_div.hierarchylisteners = listeners;
		}
		listeners.push(
			{
				onadd : add_listener,
				onremove : remove_listener
			}
		);
		if (Tools.hasHTMLParent(part_div) && add_listener)
			add_listener();
	},

	traverseChildren: function(element, func) {
		var listeners = element.hierarchylisteners;
		if (listeners != null) {
			$.each(listeners, func);
		}
		$(element).children().each(function() {
			Tools.traverseChildren(this, func);
		});
	},
	createBoxFromBox: function(box, dx, dy) {
		return {x:box.x+dx, y:box.y+dy, width:box.width, height:box.height};
	},
	createBox: function(box, p) {
		var elem = document.createElement("div");
		Tools.setBox(elem, box);
		Tools.append(p, elem);
		return elem;
	},
	createBoxColor: function(color_string, box, p) {
		var color = Tools.createBox(box, p);
		$(color).css("background-color", color_string);
		return color;
	},
	createBoxImage: function(image_url, box, p) {
		var image = Tools.createBox(box, p);
		$(image).css("background", "url(" + Tools.toDownloadURL(image_url) + ") no-repeat");
		return image;
	},
	createImage: function(info) {
		var image = document.createElement("img");
		image.src = Tools.toDownloadURL(info.image_normal);
		Tools.setPos(image, info.image_x, info.image_y);
		return image;
	},
	getColorMap: function() {
		var color_to_value = {};
		var colors = avatars.colors;
		for (var i = 0; i < colors.length; i++) {
			color_to_value[colors[i].name] = colors[i];
		}
		return color_to_value;
	},

	getGroupMap: function() {
		var name_to_group = {};
		var groups = avatars.color_groups;
		for (var i = 0; i < groups.length; i++) {
			var group = groups[i];
			name_to_group[group.name] = group;
		}
		return name_to_group;
	},

	getQuestMap: function() {
		var base_to_quest = {};
		jQuery.each(quests, function() {
			if (this.gamebase)
				base_to_quest[this.gamebase] = this;
			else {
				jQuery.each(this.parts, function() {
					base_to_quest[this.gamebase] = this;
				});
			}
		});
		return base_to_quest;
	},

	getPartMap: function() {
		var name_to_part = {};
		var parts = avatars.parts;
		for (var i = 0; i < parts.length; i++) {
			name_to_part[parts[i].name] = parts[i];
		}
		return name_to_part;
	},

	contractToURL: function(contract, user_id) {
	   return 'https://www.plimus.com/jsp/buynow.jsp?contractId=' + contract + '&custom1=' + user_id;
	},

	createTable: function(caption_text, head_labels, head_classes) {
		var table = document.createElement('table');
		var caption = document.createElement('caption');
		$(caption).text(caption_text);
		Tools.append(table, caption);
		if (head_labels) {
			var thead = document.createElement('thead');
			Tools.append(table, thead);
			var tr = document.createElement('tr');
			Tools.append(thead, tr);
			for (var i = 0; i < head_labels.length; i++) {
				var th = document.createElement('th');
				$(th).text(head_labels[i]);
				if (head_classes) {
					$(th).addClass(head_classes[i]);
				}
				Tools.append(tr, th);
			}
		}
		var tbody = document.createElement('tbody');
		Tools.append(table, tbody);
		return {table: table, tbody: tbody};
	},

	iterateColorGroups: function(part, group_map, global_groups_map, callback) {
		var added_groups = {}
		var types = part.types;
		for (var type_name in types) {
			var type_groups = types[type_name];
			for (var i = 0; i < type_groups.length; i++) {
				var group = group_map[type_groups[i]];
				if (global_groups_map[group.name] == null && added_groups[group.name] == null) {
					added_groups[group.name] = 1;
					callback(group);
				}
			}
		}
	},

	createBuy: function(cost_oddies, cost_points, disabled, buy_function, buy_text) {
		if (!buy_text)
			buy_text = 'BUY IT';
		return Tools.doCreateBuy({oddies: cost_oddies, points: cost_points}, buy_text, true, buy_function, disabled);
	},

	addHoverEffect: function(elem, image, image_height, info) {
		$(elem).css("background-image", "url(" + Tools.toDownloadURL(image) + ")");
		$(elem).css("background-attachment", "scroll");
		$(elem).css("background-color", "transparent");
		$(elem).css("background-repeat", "no-repeat");
		Tools.enableHoverEffect(elem, image_height, info);
	},

	enableHoverEffect: function(elem, image_height, info) {
		$(elem).unbind("mouseenter");
		$(elem).unbind("mouseleave");
		$(elem).css("cursor", "pointer");
		var mouse_leave = function() {
			$(elem).css("background-position", "0px 0px");
			if (info) {
				$(elem).css("color", info.color_normal);
			}
		};
		$(elem).bind("mouseenter", function() {
			$(elem).css("background-position", "0px -"+image_height+"px");
			if (info) {
				$(elem).css("color", info.color_active);
			}
		});
		$(elem).bind("mouseleave", mouse_leave);
		mouse_leave();
	},

	disableHoverEffect: function(elem, image_height) {
		$(elem).unbind("mouseenter");
		$(elem).unbind("mouseleave");
		$(elem).css("cursor", "default");
		$(elem).css("color", "#222");
		$(elem).css("background-position", "0px -"+image_height*2+"px");
	},

	doCreateBuy: function(cost, buy_button_msg, can_buy_unique, buy_function, disable_button) {
		var buy_button = document.createElement('div');
		$(buy_button).text(buy_button_msg);
		Tools.addHoverEffect(buy_button, "images/metal/metal_button_buy.png", town.armory_area_button_buy.height, theme.blue_button);
		$(buy_button).addClass("huge-button");
		Tools.hide(buy_button);
		return Tools.createCustomBuy(cost, can_buy_unique, function(can_buy) {
			Tools.show(buy_button);
			$(buy_button).unbind("click");
			if (can_buy && can_buy_unique && !disable_button) {
				$(buy_button).click(buy_function);
				Tools.enableHoverEffect(buy_button, town.armory_area_button_buy.height);
			} else {
				Tools.disableHoverEffect(buy_button, town.armory_area_button_buy.height);
			}
			return buy_button;
		});
	},

	setBox: function(elem, box) {
		Tools.setSize(elem, box.width, box.height);
		Tools.setPos(elem, box.x, box.y);
	},

	createCloseCornerButton: function(area, close_func) {
		var close_corner_button = document.createElement('div');
		Tools.setBox(close_corner_button, area);
		Tools.addHoverEffect(close_corner_button, "images/metal/metal_button_corner_close.png", area.height, theme.red_button);
		$(close_corner_button).addClass("corner-button");
		if (close_func) {
			$(close_corner_button).click(close_func);
		} else {
			$(close_corner_button).click(function() {
				Menu.navigate('town');
			});
		}
		return close_corner_button;
	},

	createCustomBuy: function(cost, can_buy_unique, buy_button_func) {
		var buy_messages = document.createElement('div');
		var button_container = document.createElement('div');
		var buy_listener = function(user) {
			var oddies = user.oddies;
			var points = user.points;
			var paying = user.paying;
			Tools.empty(buy_messages);
			var buy_button = buy_button_func(IS_DEVEL || (points >= cost.points && oddies >= cost.oddies && (!cost.paying || paying)));
			if (buy_button)
				Tools.append(button_container, buy_button);
			if (can_buy_unique) {
				if (points < cost.points) {
					var insuf_elem = document.createElement('div');
					$(insuf_elem).addClass('insufficient');
					$(insuf_elem).text('Not enough gold.');
					Tools.append(buy_messages, insuf_elem);
				}
				if (cost.paying && !paying) {
					var insuf_elem = document.createElement('div');
					$(insuf_elem).addClass('insufficient');
					$(insuf_elem).text("Only available for paying users.");
					var buy_more = Tools.createLink(' - buy (not trade) oddies in the ', 'bank', '', function() {
						Menu.navigate('bank');
					});
					Tools.append(insuf_elem, buy_more);
					Tools.append(buy_messages, insuf_elem);
				}
				if (oddies < cost.oddies) {
					var insuf_elem = document.createElement('div');
					$(insuf_elem).addClass('insufficient');
					$(insuf_elem).text('Not enough oddies.');
					var buy_more = Tools.createLink(' - buy more in the ', 'bank', '', function() {
						Menu.navigate('bank');
					});
					Tools.append(insuf_elem, buy_more);
					Tools.append(buy_messages, insuf_elem);
				}
			} else {
				var insuf_elem = document.createElement('div');
				$(insuf_elem).addClass('insufficient');
				$(insuf_elem).text('You already own this item.');
				Tools.append(buy_messages, insuf_elem);
			}
//var kids = $(buy_messages).children();
//var len = kids.addClass("hilite").length;
//console.log(len + " children");
		};
		Tools.addUserListener(buy_messages, buy_listener);
		return {"messages": buy_messages, "button": button_container};
	},
	createPriceElement: function(oddies, points) {
		var price_elem = document.createElement('div');
		$(price_elem).addClass('price');
		var text_elem = document.createElement('span');
		$(text_elem).text("Price:");
		Tools.append(price_elem, text_elem);
		var oddies_elem = document.createElement('span');
		$(oddies_elem).addClass('oddies');
		$(oddies_elem).text(oddies);
		Tools.append(price_elem, oddies_elem);
		var points_elem = document.createElement('span');
		$(points_elem).addClass('points');
		$(points_elem).text(points);
		Tools.append(price_elem, points_elem);
		return price_elem;
	},
	showItem: function(listener_elem, item, show_func, remove_func) {
		if (item.depends != null) {
			Tools.addUserListener(listener_elem, function(user) {
				if (jQuery.inArray(item.depends, user.bought_items) == -1) {
					remove_func();
				} else {
					show_func();
				}
			});
		} else {
			show_func();
		}
	},
	createColorChooser: function(color_to_value, default_color, group, offset_y, color_func, allowed_colors, x_offset) {
		var root = document.createElement('div');
		var color_group_elem;
		var click_group_elem;
		var elem_width = town.palette_swatch_highlight_area.width;
		var elem_height = town.palette_swatch_highlight_area.height;
		if (group.values.length <= 8) {
			var offset_box_frame = Tools.createBoxFromBox(town.palette_8_area, x_offset, offset_y);
			var offset_box_swatchs = Tools.createBoxFromBox(town.palette_8_swatch_area, x_offset, offset_y);
			Tools.createBoxImage("images/town/palette_8_background.png", offset_box_swatchs, root);
			color_group_elem = Tools.createBox(offset_box_swatchs, root);
			Tools.createBoxImage("images/town/palette_8.png", offset_box_frame, root);
			click_group_elem = Tools.createBox(offset_box_swatchs, root);
			$(click_group_elem).click(function() {
				return false;
			});
		} else {
			var offset_box_frame = Tools.createBoxFromBox(town.palette_24_area, x_offset, offset_y);
			var offset_box_swatchs = Tools.createBoxFromBox(town.palette_24_swatch_area, x_offset, offset_y);
			color_group_elem = Tools.createBox(offset_box_swatchs, root);
			Tools.createBoxImage("images/town/palette_24.png", offset_box_frame, root);
			click_group_elem = Tools.createBox(offset_box_swatchs, root);
		}
		for (var i = 0; i < group.values.length; i++) {
			(function() {
			var color = group.values[i];
			if (allowed_colors && $.inArray(color, allowed_colors) == -1)
				return;
			var value = color_to_value[color].value;
			var val_elem = document.createElement('div');
			Tools.setSize(val_elem, elem_width, elem_height);
			$(val_elem).addClass('color-block');
			$(val_elem).css('background-color', 'rgb(' + value[0] + ',' + value[1] + ',' + value[2] + ')');
			Tools.append(color_group_elem, val_elem);

			var val_elem_click = document.createElement('div');
			Tools.setSize(val_elem_click, elem_width, elem_height);
			$(val_elem_click).addClass('color-block');
			Tools.append(click_group_elem, val_elem_click);
			$(val_elem_click).click(function() {
				color_func(group, color);
			});
			if (color == default_color) {
				var val_elem_highlight = Tools.createBoxImage("images/town/palette_swatch_highlight.png", town.palette_swatch_highlight_area, val_elem_click);
				$(val_elem_highlight).css({position:"relative", left:"0px", top:"0px"});
			}
			})();
		}
		Tools.append(color_group_elem, Tools.createClearElement());
		return root;
	},
	random_choice: function(set) {
		return set[Math.floor(Math.random() * set.length)];
	}
}

