// should_close, onclose
window.Overlay = function (content, ready, width)
{
	if (!ready)
		ready = function ()
		{
		}

	return OverlayService.create(content, ready, width ? Number(width) : 400);
}

Overlay.any_open = function ()
{
	return OverlayService.list.length > 0;
}

window.LoadingOverlay = function (loading_message, ready, width)
{
	if (typeof(loading_message) == "string")
		loading_message = document.createTextNode(loading_message);

	var overlay = new Overlay(loading_message, ready, width);

	overlay.is_loading = true;
	overlay.ready = function (content)
	{
		this.is_loading = false;

		if (typeof(content) == "string")
		{
			var temp = content;
			content = $create("div");
			content.innerHTML = temp;
		}

		Utils.copy_style_height(this, content);
		content.style.display = "block";

		this.removeChild(loading_message);
		this.appendChild(content);

		function go()
		{
			this.resize(content);
		}
		window.setTimeout($thisfunc(this, go), 0);
	}

	return overlay;
}

window.HttpOverlay = function (url, loading_message, failure_message, width)
{
	if (!loading_message)
		loading_message = "Loading...";
	if (!failure_message)
		failure_message = "Unable to load content.  Please try again.";

	var overlay = new LoadingOverlay(loading_message, undefined, width);

	function finish(html)
	{
		overlay.ready(html);
	}

	function fail()
	{
		overlay.ready(failure_message);
	}

	rs_http_get_html(url, finish, fail);
	return overlay;
}

window.OverlayService = {
	BG_FADE_TIME: 300,
	BG_FADE_OPACITY: 0.6,
	RESIZE_GROW_TIME: 200,

	PADDING_X: 20,
	PADDING_Y: 20,
	INITIAL_Y: 80,
	INITIAL_Z: 100,

	STAGGER_FADE_TIME: 120,
	STAGGER_STEP_TIME: 120,
	STAGGER_HIDE_TIME: 100,

	STAGGER_STEP_X: 10,
	STAGGER_STEP_Y: 12,
	STAGGER_OPACITY: 0.4,

	retain_bg: 0,
	resize_hooked: false,
	list: [],

	// Show or retain the overlay background.  Should be paired with hide_bg() calls.
	show_bg: function ()
	{
		if (this.retain_bg++ > 0)
			return;

		// Might not have loaded this yet.
		if (typeof(Utils) != "undefined")
		{
			var anim = Utils.cached_anim($("overlay-background"));
			anim.stop().duration(this.BG_FADE_TIME).show().to("opacity", this.BG_FADE_OPACITY).from(0.01).go();
		}

		if (!this.resize_hooked)
		{
			this.resize_hooked = true;
			$queue_event(window, "resize", $thisfunc(this, this.reposition));
		}

		// Close the topmost div when clicking outside the overlay.
		$queue_event($("overlay-background"), "click", this.events.background_click);
		$("overlay-background").style.zIndex = this.INITIAL_Z;

		// Escape key.
		$queue_event(document, "keydown", this.events.keydown);
	},

	// Hide or release he overlay background.  Should be paired with show_bg().
	hide_bg: function ()
	{
		if (--this.retain_bg > 0)
			return;

		Utils.cached_anim($("overlay-background")).stop().duration(this.BG_FADE_TIME).to("opacity", 0.01).hide().go();
		this.reposition_delay();

		$remove_event($("overlay-background"), "click", this.events.background_click);
		$remove_event(document, "keydown", this.events.keydown);
	},

	// Create a new overlay.
	create: function (content, ready, width)
	{
		var div = $extend($create("div"), OverlayService.extend);

		// We have to mark this so we fade in properly on repositioning.
		function cleanup()
		{
			div.service_init_anim = null;
			ready();
		}

		div.service_init_width = width + this.PADDING_X;

		div.style.width = width + "px";
		div.style.left = this.calc_center(div.service_get_width()) + "px";
		div.style.top = this.INITIAL_Y + "px";
		div.style.zIndex = this.INITIAL_Z + 1 + this.list.length;

		this.fit_in_screen(div);

		if (typeof(content) != "string" && typeof(content.cloneNode) != "undefined")
			div.appendChild(content);
		else
			div.innerHTML = content;

		div.service_init_anim = Animation(div);
		div.service_init_anim.duration(0).to("opacity", 0.01).checkpoint(1);
		div.service_init_anim.duration(this.BG_FADE_TIME).show().to("opacity", 1).checkpoint(1, cleanup).go();

		$("overlay-container").appendChild(div);
		this.list.push(div);
		this.reposition_delay();
		this.show_bg();

		return div;
	},

	// Close the topmost overlay.
	close_top: function ()
	{
		var div = this.get_top();
		if (div && div.should_close())
			div.close();
	},

	// Remove the specified overlay.
	remove: function (div)
	{
		// After it's done animating out, we reposition things and remove the overlay from the DOM.
		function cleanup()
		{
			OverlayService.reposition();
			$("overlay-container").removeChild(div);
		}
		Animation(div).duration(this.STAGGER_HIDE_TIME).to("opacity", 0.01).from(0.99).checkpoint(1, cleanup).go();

		var new_list = [];
		for (var i = 0; i < this.list.length; i++)
		{
			if (this.list[i] != div)
				new_list.push(this.list[i]);
		}

		if (this.list.length != new_list.length)
		{
			this.list = new_list;
			this.hide_bg();
		}
	},

	resize: function (div, content)
	{
		Utils.cached_anim(content).stop();

		function hardset()
		{
			function really()
			{
				content.style.height = "";
				// If more content is added, we want it to stay fixed until another resize.
				Utils.copy_style_height(content, content);
			}

			// Have to delay, since Animation sets it to auto after this.
			window.setTimeout(really, 0);
		}

		var current_height = Utils.get_style_height(content);
		var desired_height;

		content.style.height = "";
		desired_height = Utils.get_style_height(content);
		content.style.height = current_height + "px";

		var max_height = $get_style_num(div, "max-height");
		// If growing, only grow to max - then just jump the rest of the way.
		if (desired_height > max_height && max_height > current_height)
			desired_height = max_height;

		if (desired_height == "0")
			desired_height = "auto";
		else
			desired_height += "px";

		var anim = Utils.cached_anim(content, true).duration(this.RESIZE_GROW_TIME);
		anim.blind().to("height", desired_height).checkpoint(1, hardset).go();
	},

	// Reposition the overlay layering after a short delay.
	reposition_delay: function ()
	{
		window.setTimeout($thisfunc(this, this.reposition), 0);
	},

	// Reposition the overlay layering.
	reposition: function ()
	{
		var top = this.list.length - 1;
		var i = this.list.length - 1;
		var x, y;

		for (; i >= 0; i--)
		{
			var div = this.list[i];
			div.style.zIndex = this.INITIAL_Z + 1 + i;

			x = this.calc_center(div.service_get_width()) - this.STAGGER_STEP_X * (top - i);
			y = this.INITIAL_Y + this.STAGGER_STEP_Y * (top - i);

			var anim = Animation(div);

			// This means two overlays quickly in a row.
			if (i != top && div.service_init_anim)
			{
				div.service_init_anim.stop();
				div.service_init_anim = null;
			}

			if (div.service_init_anim == null)
				anim.duration(this.STAGGER_FADE_TIME).to("opacity", i == top ? 1 : this.STAGGER_OPACITY).checkpoint(1);
			anim.duration(this.STAGGER_STEP_TIME).to("left", x).to("top", y).go();

			if (i == top)
				this.fit_in_screen(div);
		}
	},

	fit_in_screen: function (div)
	{
		var wheight = 0;
		if ("innerHeight" in window)
			wheight = window.innerHeight;
		else if ("documentElement" in document)
			wheight = document.documentElement.clientHeight;

		div.style.maxHeight = "";
		if (wheight > 0)
		{
			wheight -= this.INITIAL_Y + this.PADDING_Y;
			if ($get_style_num(div, "max-height") > wheight)
				div.style.maxHeight = wheight + "px";
		}
	},

	// Calculate the centered left position of the item on the page.
	calc_center: function (width)
	{
		return Math.ceil((document.body.clientWidth - width) / 2);
	},

	// Get the top most overlay object.
	get_top: function ()
	{
		return this.list[this.list.length - 1];
	},

	events: {
		background_click: function ()
		{
			OverlayService.close_top();
		},

		keydown: function (ev)
		{
			if (ev.keyCode == 27)
			{
				OverlayService.close_top();
				return false;
			}

			return true;
		}
	},

	// Properties the Overlay is extended with on creation.
	extend: {
		className: "overlay",
		is_loading: false,

		// If this is under another overlay, close that one on click.
		onclick: function ()
		{
			if (this != OverlayService.get_top())
				OverlayService.close_top();
			return true;
		},

		// Intended for override.
		should_close: function ()
		{
			return !this.is_loading;
		},

		// Close this overlay (and shift any under it.)
		close: function ()
		{
			OverlayService.remove(this);
			if (typeof(this.onclose) != "undefined")
				this.onclose();
		},

		// Resize the overlay to the content's height.
		resize: function (content)
		{
			OverlayService.resize(this, content);
		},

		// Get the width of this overlay (may be cached in case of 0.)
		service_get_width: function ()
		{
			var w = $get_style_num(this, "width") + $get_style_num(this, "padding-left") + $get_style_num(this, "padding-right");
			return isNaN(w) || w <= 0 ? this.service_init_width : w;
		}
	}
};

window.$ = function (id)
{
	return document.getElementById(id);
}

window.$create = function (tagName, id)
{
	var el = document.createElement(tagName);
	if (id)
		el.id = id;
	return el;
}

window.$thisfunc = function (self, func)
{
	return function ()
	{
		func.apply(self, arguments);
	}
}

if (document.defaultView && document.defaultView.getComputedStyle)
	window.$get_style = function (obj, rule)
	{
		return document.defaultView.getComputedStyle(obj, "").getPropertyValue(rule);
	}
else
	window.$get_style = function (obj, rule)
	{
		function uc_prop(match, m1)
		{
			return m1.toUpperCase();
		}

		if ("currentStyle" in obj && obj.currentStyle)
		{
			rule = rule.replace(/\-([a-z])/g, uc_prop);
			if (rule in obj.currentStyle)
				return obj.currentStyle[rule];
		}

		return false;
	}

window.$get_style_num = function (obj, rule)
{
	var v = $get_style(obj, rule);
	if (typeof(v) == "string")
		v = v.replace(/px/g, "");
	else
		v = "";

	if (v == "")
		return 0;
	else if (!isNaN(parseInt(v)))
		return parseInt(v);
	else
		return Number(v);
}

window.$queue_event = function (o, ty, f)
{
	var type = "on" + ty;
	var typeQueue = type + "Queue";

	if (!(typeQueue in o))
	{
		o[typeQueue] = [];
		if (o[type])
			$queue_event(o, ty, o[type]);

		o[type] = function (ev)
		{
			if (!ev)
				ev = window.event;

			var ret;
			for (var i = this[typeQueue].length - 1; i >= 0; i--)
			{
				this.$temp = this[typeQueue][i];
				if (this.$temp)
					ret = this.$temp(ev);
			}

			this.$temp = null;
			return ret;
		}
	}

	o[typeQueue].push(f);
}

window.$remove_event = function (o, ty, f)
{
	var type = "on" + ty;
	var typeQueue = type + "Queue";
	var newQueue = [];

	for (var i = 0; i < o[typeQueue].length; i++)
	{
		if (o[typeQueue][i] != f)
			newQueue[i] = o[typeQueue][i];
	}

	o[typeQueue] = newQueue;
}

window.Utils = {
	get_style_height: function (src)
	{
		var height = $get_style_num(src, "height");

		if (isNaN(height))
			return src.scrollHeight;
		else
			return height;
	},

	copy_style_height: function (src, dest)
	{
		return dest.style.height = Utils.get_style_height(src) + "px";
	},

	// Use or create a cached Animation object.
	cached_anim: function (obj, reset)
	{
		if (typeof(obj._cached_anim) == "undefined" || reset)
			obj._cached_anim = Animation(obj);
		return obj._cached_anim;
	}
}

window.$extend = function (obj, props, skip_special)
{
	if (typeof(obj._extend_init) != "undefined")
		obj._extend_init = null;

	for (var k in props)
	{
		if ("hasOwnProperty" in props && !props.hasOwnProperty(k))
			continue;

		if (!skip_special && k.substr(0, 2) == "on")
			$queue_event(obj, k.substr(2), props[k]);
		else if (typeof(props[k]) == "object" && props[k] != null)
		{
			obj[k] = new props[k].constructor();
			$extend(obj[k], props[k], skip_special);
		}
		else
			obj[k] = props[k];
	}

	if (typeof(obj._extend_init) != "undefined" && obj._extend_init)
		obj._extend_init();

	return obj;
}
