/**
 * dFrame Javascript Singleton 
 * 
 * Dieses Singleton enthält wesentliche Javascript Funktionen für dFrame.
 * 
 * @author Steffen Müller
 * @version 0.1
 */

(function()
{
	// Static, private variables //////////////////////////////////////////////////////////////////////
	var _storage = null;
	var _blockStack = [];
	var _waitingElement = null;
	var _popupStack = [];
	var _labelCache = {};
	var _modalListeners = [];
	var _ajaxHeaderListeners = [];
	var _loadingPaths = {}; 
	
	var backendURL = "/dframe/";
	var styleURL = "/media/style/";
	var scriptURL = "/media/scripting/";
	
	// Start up the local storage if applicable
	try
	{
		if (jQuery.jStore)
		{
			$(function()
			{
				if (!jQuery.jStore.isReady)
				{
					jQuery.jStore.defaults.project = "dFrame@"+window.location.hostname;
					jQuery.jStore.defaults.flash = "/design/dframe/storage/jStore.html";
					jQuery.jStore.ready(function(engine)
					{
						engine.ready(function()
						{
							_storage = engine;
						});
					});
					jQuery.jStore.load();
				}
			});
		}
	}
	catch (ex) {}
	
	// Define helper functions	
	var fixURL = function (url, prefix)
	{
		if (typeof(url) != "string" || !url.length) return "";
		if (url.charAt(0) == '/') return url;
		if (url.match(/^\w+\:\/\/.*/)) return url;
	
		if (typeof(prefix) != "string") prefix = backendURL;
		return prefix+url;
	};
	
	var postToTarget = function (url, postdata, target)
	{
		var f = document.createElement("form");
		f.setAttribute("method", "post");
		f.setAttribute("action", url);
		f.setAttribute("target", target);
		
		for (var ind in postdata)
		{
			var i = document.createElement("input");
			i.setAttribute("type", "hidden");
			i.setAttribute("name", ind);
			i.setAttribute("value", postdata[ind]);
			f.appendChild(i);
		}
		
		document.getElementsByTagName("body")[0].appendChild(f);
		
		f.submit();
		window.setTimeout(function(){f.parentNode.removeChild(f);},1000);	
	};
	
	var xhrComplete = function (xhr, textstatus)
	{
		// Receive the headers and split them (they are delivered as a string)
		var headers = {};
	    var headerstrings = xhr.getAllResponseHeaders().split('\n');
	    for(var i = 0; i < headerstrings.length; i++)
	    {
	    	var s = headerstrings[i].split(":", 2);
	    	if ($.trim(s[0]) !== '')
	    		headers[$.trim(s[0]).toLowerCase()] = $.trim(s[1]);
	    }
	    
	    // Check: shall I redirect?
	    if (headers["x-dframe-redirect"])
	    {
	    	this.blockWaiting();
	    	top.location.href = headers["x-dframe-redirect"];
	    	return;
	    }
	    
	    // Unserialize known headers
	    if (headers["x-dframe-changed-classes"])
	    	headers["x-dframe-changed-classes"] = dUnserialize(headers["x-dframe-changed-classes"]);
	    if (headers["x-dframe-changed-objects"])
	    	headers["x-dframe-changed-objects"] = dUnserialize(headers["x-dframe-changed-objects"]);
	    if (headers["x-dframe-changed-collections"])
	    	headers["x-dframe-changed-collections"] = dUnserialize(headers["x-dframe-changed-collections"]);
	    
		// Call all listeners
		for (var i = 0; i < _ajaxHeaderListeners.length; i++)
		{
			try
			{
				_ajaxHeaderListeners[i](headers);
			}
			catch (ex) {}
		}
	};
	
	var checkPathExistence = function (path)
	{
		var parts = path.split(/\./);
		var dir = dFrame;
		for (var i = 0; i < parts.length; i++)
		{
			if (!dir[parts[i]])
			{
				if (dir[parts[i]] === false)
					return false;
				return null;
			}
			dir = dir[parts[i]];
		}
		return true;
	};
	
	// Otherwise, create a new one
	var df =
	({
		// Language and Locale Management /////////////////////////////////////////////////////////////////
		getLanguage: function ()
		{
			return "de";
		},
		
		// Server communication ///////////////////////////////////////////////////////////////////////////
		getJSON: function (url, postdata, action)
		{
			var opt = {};
			
			// Error handler and callback in case of success
			opt.error = function (req, status, error)
			{
				try
				{
					if (action) action(null);
				}
				catch (ex)
				{
					dFrame.showError(ex);
				}
		
				var msg = "AJAX failed "+status;
				if (error) msg += error.message;
				dError(msg);
			};
			opt.success = function (data, status)
			{
				try
				{
					if (action) action(data);
				}
				catch (ex)
				{
					dFrame.showError(ex);
				}
			};
			
			// Target, payload and method
			opt.dataType = "json";
			opt.url = fixURL(url);
			if (postdata)
			{
				opt.data = postdata;
				opt.type = "POST";
			}
			else
				opt.type = "GET";
			
			// Check for metadata in the headers
			opt.complete = xhrComplete;
			
			// Fire the actual ajax call
			$.ajax(opt);
		},
		
		getPlain: function (url, postdata, action)
		{
			var opt = {};
			opt.error = function (req, status, error)
			{
				if (action) action(null);
		
				var msg = "AJAX failed "+status;
				if (error) msg += error.message;
				dError(msg);
			};
			opt.success = function (data, status)
			{
				if (action) action(data);
			}
			opt.dataType = "text";
			opt.url = fixURL(url);
			if (postdata)
			{
				opt.data = postdata;
				opt.type = "POST";
			}
			else
				opt.type = "GET";
			
			// Check for metadata in the headers
			opt.complete = xhrComplete;
		
			$.ajax(opt);
		},
		
		getScript: function (url, action)
		{
			$.getScript(fixURL(url, backendURL+"jscripts/"), function()
			{
				window.setTimeout(function(){action()}, 0);
			});
		},
		
		getCSS: function (url)
		{
			if (document.createStyleSheet)
			{
				try
				{
					document.createStyleSheet(fixURL(url, styleURL));
					return;
				}
				catch (ex) {}
			}

			var s = document.createElement("link");
			$(s).attr("type", "text/css").attr("rel", "stylesheet").attr("href", fixURL(url, styleURL)).appendTo("head");
		},
		
		postToBlank: function (url, postdata)
		{
			postToTarget(url, postdata, "_blank");
		},
		
		postToIFrame: function (ifr, url, postdata)
		{
			var n = $(ifr).attr("name");
			if (!n)
			{
				n = "ifr"+df.createUniqueKey(16);
				$(ifr).attr("name", n);
			}
			postToTarget(url, postdata, n);
		},
		
		/**
		 * Register a callback function that is called every time an ajax request returns. The
		 * callback receives all headers from the request and can act on them.
		 * @param callback The callback function to be called. Gets an hash containing the headers
		 * as first parameter. The keys for the headers are lower-cased.
		 */
		bindAJAXHeaderListener: function (callback)
		{
			if (typeof(callback) == "function")
				_ajaxHeaderListeners.push(callback);
		},
		
		unbindAJAXHeaderListener: function (callback)
		{
			var ind = $.inArray(callback, _ajaxHeaderListeners);
			if (ind != -1)
				_ajaxHeaderListeners.splice(ind,1);
		},

		
		// Measuring dimensions and positions /////////////////////////////////////////////////////////////
		
		getScrolling: function (w)
		{
			if (!w) w = window;
			if (w.pageYOffset != null) // all except Explorer
				return {left: w.pageXOffset, top: w.pageYOffset};
			else if (w.document.documentElement && (w.document.documentElement.scrollTop > 0 || w.document.documentElement.scrollLeft > 0))
				return {left: w.document.documentElement.scrollLeft, top: w.document.documentElement.scrollTop};
			else if (w.document.body && (w.document.body.scrollTop > 0 || w.document.body.scrollLeft > 0))
				return {left: w.document.body.scrollLeft, top: w.document.body.scrollTop};
			return {left: 0, top: 0};
		},
		
		getWindowDimensions: function (w)
		{
			if (!w) w = window;
			var x, y;
			if (w.innerHeight) // all except Explorer
			{
				x = w.innerWidth;
				y = w.innerHeight;
			}
			else if (w.document.documentElement && w.document.documentElement.clientHeight)
			{
				// Explorer 6 Strict Mode
				x = w.document.documentElement.clientWidth;
				y = w.document.documentElement.clientHeight;
			}
			else if (w.document.body) // other Explorers
			{
				x = w.document.body.clientWidth;
				y = w.document.body.clientHeight;
			}
			return {width: x, height: y};
		},
		
		findContainingIFrame: function (doc)
		{
			// Determine documents window
			var win = null;
			if (doc.parentWindow)
				win = doc.parentWindow;
			else if (doc.defaultView) win = doc.defaultView;
			if (!win || !win.parent || !win.parent.document) return null;
			
			var ret = null;
			$("IFRAME", win.parent.document).each(function()
			{
				if (this.contentDocument)
				{
					if (this.contentDocument == doc)
					{
						ret = this;
						return false;
					}
				}
				else if (this.document)
				{
					if (this.document == doc)
					{
						ret = this;
						return false;
					}
				}
				else if (typeof(win.parent.document.frames[this.getAttribute("id")]) != "undefined")
				{
					var d = win.parent.document.frames[this.getAttribute("id")].document;
					if (d == doc)
					{
						ret = this;
						return false;
					}
				}
				
			});
			return ret;
		},
		
		// Shadow effect //////////////////////////////////////////////////////////////////////////////////
		
		/**
		 * Adds a nice, transparent shadow to the given element. It is necessary for the element to act
		 * as a layout parent - therefore, its css "position" property is set to "relative" if it is still
		 * "static".
		 * @param element The DOM node to add a shadow to.
		 */
		addShadow: function (element)
		{
			if (!element || element.nodeType != 1 || $(">DIV.df_modalbg", element).length) return;
			if ($(element).css("position") == "static")
				$(element).css("position", "relative");
			$(element).append('<div class="df_modalbg df_modalbg_n"></div><div class="df_modalbg df_modalbg_ne"></div><div class="df_modalbg df_modalbg_nw"></div><div class="df_modalbg df_modalbg_s"></div><div class="df_modalbg df_modalbg_se"></div><div class="df_modalbg df_modalbg_sw"></div><div class="df_modalbg df_modalbg_w"></div><div class="df_modalbg df_modalbg_e"></div>');
		},
		
		/**
		 * Removes a shadow that was added via addShadow earlier from the given element.
		 * @param element The shadowified object that shall loose its shadow.
		 */
		removeShadow: function (element)
		{
			$("DIV.df_modalbg", element).remove();
		},
		
		// IFrame Shim ////////////////////////////////////////////////////////////////////////////////////
		
		addShim: function (element)
		{
			var isp = document.createElement("iframe");
			var css =
			{
				left: -parseInt(($(element).outerWidth()-$(element).innerWidth())/2),
				top: -parseInt(($(element).outerHeight()-$(element).innerHeight())/2),
				width: $(element).outerWidth(),
				height: $(element).outerHeight()
			}
			$(isp).css(css);
			$(isp).addClass("df_iframeshim").attr("frameborder","0").attr("src","about:blank").appendTo(element);
		},

		// Messages ///////////////////////////////////////////////////////////////////////////////////////
		
		showSuccess: function (msg)
		{
			dMessage("success", msg);
		},
		
		showError: function (msg)
		{
			dMessage("error", msg);
		},

		// Modal windows //////////////////////////////////////////////////////////////////////////////////
		
		/**
		 * Displays a modal window.
		 * @param content A string (interpreted as html content or as an iframe url, depending on option
		 * "contentisurl") or a dom node (which is appended inside the element).
		 * @param options An option object with the following possible keys:
		 * - title (window title. will be used directly. mutually exclusive with label.)
		 * - label (window title. will be translated by LABEL, mutually exclusive with title)
		 * - width (inner width in css dimensions. If negative, creates a fullscreen with given gap)
		 * - height (optional, inner height in css dimensions. If negative, creates a fullscreen with given gap)
		 * - top (optional, top position in css dimension, centered if omitted)
		 * - left (optional, left position in css dimension, centered if omitted)
		 * - draggable (optional, default false. If true, the window can be dragged around)
		 * - contentisurl (optional, if true the content is interpreted as an url to be opened in an iframe)
		 * - postdata (optional, only used if contentisurl is true: array of postdata that is used to make a post request to the iframe)
		 * - bodyclass (optional, classname to assign to the body of the window)
		 * - bodyattributes (optional, object with key/values of attributes to assign to the body div)
		 * - shadow (optional, default false. If true, use a fancy shadow around the window)
		 * - buttons (optional array. Each item of the array is an object with the keys "label" or "title" and "callback")
		 * - keepscripts (optional. Only applicable if content is a string. If true, <script> tags are kept intact. Default false)
		 * - onshow (optional, function to be executed when the window is displayed)
		 * - onclose (optional, function to be executed when the window is closed)
		 * - onbeforeclose (optional, function to be executed when window close button is pushed. Return true to allow closing.)
		 */
		showModal: function (content, options)
		{
			var doc = document;
			if (typeof(content) == "object" && content.ownerDocument)
				doc = content.ownerDocument;
			
			// Show background as soon as possible
			df.blockUI(doc);
			
			// Get information 
			var wd = df.getWindowDimensions();
		
			// Care for valid options
			if (!options) options = {};
			
			// Calculate z-index
			var zindex = 5000 + _popupStack.length*1000;
			
			// Create popup code
			var code = '<div class="df_modalinner">';
			if (options.title || options.label)
				code += '<span class="df_modaltitle"><span></span></span>';
			code += '<span class="df_modalclose"></span><div class="df_modalcontent"></div>';
			if (typeof(options.buttons) == "object" && options.buttons.length)
				code += '<div class="df_modalbuttons"></div>';
			code += '</div>';
			
			// Create popup element
			var popup = doc.createElement("div");
			$(popup).addClass("df_modal df_base").css("z-index", zindex).html(code);
			if (options.shadow)
			{
				window.setTimeout(function(){dFrame.addShadow(popup);},0);
			}
			
			// Load label if necessary
			if (options.title)
			{
				$("span.df_modaltitle span", popup).text(options.title);
			}
			else if (options.label)
			{
				LABEL(options.label, function(str)
				{
					$("span.df_modaltitle span", popup).text(str);
				});
			}
			
			// Activate closer
			var onbeforeclose = null;
			$("span.df_modalclose", popup).click(function()
			{
				if (!onbeforeclose || onbeforeclose())
					df.closeModal(null);
			});
			
			// Assign bodyclass
			if (options.bodyclass) $("div.df_modalcontent", popup).addClass(options.bodyclass);
			
			// Assign body attributes
			if (typeof(options.bodyattributes) == "object" && options.bodyattributes.length)
			{
				for (var ind in options.bodyattributes)
					$("div.df_modalcontent", popup).attr(ind, options.bodyattributes[ind]);
			}
			
			// Insert buttons
			if (typeof(options.buttons) == "object" && options.buttons.length)
			{
				$("div.df_modalcontent", popup).addClass("df_modalcontent_buttons");
				var bcon = $("div.df_modalbuttons", popup).get(0);
				for (var i = 0; i < options.buttons.length; i++)
				{
					var pretitle = options.buttons[i].title;
					if (!pretitle) pretitle = options.buttons[i].label;
					var b = doc.createElement("a");
					$(b).data("clickfunc", options.buttons[i].callback)
						.html("<span>"+pretitle+"</span>").attr("href","#")
						.addClass("dcom_button_right").mousedown(function(evt)
					{
						$(this).data("clickfunc")(popup);
						return false;
					});
					
					if (options.buttons[i].label)
						LABEL(options.buttons[i].label, $("SPAN", b).get(0));
					
					$(bcon).append(b);
				}
			}
		
			// Activate dragging support
			if (options.draggable && (options.title || options.label))
			{
				$("span.df_modaltitle", popup).addClass("df_modaltitle_move").mousedown(function(evt)
				{
					var dragorigin = {left: evt.clientX, top: evt.clientY};
					var dragstart = $(popup).position();
				
					// Add the scrolling to the dragorigin so that it is substracted - we're fixed, baby!
					var sc = df.getScrolling();
					dragorigin.top += sc.top;
					dragorigin.left += sc.left;
					
					var mmove = function (evt)
					{
						// Get the position
						var dx = dragstart.left + (evt.clientX - dragorigin.left);
						var dy = dragstart.top + (evt.clientY - dragorigin.top);
						
						// Check that we do not drag out of the window
						var wd = df.getWindowDimensions();
						var r = dx+$(popup).outerWidth();
						var b = dy+$(popup).outerHeight();
						if (r > wd.width) dx -= r-wd.width;
						if (b > wd.height) dy -= b-wd.height;
						if (dx < 0) dx = 0;
						if (dy < 0) dy = 0;
						
						$(popup).css("left", dx+"px").css("top", dy+"px");
		
						// Prevent default
						if (evt.preventDefault) evt.preventDefault();
						return false;
					};
					var mup = function (evt)
					{
						$(doc).unbind("mouseup", mup).unbind("mousemove", mmove);
					};
					
					$(doc).mouseup(mup).mousemove(mmove);
					
					// Prevent default
					if (evt.preventDefault) evt.preventDefault();
					return false;
				});
			}
			
			// Adjust dimensions if necessary
			var calcfunc = function (value, reference)
			{
				var res = value.match(/^-(\d+)%/);
				if (res)
					return (100-2*parseInt(res[1]))/100*reference+"px";
				res = value.match(/^-(\d+)/);
				if (res)
					return reference-2*parseInt(res[1])+"px";
			};
			if (options.width && options.width.length && options.width.charAt(0) == "-")
				options.width = calcfunc(options.width, wd.width);
			if (options.height && options.height.length && options.height.charAt(0) == "-")
				options.height = calcfunc(options.height, wd.height);
		
			// Let the popup get into the wild
			_popupStack.push({element: popup, options: options});
			$("body", doc).append(popup);
			
			// Set content dimensions
			var con = $("div.df_modalcontent", popup).get(0);
			if (options.width)
				$(con).css("width", options.width);
			if (options.height)
				$(con).css("height", options.height);
			
			// Create the handler object for the outside world
			var myself =
			({
				beforeclose: function (func)
				{
					if (typeof(func) == "function")
						onbeforeclose = func;
				}
			});
			
			// Set content itself 
			if (typeof(content) == "string")
			{
				if (options.contentisurl)
				{
					$("div.df_modalinner", popup).css("padding", "0");
					var ifr = null;
					if (options.postdata)
					{
						$(con).html('<iframe name="dfmodal'+df.createUniqueKey(8)+'" frameborder="0" hspace="0" width="100%" height="100%" src="javascript:\'\'"></iframe>');
						ifr = $("IFRAME", con).get(0);
						df.postToIFrame(ifr, fixURL(content), options.postdata);
					}
					else
					{
						$(con).html('<iframe frameborder="0" hspace="0" width="100%" height="100%" src="'+fixURL(content)+'"></iframe>');
						ifr = $("IFRAME", con).get(0);
					}
					ifr.dModal = myself;
				}
				else
				{
					if (!options.keepscripts)
						$(con).html(content);
					else
						dReplaceInner(con, content);
				}
			}
			else
				$(con).append(content);
			
			// Position the popup
			if (!options.left)
			{
				options.left = Math.round((wd.width - $(popup).outerWidth())/2)
				if (options.left < 0) options.left = 0;
			}
			if (!options.top)
			{
				options.top = Math.round((wd.height - $(popup).outerHeight())/2);
				if (options.top < 11) options.top = 11;
			}
			$(popup).css("left", options.left).css("top", options.top);
			
			// Care for focus and onshow asynch
			window.setTimeout(function()
			{
				if ($("INPUT,A", popup).length)
					$("INPUT,A", popup).focus();
				else
					$(popup).focus();
				
				// Call onshow if given
				if (options.onshow) options.onshow(popup, con);
				
				// Call global popup listeners
				for (var i = 0; i < _modalListeners.length; i++)
					_modalListeners[i]("show", _popupStack.length, popup);
			}, 0);
			return myself;
		},
		
		closeModal: function (parameter)
		{
			// Check: if there is no own modal stack, check whether the parent window has one
			if (!_popupStack.length)
			{
				if (parent && parent.dFrame)
					parent.dFrame.closeModal(parameter);
				return;
			}
		
			var popup = _popupStack[_popupStack.length-1];
			if (popup.options.onclose)
				popup.options.onclose(parameter);
			
			_popupStack.pop();
			df.unblockUI();
			
			// Call global popup listeners
			for (var i = 0; i < _modalListeners.length; i++)
				_modalListeners[i]("close", _popupStack.length, parameter);

			// Remove the popup or do special treatment of ie
			$(popup.element).remove();
		},
		
		closeModalDelayed: function (parameter)
		{
			window.setTimeout(function()
			{
				df.closeModal(parameter);
			}, 100);
		},
		
		/**
		 * Register a callback function that is called every time a popup is opened or closed.
		 * @param callback The callback function to be called. Gets 3 parameters:
		 *                 1) the string "show" or "close"
		 *                 2) the actual depth of the modal level (1 for the first popup etc.)
		 *                 3) when "show", the popup element, when "close", the closing parameter.
		 */
		bindModalListener: function (callback)
		{
			_modalListeners.push(callback);
		},
		
		unbindModalListener: function (callback)
		{
			if (!callback)
				_modalListeners = [];
			else
			{
				var ind = $.inArray(callback, _modalListeners);
				if (ind != -1)
					_modalListeners.splice(ind,1);
			}
		},
		
		// Prompt, Yes/No and friends /////////////////////////////////////////////////////////////////////
		
		showPrompt: function (question, callback, prefill, label, is_password)
		{
			var tp = "text";
			if (is_password) tp = "password";
			var con = document.createElement("div");
			$(con).html('<p></p><p><input type="'+tp+'" /></p>').addClass("df_prompt");
			LABEL(question, $("P", con).get(0));
			
			var inp = $("input", con).get(0);
			if (typeof(prefill) == "string") inp.value = prefill;
			
			var cb = function ()
			{
				if (callback(inp.value) !== false)
					df.closeModal();
				else
					$(inp).css("background-color", "#F00").animate({backgroundColor: "#FFF"}, "slow");
			};
			
			var buttons =
			[
			 	{label: "website.prompt_ok", callback: cb}
			];
			
			var opts =
			{
				label: label,
				width: "300px",
				height: "70px",
				draggable: true,
				shadow: true,
				buttons: buttons,
				onshow: function(){inp.focus()}
			};
			
			df.showModal(con, opts);
		},
		
		showYesNo: function (question, callback, label)
		{
			var con = document.createElement("p");
			LABEL(question, con);
			
			var buttons =
			[
			 	{label: "website.prompt_no", callback: function(){df.closeModal(false);}},
			 	{label: "website.prompt_yes", callback: function(){df.closeModal(true);}}
			];
			
			var opts =
			{
				label: label,
				width: "300px",
				draggable: true,
				shadow: true,
				buttons: buttons,
				onclose: function(val){window.setTimeout(function(){callback(val);},1);}
			};
			df.showModal(con, opts);
		},
		
		alert: function (message, label)
		{
			var con = document.createElement("p");
			LABEL(message, con);
			
			var buttons =
			[
			 	{label: "website.prompt_ok", callback: function(){df.closeModal();}}
			];
			
			var opts =
			{
				label: label,
				width: "300px",
				draggable: true,
				shadow: true,
				buttons: buttons
			};
			df.showModal(con, opts);
		},		
		
		// Blocking of the whole UI - with or without wait screen /////////////////////////////////////////
		
		blockUI: function (doc)
		{
			if (typeof(doc) != "object") doc = document;
			
			// Only create a new shadow if necessary. Otherwise, only increase references
			if (_blockStack[_popupStack.length])
			{
				_blockStack[_popupStack.length].references++;
				return;
			}
			
			var zindex = 4500 + _popupStack.length*1000;
			
			var shadow = doc.createElement("div");
			$(shadow).addClass("df_shadow").css("z-index",zindex);
			$("body", doc).append(shadow);
			
			var appshadows = [];
			$("applet", doc).each(function ()
			{
				if (this.dAppShadow) return;
					
				// Create an iframe spoof
				var isp = document.createElement("iframe");
				$(isp).addClass("df_iframeshim_applet").attr('frameborder','0').attr('src','javascript:""').css("z-index",zindex-1);
				this.dAppShadow = isp;
				isp.dFrame = this;
				$("body").append(isp);
				
				// Get the position
				var pos = $(this).offset();
				
				// Check whether the thing to cover is fix positioned.
				var isfixed = false;
				var act = this.parentNode;
				while (act && act.nodeType == 1)
				{
					if ($(act).css("position") == "fixed")
					{
						isfixed = true;
						break;
					}
					act = act.parentNode;
				}
				if (isfixed)
				{
					$(isp).css("position", "fixed");
					var sc = df.getScrolling();
					pos.left -= sc.left;
					pos.top -= sc.top;
				}
		
				$(isp).css("left", pos.left+"px");
				$(isp).css("top", pos.top+"px");
				$(isp).css("width", $(this).outerWidth()+"px");
				$(isp).css("height", $(this).outerHeight()+"px");
				
				appshadows.push(isp);
			});
			
			_blockStack[_popupStack.length] = 
			{
				"shadow": shadow,
				"appletshadows": appshadows,
				"references": 1
			};
		},
		
		unblockUI: function ()
		{
			if (!_blockStack[_popupStack.length])
				return;
			
			// Decrement references. If more than zero, let it live
			var shadow = _blockStack[_popupStack.length];
			shadow.references--;
			if (shadow.references > 0) return;
			
			// Kill the shadow and the applet shadows
			$(shadow.shadow).remove();
			for (var i = 0; i < shadow.appletshadows.length; i++)
			{
				var as = shadow.appletshadows[i];
				as.dFrame.dAppShadow = null;
				$(as).remove();
			}
			
			// Remove the shadow from the shadow stack
			_blockStack.splice(_popupStack.length, 1);
		},
		
		blockWaiting: function (doc)
		{
			if (typeof(doc) != "object") doc = document;
			
			df.blockUI(doc);
			var index = _blockStack.length-1;
			var zindex = 4501 + index*1000;
			
			if (_waitingElement)
			{
				$(_waitingElement).css("z-index", zindex);
				return;
			}
			
			var el = doc.createElement("div");
			$(el).addClass("df_waiter").css("z-index", zindex).appendTo($("BODY", doc));
			_waitingElement = el;
		},
		
		unblockWaiting: function (keepblock)
		{
			if (!_waitingElement) return;
			$(_waitingElement).remove();
			_waitingElement = null;
			if (!keepblock)
				df.unblockUI();
		},
		
		// UNIQUE KEYS ////////////////////////////////////////////////////////////////////////////////////
		
		createUniqueKey: function (length)
		{
			if (typeof(length) != "number") length = 24;
			var out = "";
			var rnd = -1;
			for (var i = 0; i < length; i++)
			{
				rnd = Math.round(Math.random()*61);
				if (rnd <= 25)
					out += String.fromCharCode(65+rnd);
				else if (rnd <= 51)
					out += String.fromCharCode(71+rnd);
				else
					out += ""+(rnd-52);
			}
			return out;
		},
		
		// DOCTYPE ANALYSIS ///////////////////////////////////////////////////////////////////////
		
		getDocType: function (doc)
		{
			if (!doc) doc = document;
			var ret = {type: false, version: false, importance: false, quirks: true};
			var re= /\s+(X?HTML)\s+([\d\.]+)\s*([^\/]+)*\//gi;

			if (doc.all && doc.all[0].nodeType==8)
			{
				ret.quirks = false;
				var res = re.exec(doc.all[0].nodeValue);
				if (res)
				{
					ret.type = res[1];
					ret.version = res[2];
					ret.importance = res[3];
				}
			}
			else if (doc.doctype != null)
			{
				ret.quirks = false;
				var res = re.exec(doc.doctype.publicId);
				if (res)
				{
					ret.type = res[1];
					ret.version = res[2];
					ret.importance = res[3];
				}
			}
			return ret;
		},
		
		// PATH FUNCTIONS /////////////////////////////////////////////////////////////////////////
		
		getBackendURL: function ()
		{
			return backendURL;
		},
		
		setBackendURL: function (url)
		{
			backendURL = url;
		},
		
		getStyleURL: function ()
		{
			return styleURL;
		},
		
		setStyleURL: function (url)
		{
			styleURL = url;
		},
		
		getScriptURL: function ()
		{
			return scriptURL;
		},
		
		setScriptURL: function (url)
		{
			scriptURL = url;
		},
		
		// LIBRARY FUNCTIONS //////////////////////////////////////////////////////////////////////
		
		provide: function(path)
		{
			if (!path || !path.length) return;
			var parts = path.split(/\./);
			var dir = this; 
			for (var i = 0; i < parts.length; i++)
			{
				dir[parts[i]] = dir[parts[i]] || {};
				dir = dir[parts[i]];
			}
		},
		
		require: function (path, callback)
		{
			if (typeof(path) == "object" && path.length)
			{
				var index = 0;
				var func = function()
				{
					// Deal with the indexes
					var i = index;
					index++;
					
					// Break the vicious circle
					if (i >= path.length)
					{
						if (callback) callback();
						return;
					}
					
					// Require the element
					if (typeof(path[i]) == "string")
						dFrame.require(path[i], func);
				};
				func();
				return;
			}
			else if (typeof(path) != "string" || !path.length)
				return;

			// Check whether the path already exists
			var existence = checkPathExistence(path); 
			if (existence)
			{
				if (callback) callback();
				return;
			}
			
			// Check whether we are first (=existence is null)
			var firsttry = !_loadingPaths[path];
			_loadingPaths[path] = true;
			
			// Create wrapper
			var faultcount = 0;
			var cbwrapper = function ()
			{
				if (!checkPathExistence(path))
				{
					if (faultcount > 10)
					{
						if (firsttry)
							dFrame.showError("Could not require component '"+path+"'. Please reload page.");
						return;
					}
					
					window.setTimeout(cbwrapper, 100);
					faultcount++;
					return;
				}
				
				if (callback)
				{
					delete _loadingPaths[path];
					callback();
				}
			};

			// Nope: include it
			if (firsttry)
				this.getScript(scriptURL+"dframe."+path.toLowerCase()+".js", cbwrapper);
			else
				cbwrapper();
		},
		
		// CREATING CALLBACK CLOSURES /////////////////////////////////////////////////////////////////
		createCallback: function (ctx, func)
		{
			if (!ctx) ctx = window;
		    var args = [];
		    for (var i=2; i < arguments.length; i++)
		        args.push(arguments[i]);
		    return function()
		    {
		    	try
		    	{
		    		func.apply(ctx, args);
		    	}
		    	catch (ex)
		    	{
		    		if (window.console) console.log(ex);
		    		dFrame.showError(ex.message);
		    	}
		    	return false;
		    };
		},
		
		// CREATING STRINGIFIED CLOSURES //////////////////////////////////////////////////////////
		
		createJSONP: function (func)
		{
			var df = this;
			if (top.dFrame)
				df = top.dFrame;
			else
				top.dFrame = this;
			
			var k = this.createUniqueKey(8);
			if (!df["jp"]) df.jp = {};
			df.jp[k] = func;
			
			return "top.dFrame.jp."+k;
		},		
		
		// Permanent storage //////////////////////////////////////////////////////////////////////////////
		storeSet: function (key, value)
		{
			if (_storage)
			{
				if (typeof(value) != "string")
					value = dSerialize(value);
				_storage.set(key, value);
				return true;
			}
			else
				return null;
		},
		storeGet: function (key)
		{
			if (_storage)
				return _storage.get(key);
			else
				return null;
		},
		storeRemove: function (key)
		{
			if (_storage)
				return _storage.rem(key, value);
			else
				return null;
		},
		storeReady: function ()
		{
			return (_storage != null);
		}
	});
	window.dFrame = df;
	
	// Label Management ///////////////////////////////////////////////////////////////////////////////
	window.LABEL = function (key, callback)
	{
		// The key is an array - load all keys in the array, then call the callback
		if (key && typeof(key) == "object" && key.splice && key.length)
		{
			var result = {};
			var lf = this;
			var chainfunction = function (k, index, cb)
			{
				if (index >= k.length)
				{
					cb(result);
				}
				else
				{
					LABEL(k[index], function(val)
					{
						result[k[index]] = val;
						chainfunction(k, index+1, cb);
					});
				}
			};
			chainfunction(key, 0, callback);
			return;
		}
		
		// Otherwise, it has to be an ordinary string - do as always
		if (typeof(key) != "string" || !key.length) return callback("");
		var okey = key;
		key = key.toLowerCase();
		
		// Collect replacers
		var replacers = [];
	    for (var i = 2; i < arguments.length; i++)
	    	replacers.push(arguments[i]);
	
	    // Prepare the function for when we have found the string
		var fn = function (string)
		{
			 for (var i = 0; i < replacers.length; i++)
				 string = string.replace("%"+(i+1), replacers[i]);
			 
			 if (typeof(callback) == "function")
				 callback(string);
			 else if (typeof(callback) == "object")
				 callback.innerHTML = string;
		};
		
		// Analyze key
		var res = key.match(/^((\w+)\.)?(\w+)$/);
		if (res)
		{
			var section = res[2];
			if (!section) section = "general";
			var subkey = res[3];
		}
		else
			return fn(okey);
		
		var sectionfunc = function()
		{
			var str = okey;
			if (_labelCache[section][subkey])
				str = _labelCache[section][subkey];
			fn(str);
		};
		
		if (_labelCache[section])
			sectionfunc();
		else
		{
			if (dFrame.storeReady())
			{
				var s = dFrame.storeGet("dFrame.labelCache."+section);
				if (s)
				{
					_labelCache[section] = s;
					sectionfunc();
					return;
				}
			}
			
			df.getJSON("/go/main/labels/"+df.getLanguage()+"/"+section, null, function(data)
			{
				_labelCache[section] = data;
				dFrame.storeSet("dFrame.labelCache."+section, data);
				sectionfunc();
			});
		}
	};
})();