$(function() {
	Util.Init();
});

var Map = null;

var Util = {
	Data: null,
	MapArea: null,
	InitActions: [],
	LayerTypes: {Places:1,Boundaries:2},
	LayerServices: null,
	LayerOffsets: {Places:100,Boundaries:0},
	MoveData: null,
	MovePackage: null,
	DataTypes: {None:0, Point:1, Marker:2, Household:3, HouseholdGroup:4, Place:5},
	MapMask: null,
	ProcessMask: null,
	ProcessBalloon: null,
	ProcessCount: null,
	ProcessListItem: null,
	Hold: null,
	AddressProps: ["street1","street2","city","state","zip","country"],
	AddressPropsParams:{a:"street",c:"city",s:"state",p:"zip",n:"country"},
	CoordinateProps: ["latitude","longitude"],
	CoordinatePropsParams:{lat:"latitude",lng:"longitude"},
	BaseAnchor: "<a onclick='return Main.UpdateStateHandler(this);'/>",
	Preloader: null,
	IsTouch: (/(iphone|ipad|ipod|android)/gi).test(navigator.appVersion),
	PopupOptions: {height:476,width:735,location:0,menubar:0,status:0,toolbar:0},
	Init: function() {
		if (!$.cookiesEnabled()) {
			alert($.resource("error_cookies"));
		}
		Util_GetInitData();
		Util.MapArea = $("#map");
		Map = new Mapper(Util.MapArea.attr("id"));
		Util.NavControl();
		Util.LayerServices = {
			Boundaries: $.resource("boundariesService"),
			Places: $.resource("placesService")
		};
		Util.LayerSelect();
		Util.ProcessMask = $("<div class='process'><img src='" + $.resolveUrl("images/process-1.gif") + "' alt='" + $.resource("process") + "'/></div>");
		Util.ProcessBalloon = $("<div class='balloon process'><img src='" + $.resolveUrl("images/process-2.gif") + "' alt='" + $.resource("process") + "'/></div>");
		Util.ProcessCount = $.format($.resource("f_count"), $.format("<img src='{0}' alt='{1}'/>", $.resolveUrl("images/process-3.gif"), $.resource("process")));
		Util.ProcessListItem = $("<li class='process'><img src='" + $.resolveUrl("images/process-1.gif") + "' alt='" + $.resource("process") + "'/></li>");
		Util.Preloader = $("<div/>").css({height:0,overflow:"hidden"}).appendTo(document.body);
		if (Util.IsTouch) {
			$.getScript($.resolveUrl("scripts/touch.js"));
		}
	},
	GetInitDataCompleted: function(request, data) {
		if (data) {
			Util.Data = data;
			if ($.isEmptyObject(Util.Data.settings)) { //default settings
				var us = {};
				if (Util.Data.boundaries) {
					us["Boundaries.selected"] = true;
				}
				if (!$.isEmptyObject(us)) {
					Util.Settings(us);
				}
			}
			if (Util.Data.boundaries) {
				Util.Data.boundaries.unshift("selected");
			}
			for (var i = 0; i < Util.Data.layers.length; i++) {
				Util.Data.layers[i].name = Util.Data.layers[i].ldsmapsLayer;
				if (/^other.+/.test(Util.Data.layers[i].ldsmapsLayer)) {
					Util.Data.layers[i].name += ":" + Util.Data.layers[i].ldsmapsSubLayer;
				}
			}
			$.extend(Util.Data, {balloons:{},panels:{},modals:{},markers:{}});
			for (var i = 0; i < Util.InitActions.length; i++) {
				$.thread(Util.InitActions[i]);
			}
		} else {
			if ($.cookie("loadCount") === "1" || !$.cookiesEnabled()) {
				alert($.resource("error_load"));
				$.cookie("loadCount", null);
				return;
			} else {
				$.cookie("loadCount", "1");
				location.reload(true);
			}
		}
	},
	GetEntityDetailsCompleted: function(request, data) {
		Util.DelayUntilMap(function() {
			Util.ShowContent(data);
		});
	},
	GetMapPointDetailsCompleted: function(request, data) {
		if (data.length) {
			if (data.length === 1 && $.defined(data[0].description)) {
				Util.ShowContent(data[0]);
				Util.LayerSelect(data[0].id, data[0].gisLayerName ? Util.LayerByGis(data[0].gisLayerName).name : null);
			} else {
				Util.MarkerLoad($.extend(data, request.options.parameters));
			}
		}
	},
	ShowContent: function(data) {
		if (Util.Hold) {
			Map.Bind("showcontent", Util.ShowContentCompleted);
		} else {
			Util.ZoomToUnit(data);
		}
		if (Mapper.GetPoint(data)) {
			Util.MarkerLoad(data);
		} else {
			Util.BalloonLoad(data, Util.UpdateContent, Map);
		}
	},
	ShowContentCompleted: function() {
		Map.Unbind("showcontent", Util.ShowContentCompleted);
		if (Util.Hold) {
			Map.Point(Util.Hold);
			Util.Hold = null;
		}
	},
	HideContentHandler: function(obj) {
		Map.HideContent();
		if (obj) {
			Main.UpdateStateHandler(obj);
		}
		return false;
	},
	UpdateContent: function(data, content, obj) {
		obj.ShowContent(content);
	},
	MarkerClearHandler: function(e) {
		Map.ClearMark(e.Marker.ID());
	},
	MarkersAdd: function(marker, data) {
		var layer = Util.LayerMarkerByGis(data.gisLayerName).name;
		if (!$.defined(Util.Data.markers[layer])) {
			Util.Data.markers[layer] = [];
		}
		Util.Data.markers[layer].push(marker.ID());
	},
	MarkerClear: function(ID) {
		for (var i in Util.Data.markers) {
			for (var j = 0; j < Util.Data.markers[i].length; j++) {
				if (Util.Data.markers[i][j] == ID) {
					Map.ClearMark(ID);
					Util.Data.markers[i].splice(j, 1);
					return;
				}
			}
		}
		Map.ClearMark(ID);
	},
	MarkersClear: function(layer) {
		if (Map.Loaded()) {
			if ($.isString(layer)) {
				var m = Util.Data.markers[layer];
				if (m) {
					for (var i = 0; i < m.length; i++) {
						Map.ClearMark(m[i]);
					}
					Util.Data.markers[layer] = [];
				}
			} else {
				if (layer) {
					for (var i in Util.Data.markers) {
						for (var j = 0; j < Util.Data.markers[i].length; j++) {
							Map.ClearMark(Util.Data.markers[i][j]);
						}
					}
				} else {
					Map.ClearMark();
				}
				Util.Data.markers = {};
				Map.HideContent();
			}
		}
	},
	MarkerLoad: function(data) {
		var cm = null;
		var layer = data.gisLayerName ? Util.LayerMarkerByGis(data.gisLayerName).name : null;
		if (layer) {
			var md = Util.Data.markers[layer];
			if (md) {
				for (var i = 0; i < md.length; i++) {
					var m = Map.Markers(md[i]);
					if (m && Mapper.PointsEqual(m.Point(), data)) {
						cm = m;
						break;
					}
				}
			}
		}
		if (cm) {
			Util.BalloonLoad(data, Util.UpdateContent, cm);
		} else {
			Util.MarkerIconGenerate(data, Util.MarkerContentLoad);
		}
	},
	MarkerContentLoad: function(data, icon) {
		Util.BalloonLoad(data, Util.MarkerLoadComplete, icon);
	},
	MarkerLoadComplete: function(data, content, icon) {
		var m = new MapMarker(Mapper.GetPoint(data), content);
		m.Icon(icon);
		var t = Util.DataType(data);
		switch (t) {
			case Util.DataTypes.Place:
				Util.MarkersAdd(m, data);
				break;
			case Util.DataTypes.Marker:
				m.Draggable(true);
				m.Bind("dragstart", Main.MarkPreDrag);
				Main.MarkerID = m.ID();
				break;
			case Util.DataTypes.Point:
				m.Bind("hidecontent", Util.MarkerClearHandler);
				break;
		}
		Map.Mark(m);
		m.ShowContent();
	},
	MarkerIconGenerate: function(data, callback) {
		if (!Util.MarkerIconGenerate.init) {
			$.extend(Util.MarkerIconGenerate, {
				init: true,
				icons: {},
				load: function() {
					var src = $(this).attr("src");
					var s = new MapPixel(this.width, this.height);
					var d = Util.MarkerIconGenerate.icons[src][0].data;
					var x, ny;
					var t = Util.DataType(d);
					switch (t) {
						case Util.DataTypes.Household:
						case Util.DataTypes.HouseholdGroup:
							x = s.Y === 28 ? 13 : 7;
							switch (s.Y) {
								case 22:
								case 27:
									ny = 13;
									break;
								case 28:
									ny = 14;
									break;
								default:
									ny = 9;
									break;
							}
							break;
						case Util.DataTypes.None:
							x = d.verified ? 0 : 5;
							ny = Math.floor(s.Y / 2);
							break;
						case Util.DataTypes.Place:
							var ld = Util.LayerMarkerByGis(d.gisLayerName);
							x = ld.iconOffsetX;
							ny = s.Y - ld.iconOffsetY;
							break;
						case Util.DataTypes.Marker:
							x = Math.floor(s.X / 2);
							ny = 0;
							break;
					}
					var icon = new MapMarkerIcon(src, s, $.empty(x) ? null : new MapPixel(x, s.Y - ny));
					var queue = Util.MarkerIconGenerate.icons[src];
					Util.MarkerIconGenerate.icons[src] = icon;
					for (var i = 0; i < queue.length; i++) {
						queue[i].callback(queue[i].data, $.clone(icon));
					}
					$(this).remove();
				}
			});
		}
		var src = Util.MarkerIconGenerateUrl(data);
		var icon = Util.MarkerIconGenerate.icons[src];
		if (icon) {
			if ($.isObject(icon)) {
				$.thread(function() {
					callback(data, $.clone(Util.MarkerIconGenerate.icons[src]));
				});
			} else {
				Util.MarkerIconGenerate.icons[src].push({data:data,callback:callback});
			}
		} else {
			Util.MarkerIconGenerate.icons[src] = [
				{data:data,callback:callback}
			];
			$("<img/>").load(Util.MarkerIconGenerate.load).appendTo(Util.Preloader).attr("src", src);
		}
	},
	MarkerIconGenerateUrl: function(data) {
		var url = "images/icon.png?";
		var params = [];
		var t = Util.DataType(data);
		switch (t) {
			case Util.DataTypes.Point:
				url = "images/markers/crosshair.png";
				break;
			case Util.DataTypes.Marker:
				url = "images/markers/marker.png";
				break;
			case Util.DataTypes.Household:
				if (!data.nomarker) {
					if (Mapper.GetPoint(data)) {
						params.push("x=h");
					} else {
						params.push("x=u");
					}
				}
				if (data.verification >= 0 && data.verification <= 2) {
					params.push("v=" + data.verification);
				}
				if (!data.nohm && data.id == Util.Data.mid) {
					params.push("hm");
				}
				for (var i in data.categories) {
					var c = i.toLowerCase();
					if (!data["no-" + c]) {
						params.push(c);
					}
				}
				if (data.h) {
					params.push("h");
				}
				if (data["number-on"] || data["nametag-on"]) {
					var tag = "";
					if (data["number-on"]) {
						tag += data.order;
					}
					if (data["nametag-on"]) {
						tag += (tag ? ". " : "") + data.familyName;
					}
					params.push("t=" + tag);
				}
				break;
			case Util.DataTypes.HouseholdGroup:
				if (!data.nomarker) {
					params.push("x=hg");
				}
				if (data.verification >= 0 && data.verification <= 2) {
					params.push("v=" + data.verification);
				}
				if (!data.nohm && data.hm) {
					params.push("hm");
				}
				for (var i in data.categories) {
					var c = i.toLowerCase();
					if (!data["no-" + c]) {
						params.push(c + "=" + data.categories[i]);
					}
				}
				if (data.h) {
					params.push("h");
				}
				if (data["number-on"] || data["nametag-on"]) {
					var tag = "";
					if (data["number-on"]) {
						tag += data.order;
					}
					if (data["nametag-on"]) {
						tag += (tag ? ". " : "") + data.name;
					}
					params.push("t=" + tag);
				}
				break;
			case Util.DataTypes.Place:
				url = $.format("images/markers/{0}.png", Util.LayerMarkerByGis(data.gisLayerName).name);
				break;
		}
		return $.resolveUrl(url + params.join("&"));
	},
	BalloonLoad: function(data, callback, state) {
		var bn;
		var t = Util.DataType(data);
		switch (t) {
			case Util.DataTypes.Place:
				var ln = data.gisLayerName ? Util.LayerByGis(data.gisLayerName).ldsmapsLayer : null;
				switch (ln) {
					case "meetinghouses":
						bn = "meetinghouse";
						break;
					case "wards":
					case "otherwards":
						bn = "ward";
						break;
					default:
						bn = "facility";
						break;
				}
				break;
			case Util.DataTypes.Marker:
				bn = "marker";
				break;
			case Util.DataTypes.Point:
				bn = "facilities";
				break;
		}
		Util.BalloonByName(bn, Util.BalloonPopulator, {data:data,callback:callback,state:state});
	},
	BalloonPopulator: function(name, balloon, state) {
		var data = state.data;
		var pid, uid, id = data.id, cu = true;
		var p = Mapper.GetPoint(data);
		var layer = data.gisLayerName ? Util.LayerByGis(data.gisLayerName).name : null;
		switch (name) {
			case "meetinghouse":
				pid = data.id;
				Util.LoadAddress(balloon.find(".address"), data);
				var t = balloon.find("tbody");
				if (data.congregations && data.congregations.length) {
					var c = t.closest(".resources").find("h5 a");
					c.text($.format(c.text(), data.congregations.length));
					for (var i = 0; i < data.congregations.length; i++) {
						var d = data.congregations[i];
						var ld = Util.LayerByGis(d.layerName);
						var a = $(Util.BaseAnchor).attr("href", $.format("#id={0},{1}", d.id, ld ? ld.name : "")).text(d.name);
						var n = $("<td/>").append(a);
						var h = $("<td/>").text(d.STime);
						$("<tr/>").append(n).append(h).appendTo(t);
					}
				} else {
					t.closest(".resources").remove();
				}
				var ul = balloon.find(".other ul");
				if (data.otherResources && data.otherResources.length) {
					var c = ul.closest(".other").find("h5 a");
					c.text($.format(c.text(), data.otherResources.length));
					for (var i = 0; i < data.otherResources.length; i++) {
						var d = data.otherResources[i];
						var li = $("<li/>").appendTo(ul);
						if (d.id) {
							var ld = Util.LayerByGis(d.gisLayerName);
							var a = $(Util.BaseAnchor).attr("href", $.format("#id={0},{1}", d.id, ld ? ld.name : "")).text(d.name);
							li.append(Util.LayerBGByGis(a, d.gisLayerName));
						} else {
							li.text(d.name);
						}
					}
				} else {
					ul.closest(".other").remove();
				}
				break;
			case "ward":
				var t = balloon.find("table");
				if (data.contactName || data.contactOfficePhone || data.contactHomePhone) {
					if (data.contactHomePhone) {
						t.find("tr:eq(2) td p:eq(2) a").text(data.contactHomePhone).attr("href", "tel:" + data.contactHomePhone.replace(/[^a-z0-9#*]/gi, ""));
					} else {
						t.find("tr:eq(2) td p:eq(2)").remove();
					}
					if (data.contactOfficePhone) {
						t.find("tr:eq(2) td p:eq(1) a").text(data.contactOfficePhone).attr("href", "tel:" + data.contactOfficePhone.replace(/[^a-z0-9#*]/gi, ""));
					} else {
						t.find("tr:eq(2) td p:eq(1)").remove();
					}
					if (data.contactName) {
						t.find("tr:eq(2) td p:eq(0)").text(data.contactName);
					} else {
						t.find("tr:eq(2) td p:eq(0)").remove();
					}
				} else {
					t.find("tr.leader").remove();
				}
				if (data.worshipServiceTime) {
					t.find("tr.time:eq(1) td").text(data.worshipServiceTime);
				} else {
					t.find("tr.time:eq(1)").remove();
				}
				if (data.operatingTime) {
					t.find("tr.time:eq(0) td").text(data.operatingTime);
				} else {
					t.find("tr.time:eq(0)").remove();
				}
				break;
			case "facility":
				var t = balloon.find("table");
				var d = [];
				if (data.contactName) {
					d.push("<p>" + data.contactName + "</p>");
				}
				if (data.contactOfficePhone) {
					d.push("<p>" + data.contactOfficePhone + "</p>");
				}
				if (data.contactEmail) {
					d.push($.format("<p><a href='mailto:{0}'>{1}</a></p>", data.contactEmail, $.emailBreak(data.contactEmail)));
				}
				if (d.length) {
					t.find(".leader td").html(d.join());
				} else {
					t.find(".leader").remove();
				}
				if (data.operatingTime) {
					t.find(".time td").text(data.operatingTime);
				} else {
					t.find(".time").remove();
				}
				if (data.notes) {
					balloon.find(".notes > div").append(data.notes.replace(/\n/g, "<br/>"));
				} else {
					balloon.find(".notes").remove();
				}
				break;
			case "facilities":
				var ul = balloon.find(".resources ul");
				var z = ul.find("li").remove();
				for (var i = 0; i < data.length; i++) {
					var d = data[i];
					if ($.isObject(d)) {
						var li = null;
						var ld = Util.LayerByGis(d.gisLayerName);
						if (d.id) {
							var a = $(Util.BaseAnchor).attr("href", $.format("#id={0},{1}", d.id, ld ? ld.name : "")).text(d.name);
							li = $("<li/>").append(Util.LayerBGByGis(a, d.gisLayerName));
						} else {
							li = z.clone();
							var a = Util.LayerBGByGis(li.find("a"), d.gisLayerName);
							var p = Mapper.GetPoint(data);
							//TODO: find out why it must / 2
							var sr = Math.sqrt(Math.pow(ld.iconSizeX * data.mpp, 2) + Math.pow(ld.iconSizeY * data.mpp, 2)) / 2;
							var tl = Mapper.CalculatePoint(p, sr, 315);
							var br = Mapper.CalculatePoint(p, sr, 135);
							a.attr("href", $.format(a.attr("href"), tl.Latitude, tl.Longitude, br.Latitude, br.Longitude)).text(d.name);
						}
						li.appendTo(ul);
					}
				}
				cu = false;
				break;
			case "marker":
				balloon.find(".latitude:first").text(Mapper.PrintCoordinate(p.Latitude));
				balloon.find(".longitude:first").text(Mapper.PrintCoordinate(p.Longitude));
				if (Main.GetCurrentTask() !== "directions") {
					balloon.find("li.directions").remove();
				}
				var z = Map.Zoom();
				balloon.find("a").each(function() {
					$(this).attr("href", $.format($(this).attr("href"), p.Latitude, p.Longitude, z));
				});
				cu = false;
				break;
		}
		if (name === "ward" || name === "facility") {
			uid = data.id;
			pid = data.propNum;
			balloon.find("h4").text(data.name);
			var ba = balloon.find(".address a");
			Util.LoadAddress(ba, data);
			if (pid && data.meetinghouse) {
				var mh = balloon.find(".meetinghouse a");
				mh.attr("href", $.format(mh.attr("href"), pid));
				ba.attr("href", $.format(ba.attr("href"), pid));
			} else {
				balloon.find(".meetinghouse").remove();
				balloon.find(".address").text(ba.text());
			}
			if (data.moreInfoURL) {
				var a = balloon.find(".web a").attr("href", data.moreInfoURL);
				if (Main.IsClient) {
					a.attr("target", "_top");
				}
			} else {
				balloon.find(".details").remove();
			}
			if (!p || !id) {
				balloon.find(".utilities").remove();
			}
			var ul = balloon.find(".other ul");
			if (data.associatedUnits && data.associatedUnits.length) {
				for (var i = 0; i < data.associatedUnits.length; i++) {
					var d = data.associatedUnits[i];
					var li = $("<li/>").appendTo(ul);
					if (d.id) {
						var ld = Util.LayerByGis(d.gisLayerName);
						var a = $(Util.BaseAnchor).attr("href", $.format("#id={0},{1}", d.id, ld ? ld.name : "")).text(d.name);
						li.append(Util.LayerBGByGis(a, d.gisLayerName));
					} else {
						li.text(d.name);
					}
				}
				var c = ul.closest(".other").find("h5 a");
				c.text($.format(c.text(), ul.children().length));
			} else if (!ul.children().length) {
				ul.closest(".other").remove();
			}
		}
		if (cu) {
			if (p && id) {
				var a = balloon.find(".directions a");
				if (a.length) {
					a.attr("href", $.format(a.attr("href"), id, layer));
				}
			} else {
				balloon.find(".directions").remove();
			}
			if (p && pid) {
				var a = balloon.find(".move a");
				if (a.length) {
					a.attr("href", $.format(a.attr("href"), pid));
				}
			} else {
				balloon.find(".move").remove();
			}
			if (pid) {
				var pl = Util.LayerMarkerByGis(data.gisLayerName).ldsmapsLayer;
				var a = balloon.find(".feedback a");
				if (a.length) {
					a.attr("href", $.format(a.attr("href"), pid, pl, uid ? "&uid=" + uid : ""));
				}
			} else {
				balloon.find(".feedback").remove();
			}
			if (id) {
				balloon.attr("data-id", id).attr("data-layer", layer);
			}
			if (balloon.find(".actions > ul").length && !balloon.find(".actions > ul > *").length) {
				balloon.find(".actions").remove();
			}
		}
		state.callback(data, $.html(balloon), state.state);
	},
	BalloonVisibleID: function() {
		var bid = null;
		var b = $(".balloon[data-id]:first");
		if (b.length) {
			bid = $.format("{0},{1}", b.attr("data-id"), b.attr("data-layer"));
		}
		return bid;
	},
	ToggleSection: function(obj) {
		$(obj).closest("div").toggleClass($.classes.selected);
		return false;
	},
	BalloonByName: function(name, callback, state) {
		if (Util.Data.balloons[name]) {
			$.thread(function() {
				callback(name, Util.Data.balloons[name].clone(), state);
			});
		} else {
			var np = name.split("?");
			$.get($.resolveUrl($.format("content/balloons/{0}.jsf{1}", np[0], np[1] ? "?" + np[1] : "")), function(data) {
				Util.Data.balloons[name] = $(data);
				Util.BalloonByName(name, callback, state);
			});
		}
	},
	PanelByName: function(name, callback, state) {
		if (Util.Data.panels[name]) {
			$.thread(function() {
				callback(name, Util.Data.panels[name].clone(), state);
			});
		} else {
			var np = name.split("?");
			$.get($.resolveUrl($.format("content/panels/{0}.jsf{1}", np[0], np[1] ? "?" + np[1] : "")), function(data) {
				var p = $(data);
				var a = $("<a href='#' onclick='return Util.PanelClose(this)'/>").addClass("close-x")
					.text($.resource("close")).attr("title", $.resource("close"));
				p.append(a);
				Util.Data.panels[name] = p;
				Util.PanelByName(name, callback, state);
			});
		}
	},
	ModalByName: function(name, callback, state) {
		if (Util.Data.modals[name]) {
			$.thread(function() {
				callback(name, Util.Data.modals[name].clone(), state);
			});
		} else {
			var np = name.split("?");
			$.get($.resolveUrl($.format("content/modals/{0}.jsf{1}", np[0], np[1] ? "?" + np[1] : "")), function(data) {
				Util.Data.modals[name] = $(data);
				Util.ModalByName(name, callback, state);
			});
		}
	},
	DataType: function(data) {
		var type = Util.DataTypes.None;
		if ($.defined(data.groupId)) {
			type = Util.DataTypes.Household;
		} else if ($.defined(data.households)) {
			type = Util.DataTypes.HouseholdGroup;
		} else if ($.defined(data.gisLayerName)) {
			type = Util.DataTypes.Place;
		} else if ($.defined(data.Latitude)) {
			type = Util.DataTypes.Marker;
		} else if ($.defined(data.latitude)) {
			type = Util.DataTypes.Point;
		}
		return type;
	},
	LoadFormEvents: function(form) {
		var t = $(form).find("input:text,textarea").inputHint();
		t.filter("[id$=country]").autocomplete($.resolveUrl("services/countries.jsf"), {minChars:2});
		t.unbind(".form-events").bind(($.browser.opera ? "keypress" : "keydown") + ".form-events", Util.InputEnter).bind("blur.form-events keyup.form-events", Util.EnableForm).keyup();
		$(form).find("a.reset").unbind("click").click(Util.ResetForm);
	},
	ResetForm: function() {
		$(this).closest("form").find("input:text").val("").blur();
		return false;
	},
	InputEnter: function(e) {
		if (e.which === 13 && !e.isDefaultPrevented()) {
			var f = $(this).blur().closest("form");
			f.find("input.execute").focus().click();
			return false;
		}
	},
	EnableForm: function() {
		var f = $(this).closest("form");
		var t = $.empty($.trim(f.find("input:text:not(.text-hint),textarea:not(.text-hint)").val()));
		f.find("input.execute").attr("disabled", t ? "disabled" : "");
	},
	PreSubmit: function(obj) {
		$(obj).add($(obj).closest("form")).find("input:text.text-hint,textarea.text-hint").val("");
		//$.thread(function(){t.blur();}, 500);
	},
	PanelClose: function(obj) {
		$(obj).closest(".panel").remove();
		return false;
	},
	SelectSection: function() {
		$(this).closest(".task-section").toggleClass($.classes.selected);
		return false;
	},
	LoadAddress: function(obj, data, alt) {
		if (obj.length) {
			obj.empty();
			var h = null;
			var t = "";
			for (var i = 0; i < Util.AddressProps.length; i++) {
				var p = Util.AddressProps[i];
				if (data[p]) {
					if (h || !alt) {
						if (t) {
							t += ",";
						}
						t += " " + data[p];
					} else {
						h = $("<span/>").text(data[p]);
					}
				}
			}
			obj.text(t);
			if (h) {
				obj.prepend(h);
			}
		}
	},
	ConvertAddress: function(text, alt) {
		var t = null;
		if (!$.empty(text)) {
			if (text.indexOf("</span>") === -1 && text.indexOf("</a>") === -1) {
				t = (alt ? "<span>" : "") + text;
				var p = t.indexOf("\n");
				if (p === -1 && alt) {
					t += "</span>";
				} else {
					if (alt) {
						t = t.replace("\n", "</span>").replace(/\n/g, "<br/>");
					} else {
						t = t.replace(/\s*\n+\s*/g, ", ");
					}
				}
			} else {
				t = $.replaceTags(text.replace("</span>", "\n").replace("</a>", "\n").replace(/<br\/?>/ig, "\n"));
			}
		}
		return t;
	},
	ConvertAddressProp: function(data) {
		if ($.defined(data.street)) {
			data.street1 = data.street;
			delete data.street;
		} else if ($.defined(data.street1) || $.defined(data.street2)) {
			var s = null;
			if ($.defined(data.street1)) {
				s = data.street1;
			}
			if ($.defined(data.street2)) {
				if (!$.empty(s) && !$.empty(data.street2)) {
					s += ", " + data.street2;
				} else if ($.empty(s)) {
					s = data.street2;
				}
			}
			data.street = s;
			delete data.street1;
			delete data.street2;
		}
		return data;
	},
	LoadProcessSection: function(obj) {
		$(obj).html($.html(Util.ProcessMask.show()));
	},
	GenerateProcessBalloon: function(attr) {
		var b = Util.ProcessBalloon.clone();
		if (attr) {
			b.attr(attr);
		}
		return $.html(b);
	},
	LoadMapMask: function(callback) {
		if (!Util.MapMask && callback) {
			Util.MapMask = $("<div/>").addClass("map-mask");
			Util.PanelByName("keys", function(name, panel) {
				Util.MapMask.append(panel);
			});
		}
		if (callback) {
			Util.MapMask.data("cancel", callback).appendTo(document.body);
			$("a:first").focus().blur();
		} else if (Util.MapMask) {
			Util.MapMask.removeData("cancel").remove();
		}
	},
	LoadCursor: function(cursor) {
		$("#ldsmaps-overlay").remove();
		if (cursor) {
			var s = $.format("<style id='ldsmaps-overlay' type='text/css'>* {cursor: {0} !important;}</style>", cursor);
			$("head").append(s);
		}
	},
	LoadToolTip: function(tooltip) {
		if (!Util.LoadToolTip.init) {
			$.extend(Util.LoadToolTip, {
				init: true,
				panel: null,
				text: null,
				move: function(e) {
					if (!Util.LoadToolTip.panel) {
						Util.LoadToolTip.panel = $("<div/>").addClass("tooltip").text(Util.LoadToolTip.text).appendTo(document.body);
					}
					Util.LoadToolTip.panel.css({left:e.pageX + 10, top:e.pageY});
				}
			});
		}
		if (tooltip) {
			if (Util.LoadToolTip.panel) {
				Util.LoadToolTip();
			}
			Util.LoadToolTip.text = tooltip;
			$(document).bind("mousemove.tooltip", Util.LoadToolTip.move);
		} else {
			Util.LoadToolTip.text = null;
			$(document).unbind(".tooltip");
			if (Util.LoadToolTip.panel) {
				Util.LoadToolTip.panel.remove();
				Util.LoadToolTip.panel = null;
			}
		}
	},
	PlaceFacility: function(layer, fullname, email) {
		$.thread(function() {
			var data = {gisLayerName:Util.LayerGisByName(layer), fullname:fullname, email:email};
			Util.MoveSetup(data, $.resource("facility_move"), "facility-move", Util.MoveBalloonLoad);
			Util.MoveStart();
		});
	},
	MoveFacilityClick: function(obj) {
		var c = $(obj).closest(".balloon");
		var data = {id:$.segment($(obj).attr("href"), "=", 1), gisLayerName:Util.LayerGisByName(c.attr("data-layer")), name:c.find(".balloon-title h4").text(), description:Util.ConvertAddress(c.find(".balloon-description .address").html())};
		Util.MoveSetup(data, $.resource("facility_move"), "facility-move", Util.MoveBalloonLoad);
		Util.MoveClick();
		return false;
	},
	MoveFacilityConfirm: function(obj) {
		var d = $.clone(Util.MovePackage.data);
		d.mid = Util.MoveData.mid;
		d.tp = Map.MarkerPoint(Util.MoveData.tid);
		var f = $(obj).closest(".balloon").find("form");
		if (f.length) { // TODO: check for required input
			d.name = f.find("input:text").val();
			d.description = f.find("textarea").val();
		}
		Main_MoveFacility(d.mid ? "placewrong" : "placemissing", d.id || 0, Util.LayerByGis(d.gisLayerName).name, $.nz(d.name), $.nz(d.description), d.tp.Latitude, d.tp.Longitude, $.nz(d.fullname), $.nz(d.email));
		Util.MarkerIconGenerate(d, Util.LoadNewFacility);
		Util.MoveCancel();
	},
	LoadNewFacility: function(data, icon) {
		Util.BalloonByName("facility-move-confirm", Util.MoveBalloonLoad, {data:data,icon:icon,callback:Util.LoadNewFacilityMarker});
	},
	LoadNewFacilityMarker: function(content, state) {
		var d = state.data;
		var m = new MapMarker(d.tp, content);
		state.icon.Opacity = 0.4;
		m.Icon(state.icon);
		Map.Mark(m);
		if (d.mid) {
			var x = [m.ID(), d.mid];
			var ln1 = new MapLine(x, new MapColor(255, 255, 255, 0.5), 4);
			var ln2 = new MapLine(x, new MapColor(52, 132, 253), 2);
			Map.Line(ln1).Line(ln2);
		}
		m.ShowContent();
	},
	MoveBalloonLoad: function(name, c, state) {
		var d = state.data;
		if (!state.point || d.name || d.description) {
			c.find("form").remove();
			if (d.name) {
				c.find(".balloon-description strong").text(d.name);
			} else {
				c.find(".balloon-description strong").remove();
			}
			if (d.description) {
				c.find(".balloon-description .address").html(Util.ConvertAddress(d.description));
			} else {
				c.find(".balloon-description .address").remove();
			}
		} else {
			c.find(".balloon-description").remove();
			if (Util.LayerByGis(d.gisLayerName).name === "meetinghouses") {
				c.find("form:has(:text)").remove();
			} else {
				c.find("form:not(:has(:text))").remove();
			}
		}
		state.callback($.html(c), state);
	},
	MoveSetup: function(data, tooltip, contentName, contentFn) {
		Util.MovePackage = {data:data, tooltip:tooltip};
		if (contentName) {
			Util.MovePackage.content = {name:contentName, fn:contentFn};
		}
	},
	MoveClick: function() {
		var m = Map.MarkersVisibleContent();
		Util.MoveStart(m ? m.ID() : null);
	},
	MoveDrag: function(e) {
		$(Map.MarkerPrimitive(e.Marker.ID())).simulate("mouseup");
		Util.MoveStart(e.Marker.ID(), true);
	},
	MoveTarget: function(e) {
		$(Map.MarkerPrimitive(e.Marker.ID())).simulate("mouseup");
		Util.MoveStart(Util.MoveData.mid, true);
	},
	MoveStart: function(mid, dragged) {
		Util.MoveCancel();
		Util.LoadMapMask(Util.MoveFinish);
		Util.LoadCursor(Main.Cursors.Crosshair);
		Map.HideContent();
		if (mid) {
			var x = [Map.MarkerPoint(mid), mid];
			var ln1 = new MapLine(x, new MapColor(255, 255, 255, 0.5), 4);
			var ln2 = new MapLine(x, new MapColor(52, 132, 253), 2);
			Util.MoveData = {mid:mid, tid:null, ln1id:ln1.ID(), ln2id:ln2.ID(), mp:x[0], drag:false, tooltip:null};
			Map.Line(ln1).Line(ln2);
			$(document).bind("mousemove.moving keydown.moving", Util.MoveRefreshLine);
		} else {
			Util.MoveData = {tid:null, drag:false, tooltip:null};
		}
		if (dragged) {
			$(document).bind("mouseup.moving", Util.MoveFinish);
		} else {
			$.thread(function() {
				$(document).bind("click.moving", Util.MoveFinish);
			});
		}
		$(document).bind("mousemove.moving", Util.MoveRefresh);
	},
	MoveRefresh: function(e) {
		Util.MoveData.drag = false;
		if (Util.MovePackage.tooltip) {
			if (!Util.MoveData.tooltip) {
				Util.MoveData.tooltip = $("<div/>").addClass("tooltip").text(Util.MovePackage.tooltip).appendTo(document.body);
			}
			Util.MoveData.tooltip.css({left:e.pageX + 11, top:e.pageY - 30});
		}

		var p = Util.WindowMapPanPixel(e);
		if (p) {
			Util.MoveData.drag = true;
			Map.Pan(p);
			$.thread(function() {
				if (Util.MoveData && Util.MoveData.drag) {
					Util.MoveRefresh(e);
				}
			}, 50);
		}
	},
	MoveRefreshLine: function(e) {
		var mp = Map.PointToPixel(Util.MoveData.mp);
		var tp = Util.WindowToMapPixel($.trackMouse);
		if (tp) {	//TODO: fix for outside map
			var td = Math.sqrt(Math.pow(tp.X - mp.X, 2) + Math.pow(tp.Y - mp.Y, 2));
			if (td > 8) {
				var k = 8 / td;
				mp = new MapPixel(tp.X + k * (mp.X - tp.X), tp.Y + k * (mp.Y - tp.Y));
			}
			var p = Map.PixelToPoint(mp);
			Map.LinePoint(Util.MoveData.ln1id, 0, p);
			Map.LinePoint(Util.MoveData.ln2id, 0, p);
		}
	},
	MoveFinish: function(e) {
		Util.MoveData.drag = false;
		var p = Util.WindowToMapPoint(e);
		if (p) {
			if (Util.MovePackage.content) {
				Util.BalloonByName(Util.MovePackage.content.name, Util.MovePackage.content.fn, {data:Util.MovePackage.data,point:p,callback:Util.MoveFinishMarker});
			} else {
				Util.MoveFinishMarker(p);
			}
		} else {
			Util.MoveCancel();
		}

		// Cleanup
		$(document).unbind(".moving");
		Util.LoadMapMask();
		Util.LoadCursor(null);
		if (Util.MoveData && Util.MoveData.tooltip) {
			Util.MoveData.tooltip.remove();
			Util.MoveData.tooltip = null;
		}
	},
	MoveFinishMarker: function(content, state) {
		var m = new MapMarker(state.point, content);
		m.Icon(new MapMarkerIcon($.resolveUrl("images/markers/crosshair.png"), new MapPixel(21, 21)));
		if (Util.MovePackage.tooltip) {
			m.Tooltip(Util.MovePackage.tooltip);
		}
		m.Draggable(true).Bind("dragstart", Util.MoveTarget);
		Map.Mark(m);
		if (content) {
			m.ShowContent();
		}
		Util.MoveData.tid = m.ID();

		if (Util.MoveData.mid) {
			Map.LinePoint(Util.MoveData.ln1id, 0, Util.MoveData.tid);
			Map.LinePoint(Util.MoveData.ln2id, 0, Util.MoveData.tid);
		}
	},
	MoveCancel: function() {
		if (Util.MoveData) {
			if (Util.MoveData.ln1id) {
				Map.ClearLine(Util.MoveData.ln1id);
			}
			if (Util.MoveData.ln2id) {
				Map.ClearLine(Util.MoveData.ln2id);
			}
			if (Util.MoveData.tid) {
				Map.ClearMark(Util.MoveData.tid);
			}
			if (Util.MoveData.tooltip) {
				Util.MoveData.tooltip.remove();
			}
			Util.MoveData = null;
		}
		return false;
	},
	KeyControls: function(e) {
		var o = e.originalEvent;
		if (!$(e.originalTarget).is("input") && !o.metaKey && !o.ctrlKey) {
			var c = e.which || e.charCode || e.keyCode;
			if (o.altKey) {
				if (o.shiftKey) {
					switch (c) {
						case 37:	//left
							Map.Pan(new MapPixel(-40, 0));
							return false;
						case 38:	//up
							Map.Pan(new MapPixel(0, -40));
							return false;
						case 39:	//right
							Map.Pan(new MapPixel(40, 0));
							return false;
						case 40:	//down
							Map.Pan(new MapPixel(0, 40));
							return false;
					}
				} else {
					var z = 0;
					switch (c) {
						case 49:	//1
						case 50:	//2
						case 51:	//3
						case 52:	//4
							Map.Type(c - 48);
							return false;
						case 45:	//-
						case 109:	//Mozilla-
						case 189:	//IE-
							z--;
							break;
						case 61:	//+
						case 107:	//Mozilla+
						case 187:	//IE+
							z++;
							break;
					}
					if (z !== 0) {
						var p = Util.WindowToMapPoint($.trackMouse);
						if (p) {
							Map.Position(p, Map.Zoom() + z);
						} else {
							Map.Zoom(Map.Zoom() + z);
						}
						return false;
					}
				}
			} else if (!o.shiftKey && c === 27) {//Esc
				var fn = Util.MapMask.data("cancel");
				if (fn) {
					fn();
					return false;
				}
			}
		}
	},
	QueryParams: function(keys) {
		var p = {};
		for (var i = 0; i < keys.length; i++) {
			var qp = keys[i];
			var val = $.query.get(qp);
			if (!$.empty(val) && val !== false) {
				p[qp] = (val === true ? "" : val);
			}
		}
		return p;
	},
	IsValidExtentResponse: function(request, extent) {
		var p = request.options.parameters;
		var x = extent || Map.Extent();
		return (p.top == x[0].Latitude && p.left == x[0].Longitude && p.bottom == x[1].Latitude && p.right == x[1].Longitude);
	},
	Popup: function(obj) {
		$.newWindow($.nz(obj.href, this.href), Util.PopupOptions);
		return false;
	},
	Download: function(src) {
		$("<iframe/>").appendTo(Util.Preloader).attr("src", src);
	},
	ZoomToUnit: function(data, alt) {
		if (data.boundaryExtent) {
			//TODO: compensate for balloon height?
			f = Mapper.GetExtent(data.boundaryExtent);
			var p = Mapper.GetPoint(data);
			if (p) {
				f.push(p);
			}
			Map.Frame(f);
		} else if (alt) {
			Map.Zoom(alt);
		}
	},
	CalculatePrecision: function(data) {
		var p = 0;
		if (data.street1 || data.street2 || data.a) {
			p = 5;
		}
		else if (data.zip || data.p) {
			p = 4;
		}
		else if (data.city || data.c) {
			p = 3;
		}
		else if (data.state || data.s) {
			p = 2;
		}
		else if (data.country || data.n) {
			p = 1;
		}
		return p;
	},
	CalculateZoom: function(data) {
		var p = $.isObject(data) ? Util.CalculatePrecision(data) : parseInt(data);
		var z = null;
		switch (p) {
			case 1:
				z = Mapper.ZoomLevels.Country;
				break;
			case 2:
				z = Mapper.ZoomLevels.State;
				break;
			case 3:
				z = Mapper.ZoomLevels.City;
				break;
			case 4:
				z = Mapper.ZoomLevels.Zip;
				break;
			case 5:
				z = Mapper.ZoomLevels.Street;
				break;
			default:
				z = Mapper.ZoomLevels.Point;
		}
		return z;
	},
	ParseState: function(state) {
		var s = {};
		if (state) {
			var h = state.split("&");
			for (var i in h) {
				var kv = h[i].split("=");
				if (kv.length === 2) {
					s[kv[0].toLowerCase()] = kv[1];
				} else if (kv.length === 1) {
					s[kv[0].toLowerCase()] = null;
				}
			}
		}
		return s;
	},
	CompileState: function(state) {
		var s = [];
		for (var i in state) {
			var x = state[i];
			if ($.empty(x)) {
				s.push(i);
			} else {
				s.push(i + "=" + x);
			}
		}
		return s.join("&");
	},
	AbortExecute: function() {
		var q = A4J.AJAX.EventQueue.getQueue("execute");
		if (q && q.items) {
			for (var i = q.items.length - 1; i >= 0; i--) {
				if (q.items[i].request) {
					q.items[i].request.abort();
				}
				q.items.splice(i, 1);
			}
		}
		return false;
	},
	SettingEnabled: function(setting) {
		var se = [];
		var p = new RegExp($.initCase(setting) + "\.[a-zA-Z]+$");
		for (var i in Util.Data.settings) {
			if (p.test(i) && $.bool(Util.Data.settings[i])) {
				se.push(i.substr(i.indexOf(".") + 1).toLowerCase());
			}
		}
		return se;
	},
	LayerByName: function(name) {
		for (var i = 0; i < Util.Data.layers.length; i++) {
			if (Util.Data.layers[i].name === name) {
				return Util.Data.layers[i];
			}
		}
		return null;
	},
	LayerByGis: function(gis) {
		for (var i = 0; i < Util.Data.layers.length; i++) {
			if (Util.Data.layers[i].layerName === gis) {
				return Util.Data.layers[i];
			}
		}
		return null;
	},
	LayersByGis: function(gis) {
		var d = [];
		for (var i = 0; i < gis.length; i++) {
			var ld = Util.LayerByGis(gis[i]);
			if (ld) {
				d.push(ld);
			}
		}
		return d;
	},
	LayerGisByName: function(name) {
		var ld = Util.LayerByName(name);
		return ld ? ld.layerName : null;
	},
	LayersGisByNames: function(names) {
		var g = [];
		for (var i = 0; i < Util.Data.layers.length; i++) {
			if ($.inArray(Util.Data.layers[i].name, names) !== -1) {
				g.push(Util.Data.layers[i].layerName);
			}
		}
		return g;
	},
	LayersGisByMainNames: function(names) {
		var g = [];
		for (var i = 0; i < Util.Data.layers.length; i++) {
			if ($.inArray(Util.Data.layers[i].ldsmapsLayer, names) !== -1) {
				g.push(Util.Data.layers[i].layerName);
			}
		}
		return g;
	},
	LayersEnabled: function(type, excludeSub) {
		var le = [];
		var a = Util.LayerNames(type);
		if (a) {
			var tn = Util.LayerTypeNames(type);
			if (tn) {
				var p = new RegExp("(" + tn.join("|") + ")\\.[a-zA-Z]+$");
				for (var i in Util.Data.settings) {
					if (p.test(i) && $.bool(Util.Data.settings[i])) {
						var n = i.substr(i.indexOf(".") + 1);
						if ($.inArray(n, a) !== -1) {
							if (!excludeSub && /other.+/.test(n)) {
								var sub = "Boundaries." + n + ".Selected";
								if (Util.Data.settings[sub]) {
									n += ":" + Util.Data.settings[sub];
								}
							}
							le.push(n);
						}
					}
				}
			}
		}
		return le;
	},
	LayerInZoom: function(name) {
		if ($.isArray(name)) {
			var lz = [];
			for (var i = 0; i < name.length; i++) {
				if (Util.LayerInZoom(name[i])) {
					lz.push(name[i]);
				}
			}
			return lz;
		} else {
			var layer = name.split(":")[0];
			for (var i = 0; i < Util.Data.layers.length; i++) {
				if (Util.Data.layers[i].ldsmapsLayer === layer) {
					return Util.Data.layers[i].minZoomLevel ? Util.Data.layers[i].minZoomLevel <= Map.Zoom() : true;
				}
			}
			return true;
		}
	},
	LayerInZoomEnable: function(obj) {
		obj.each(function() {
			var id = $(this).attr("data-id");
			if (Util.LayerInZoom(id)) {
				if ($(this).hasClass($.classes.disabled)) {
					$(this).removeClass($.classes.disabled).removeAttr("title");
					$(this).find("input:checkbox").removeAttr("disabled");
				}
			} else {
				if (!$(this).hasClass($.classes.disabled)) {
					$(this).addClass($.classes.disabled).attr("title", $.resource("item_zoom_disabled"));
					$(this).find("input:checkbox").attr("disabled", "disabled");
				}
			}
		});
	},
	LayerExist: function(name, type) {
		var a = Util.LayerNames(type);
		return a ? $.inArray(name, a) > -1 : false;
	},
	LayerTypeName: function(type) {
		if (type) {
			for (var i in Util.LayerTypes) {
				if (type === Util.LayerTypes[i]) {
					return i;
				}
			}
		}
		return null;
	},
	LayerTypeNames: function(type) {
		if (type) {
			var tn = Util.LayerTypeName(type);
			return tn ? [tn] : null;
		} else {
			var d = [];
			for (var i in Util.LayerTypes) {
				d.push(i);
			}
			return d.length ? d : null;
		}
	},
	LayerTypeByName: function(name) {
		for (var i in Util.LayerTypes) {
			if (Util.LayerExist(name, Util.LayerTypes[i])) {
				return Util.LayerTypes[i];
			}
		}
		return null;
	},
	LayerNames: function(type) {
		var tn = Util.LayerTypeNames(type);
		if (tn) {
			if (tn.length === 1) {
				return Util.Data[tn[0].toLowerCase()] || null;
			} else if (tn.length > 1) {
				var d = [];
				for (var i = 0; i < tn.length; i++) {
					var ln = Util.Data[tn[i].toLowerCase()];
					if (ln) {
						$.merge(d, ln);
					}
				}
				return d.length ? d : null;
			}
		}
		return null;
	},
	LayerMarkerByGis: function(gis) {
		var ld = Util.LayerByGis(gis);
		if (ld.boundary) {
			ld = Util.LayerByName("meetinghouses");
		}
		return ld;
	},
	LayerBGByGis: function(obj, gis) {
		var ld = Util.LayerByGis(gis);
		return obj.css({"background-image":$.format("url('{0}{1}.png')", $.resolveUrl("images/"), ld.boundary ? "boundaries" : "places"),
			"background-position":$.format("2px {0}px", 4 - 32 * ld.iconIndex)});
	},
	LayerSelect: function(id, layer) {
		if (!Util.LayerSelect.init) {
			$.extend(Util.LayerSelect, {
				init: true,
				cid: null,
				id: null,
				layer: null,
				enabled: false,
				Reset: function() {
					if (Util.LayerSelect.cid) {
						var cid = Util.LayerSelect.cid;
						Util.LayerSelect.cid = Util.LayerSelect.id = Util.LayerSelect.layer = null;
						Util.DelayUntilMap(function() {
							Map.ClearLayer(cid);
						});
					}
				},
				Enable: function(enabled) {
					Util.LayerSelect.enabled = enabled;
					if (!enabled) {
						Util.LayerSelect.Reset();
					}
				},
				Current: function() {
					return Util.LayerSelect.cid ? $.format("{0},{1}", Util.LayerSelect.id, Util.LayerSelect.layer) : null;
				},
				Execute: function(id, layer) {
					if (!(Util.LayerSelect.enabled && Util.LayerSelect.cid && Util.LayerSelect.id == id && Util.LayerSelect.layer == layer)) {
						Util.LayerSelect.Reset();
						if (Util.LayerSelect.enabled && id && layer) {
							var ld = Util.LayerByName(layer);
							if (!$.empty(ld.arcgisLayerSingleId) && Util.LayerSelectable(id, ld.ldsmapsLayer)) {
								var details = {};
								details[ld.arcgisLayerSingleId] = "UNITNO=" + id;
								var l = new MapLayer(Util.LayerServices.Boundaries, [ld.arcgisLayerSingleId], details, null, 201);
								$.extend(Util.LayerSelect, {cid:l.ID(),id:id,layer:layer});
								Util.DelayUntilMap(function() {
									Map.Layer(l);
								});
							}
						}
					}
				}
			});
		}
		Util.LayerSelect.Execute(id, layer);
	},
	LayerSelectable: function(id, layer) {
		return Util.LayerExist(layer, Util.LayerTypes.Boundaries) || (Util.Data.units && $.inArray(parseFloat(id), Util.Data.units) !== -1);
	},
	Settings: function(key, value) {
		if (!Util.Settings.init) {
			$.extend(Util.Settings, {
				init: true,
				cookie: function(key, value) {
					$.cookie("org.lds.maps." + key, value, {expires:30});
				}
			});
			if (!$.isGlobal("Util_SetUserSettings")) {
				window["Util_SetUserSettings"] = function(settings) {
					var s = $.parseObject(settings);
					for (var i in s) {
						Util.Settings.cookie(i, s[i]);
					}
				};
			}
		}
		if ($.isObject(key)) {
			var settings = {};
			for (var i in key) {
				var v = key[i] !== null ? key[i].toString() : null;
				if (Util.Data.settings[i] != v) {
					settings[i] = v;
				}
			}
			if (!$.isEmptyObject(settings)) {
				$.extend(Util.Data.settings, settings);
				Util_SetUserSettings($.parseObject(settings));
			}
		} else if ($.defined(value)) {
			var s = {};
			s[key] = value;
			Util.Settings(s);	//TODO: add a delay/queue to this to see if other settings will be sent soon?
		} else {
			return Util.Data.settings[key];
		}
	},
	HasRole: function(role) {
		return $.defined(Util.Data.roles["ROLE_" + role.toUpperCase()]);
	},
	HasRoleInUnit: function(role, unit) {
		var r = Util.Data.roles["ROLE_" + role.toUpperCase()];
		if ($.defined(r)) {
			for (var i = 0; i < r.length; i++) {
				if (r[i] == unit) {
					return true;
				}
			}
		}
		return false;
	},
	RoleUnits: function(role) {
		return Util.Data.roles["ROLE_" + role.toUpperCase()];
	},
	NavControl: function() {
		if (!Util.NavControl.init) {
			$.extend(Util.NavControl, {
				init: true,
				radius: 51.5,
				Control: $("#map-controls"),
				PanControl: $("#map-compass"),
				SliderControl: $("#map-zoom-slider"),
				Selector: null,
				Show: function() {
					Util.NavControl.Control.stop(true, true).fadeTo("fast", 1);
				},
				Hide: function() {
					Util.NavControl.Control.stop(true, true).delay(500).fadeTo("slow", 0.4, function() {
						if (Util.NavControl.Selector) {
							Util.NavControl.Selector.hide();
						}
					});
				},
				PanMouseMove: function(e) {
					var o = Util.NavControl.PanOffset(e);
					if (o) {
						//TODO: highlight?
						Util.NavControl.PanControl.css("cursor", "pointer");
						return false;
					} else {
						//TODO: remove tooltip
						Util.NavControl.PanControl.css("cursor", "");
					}
				},
				PanClick: function(e) {
					var o = Util.NavControl.PanOffset(e);
					if (o) {
						Map.Pan(new MapPixel(o.x * 8, o.y * 8));
						return false;
					} else {
						//TODO: trigger map click
					}
				},
				PanOffset: function(e) {
					var o = Util.NavControl.PanControl.offset();
					var x = e.pageX - o.left - Util.NavControl.radius;
					var y = e.pageY - o.top - Util.NavControl.radius;
					if (Math.pow(x, 2) + Math.pow(y, 2) < Math.pow(Util.NavControl.radius, 2)) {
						return {x:x,y:y};
					}
					return null;
				},
				ZoomOut: function() {
					Map.Zoom(Map.Zoom() - 1);
					return false;
				},
				ZoomIn: function() {
					Map.Zoom(Map.Zoom() + 1);
					return false;
				},
				Slide: function(e, ui) {
					Map.Zoom(ui.value);
				},
				SlideUpdate: function() {
					Util.NavControl.SliderControl.slider("value", Map.Zoom());
				},
				SelectorToggle: function() {
					if (Util.NavControl.Selector) {
						if (!Util.NavControl.Selector.is(":visible")) {
							Util.NavControl.SelectorUpdate();
						}
						Util.NavControl.Selector.toggle();
					} else {
						Util.PanelByName("map-providers", function(name, panel) {
							Util.NavControl.Selector = panel.hide().appendTo(Util.NavControl.Control);
							Util.NavControl.Selector.find(".close-x").removeAttr("onclick").click(Util.NavControl.SelectorToggle);
							Util.NavControl.Selector.find("li a").click(Util.NavControl.SelectorClick);
							Util.NavControl.Selector.append("<span class='map-providers-tail'/>")
							Util.NavControl.SelectorToggle();
						});
					}
					return false;
				},
				SelectorUpdate: function() {
					if (Util.NavControl.Selector) {
						var m = Map.Provider().toLowerCase();
						var t = Map.Type();
						Util.NavControl.Selector.find($.format("a[href$='m={0}&t={1}']", m, t)).select(2);
					}
				},
				SelectorClick: function() {
					$(this).select(2);
					Util.NavControl.Selector.hide();
					return Main.UpdateStateHandler(this);
				},
				QuickZoomShow: function() {
					$("#map-zoom-quick").stop(true, true).slideDown();
				},
				QuickZoomHide: function() {
					$("#map-zoom-quick").stop(true, true).delay(200).slideUp();
				},
				QuickZoom: function() {
					Util.NavControl.QuickZoomHide();
					return Main.UpdateStateHandler(this);
				}
			});
			if ($.support.opacity && !Util.IsTouch) {
				Util.NavControl.Control.hover(Util.NavControl.Show, Util.NavControl.Hide);
			}
			Util.NavControl.PanControl.mousemove(Util.NavControl.PanMouseMove).click(Util.NavControl.PanClick);
			$("#map-zoom-out a").click(Util.NavControl.ZoomOut);
			$("#map-zoom-in a").click(Util.NavControl.ZoomIn);
			Util.NavControl.SliderControl.slider({value:2,min:0,max:22,slide:Util.NavControl.Slide});
			$("#map-selector a").click(Util.NavControl.SelectorToggle);
			if ($("#map-zoom-quick").length) {
				$("#map-zoom").hover(Util.NavControl.QuickZoomShow, Util.NavControl.QuickZoomHide);
				$("#map-zoom-quick").find("a").click(Util.NavControl.QuickZoom);
			}
			Util.DelayUntilMap(function() {
				Util.NavControl.SlideUpdate();
				Map.Bind("zoomend", Util.NavControl.SlideUpdate);
				if ($.support.opacity && !Util.IsTouch) {
					Util.NavControl.Hide();
				}
			});
		}
	},
	WindowToMapPixel: function(e) {
		if (e) {
			var o = Util.MapArea.offset();
			var x = e.pageX - o.left;
			var y = e.pageY - o.top;
			return (y < 0 || y > Util.MapArea.height() || x < 0 || x > Util.MapArea.width()) ? null : new MapPixel(x, y);
		} else {
			return null;
		}
	},
	WindowToMapPoint: function(e) {
		var p = Util.WindowToMapPixel(e);
		return p ? Map.PixelToPoint(p) : null;
	},
	WindowMapPanPixel: function(e) {
		var x = e.pageX;
		var y = e.pageY;
		var p = null;
		var o = Util.MapArea.offset();
		var t = o.top + 10;
		var b = Util.MapArea.height() + o.top - 10;
		var l = o.left + 10;
		var r = Util.MapArea.width() + o.left - 10;
		if (y < t || y > b || x < l || x > r) {
			p = new MapPixel();
			if (y < t) {
				p.Y -= 10;
			} else if (y > b) {
				p.Y += 10;
			}
			if (x < l) {
				p.X -= 10;
			} else if (x > r) {
				p.X += 10;
			}
		}
		return p;
	},
	DelayUntilMap: function(callback) {
		$.delayUntil(callback, function() {
			return Map.Loaded();
		});
	}
};

$.extend(Util.Settings, {
	Get:function(key, replace) {
		return $.nz(Util.Settings(key), replace);
	},
	Set:function(key, value) {
		return Util.Settings(key, value);
	}
});

