(function($) {
	$.extend({
		disableTextSelection: function() {
			$(document.documentElement).bind("drag selectstart", function() { return false; });
			$(document.body).addClass("no-select");
		},
		enableTextSelection: function() {
			$(document.documentElement).unbind("drag selectstart");
			$(document.body).removeClass("no-select");
		},
		esc: function(s) { return $("<div />").text(s).html(); }
	});
	$.fn.extend({
		any: function(p) {
			for (var i = 0; i < this.length; ++i)
				if (p.call(this[i])) return true;
			return false;
		},
		contains: function(node) {
			return this.any(function() { return this == node; });
		},
		calendar: function() {
			return this.each(function() {
				var dateField = $(this);
				
				// Create calendar object and bind to a "trigger" icon
				var cal = new Calendar(1, null, function(cal, date) { dateField.val(date); if (cal.dateClicked) cal.hide(); }, function(cal) { cal.hide(); });
				cal.create()
				cal.setDateFormat("%e/%m/%Y");
				cal.yearStep = 1;
				var trigger = $("<a href='#'><img src='/images/calendar-icon.png' /></a>");
				trigger.click(function() {
					cal.parseDate(dateField.val());
					cal.showAtElement(trigger[0], "CR");
					return false;
				});
				dateField.after(trigger).after($("<span>&nbsp;</span>"));
			});
			return this;
		},
		backgroundSubmit: function(callback) {
			// Submit the selected form or forms to a hidden iframe
			return this.each(function() {
				if (this.nodeName != "FORM") throw "Can't backgroundSubmit() something that isn't a form";
				
				var submitValues = {};
				
				function doneLoading() {
					if (this.contentWindow.location.href != "about:blank") {
						if (callback) callback.call(this.contentWindow, submitValues);
						$(this).remove();
					}
				}
				
				// Clone the form in order to submit without triggering events in browsers such as Chrome
				var frameName = "bgsubmit_";// + parseIntMath.random();
				var hiddenFrame = $("<iframe style='display:none' src='about:blank' name='" + $.esc(frameName) + "'></iframe>").load(doneLoading).appendTo($(document.body));
				var surrogateForm = $(this).clone().attr("target", frameName).hide().appendTo($(document.body));
				
				// In browsers such as IE, file fields can't be cloned properly.
				// Move all file fields from the original form to the surrogate form, submit and revert.
				surrogateForm.find("input[type=file]").remove();
				
				var files = [];
				$(this).find("input[type=file]").each(function() {
					files.push({el: this, parent: this.parentNode, before: this.nextSibling});
					$(this).appendTo(surrogateForm);
				});
				
				$($(surrogateForm).serializeArray()).map(function() {
					submitValues[this.name] = this.value;
				});
				
				surrogateForm.submit();
				surrogateForm.remove();
				
				surrogateForm.find("input[type=file]").remove();
				for (var i = files.length - 1; i >= 0; i--) files[i].parent.insertBefore(files[i].el, files[i].before);
			});
		},
		registerBackgroundSubmit: function(options) {
			return this.submit(function(e) {
				e.preventDefault();
				if (options.flag) var flagField = $("<input type='hidden' value=1 />").attr("name", options.flag).appendTo($(this));
				if (options.loader) var loader = $("<span>&nbsp;</span><img src='" + $.esc(options.loader) + "' />").insertAfter($(this).find("input[type=submit]").eq(0));
				$(this).backgroundSubmit(function(vals) {
					if (options.loader) loader.remove();
					if (options.callback) options.callback.call(this, vals);
				});
				if (options.flag) flagField.remove();
			});
		},
		dragdrop: function() {
			return this.each(function() {
				var list = $(this);
				list.mousedown(function(e) {
					if (e.target.nodeName != "IMG") return;
					var li = $(e.target).closest("li");
					
					// dragItem is the "floating" image that appears to be dragged around. It's actually a
					// copy of the real image, which is still in place, but hidden. I apply 15% transparency
					// to dragItem by calling fadeTo() rather than using the "opacity" CSS property.
					var dragItem = li.find("img").clone().css({position: "absolute"}).fadeTo(0, 0.85).appendTo("body");
					
					// Once the drag item is created, Opera triggers an additional mousedown event on it. This causes
					// Opera's built-in image-dragging logic to kick in, unless I call preventDefault() on that event too.
					dragItem.mousedown(function(e) { e.preventDefault(); });
					
					function reposition(e) {
						// Determine where the top-left corner of dragItem should go.
						var w = dragItem.width(), h = dragItem.height();
						var x = e.pageX - Math.floor(dragItem.width()) / 2, y = e.pageY - Math.floor(dragItem.height()) / 2;
						
						// Clamp to the list's rectangle
						var listPos = list.offset(), listW = list.width(), listH = list.height();
						x = Math.max(Math.min(x, listPos.left + listW - w), listPos.left);
						y = Math.max(Math.min(y, listPos.top  + listH - h), listPos.top );
						
						// Reposition the drag item while working around an IE 8 bug
						/*@cc_on if (@_jscript_version == 5.8) { dragItem.hide(); } @*/
						dragItem.css({left: x + "px", top: y + "px"});
						/*@cc_on if (@_jscript_version == 5.8) { dragItem.show(); } @*/
						
						// Move the hidden LI to a new position.
						// Because of borders, padding and margins, the mouse not be within an LI element.
						// Instead, locate the LI whose center is closest under the 2-norm.
						var closest = null, bestDist = 9999999;
						list.children().each(function() {
							var cur = $(this);
							var curPos = cur.offset();
							var thisDist = Math.pow(curPos.left + cur.width() / 2 - e.pageX, 2) + Math.pow(curPos.top + cur.height() / 2 - e.pageY, 2);
							if (thisDist < bestDist) { bestDist = thisDist; closest = cur; }
						});
						if (closest[0] != li[0]) {
							if (li.nextAll().contains(closest[0]))
								li.insertAfter(closest);  // If moving forwards, insert *after*
							else
								li.insertBefore(closest); // If moving backwards, insert *before*
						}
					}
					reposition(e);
					li.fadeTo(0, 0);
					$.disableTextSelection();
					$(document.documentElement).mousemove(function(e) {
						reposition(e);
						e.preventDefault();
					});
					$(document.documentElement).mouseup(function(e) {
						var targetPos = li.find("img").offset();
						dragItem.animate({left: targetPos.left + "px", top: targetPos.top + "px"}, {duration: 150, complete: function() { li.fadeTo(150, 1, function() { dragItem.remove(); }); }});
						$(document.documentElement).unbind("mousemove mouseup");
						$.enableTextSelection();
					});
					
					e.preventDefault(); // Prevent default image-dragging behaviour in some browsers
				});
			});
		},
		// Enqueue a jquery method call in the animation queue.
		// Example: $(elem).animate({backgroundColor: "blue"}, 500).then("css", {backgroundColor: "transparent"});
		// Example: $(elem).animate({backgroundColor: "blue"}, 500).then(function() { /* do something else */ });
		then: function(name) {
			if (name.constructor == Function) {
				var fn = name;
				this.queue(function() {
					var that = $(this);
					fn.call(that);
					that.dequeue();
				});
			}
			else {
				var args = Array.prototype.slice.call(arguments, 1);
				this.queue(function() {
					var that = $(this);
					that[name].apply(that, args);
					that.dequeue();
				});
			}
			return this;
		}
	});
	$(function() {
		$("input.date").calendar();
	});
})(jQuery);

