function $returnFalse() { return false; }

Function.implement({
	callOnce:function (bind) {
		if (!this.$alreadyCalled) this.call(bind);
		this.$alreadyCalled=true;
	},
	resetCall:function () {
		this.$alreadyCalled=null;
	},
	// use -- Math.cos.memoize(inValue)
	memoize:function(){
		var args=$A(arguments);
		return ((this.memoized || (this.memoized={}))[args] || (this.memoized[args]=this.apply(this, args)));
	}
});

Array.implement({
	removeDuplicates:function () {
		// TODO to implement
		return this;
	},
	pluck:function (property) {
		var a=[];
		this.each(function (o) { a.push(o?o[property]:null); });
		return a;
	},
	invoke:function (fn,args) {
		var result=[];
		for (var i=0,l=this.length;i<l;i++) {
			if (this[i] && this[i][fn]) result.push(args ? this[i][fn].pass(args, this[i])() : this[i][fn]());
		}
		return result;
	}
});

Window.implement({
	$E:function(selector) {
		return this.document.getElement(selector);
	},
	$ES:function(selector) {
		return this.document.getElements(selector);
	}
});
Element.implement({
	setHTML:function() {
		var html=Array.join(arguments,"");
		this.set("html",Element.execHTML(html));
		return this;
	},
	setVisibility:function (visible) { return this.set("visibility",visible); },
	visible:function () { return this.get("visibility"); },
	toggle:function () { this.set("display","toggle"); },
	hover:function (over,out) { return this.addEvent("mouseenter",over).addEvent("mouseleave",out); },

	setSelection:function (b) {
		if (Browser.engine.trident || Browser.engine.presto) this[(b?"remove":"add")+"Event"]("selectstart",$returnFalse);
		if (Browser.engine.gecko) this.style.MozUserSelect=b?"":"none";
		if (Browser.engine.webkit) this.style.KhtmlUserSelect=b?"":"none";
		return this;
	},

	removeTextNodes:function () {
		if (this._removedTextNodes) return;
		$A(this.childNodes).each(function (o) {
			if (o.nodeType===3) {
				if (/^\s+$/.test(o.data)) o.parentNode.removeChild(o);
			}
			else $(o).removeTextNodes();
		});
		this._removedTextNodes=true;
		return this;
	},

	removeChildren:function () {
		while (this.hasChildNodes()) this.removeChild(this.lastChild);
		return this;
	},

	retrieveOrStore:function (key,getValueFunction) {
		if (!this.retrieve(key)) this.store(key,getValueFunction.call(this));
		return this.retrieve(key);
	},

	addReplacingEvent:function (type,fn) {
		return this.addEvent(type,function (e) {
			e.stop();
			fn.call(this,e);
		});
	},
	effects:function(options) { return new Fx.Morph(this,options); },
	effect:function(property,options) {
		options=$extend({property:property},options);
		return new Fx.Tween(this,options);
	},
	
	/*
	Event delegation - instead of attaching events to new elements, attach to parent and use bubbling to fire children events

	Useage:

	<ul id="list">
		<li>Foo</li>
		<li>Bar</li>
		<li>Bar</li>
	</ul>

	$("list").delegateEvent("click","li",function(e) { this==li },false,true);
	// or
	$("list").delegateEvent("click",function (el) { return Element.get(el,'tag')=="li"; },function(e) { this==li },false,true);
	*/
	delegateEvent:function (type,selector,fn,preventDefault,stopPropagation) {
		var check=$type(selector)=="function" ? selector : function (target) { return Element.match(target,selector); };
		return this.addEvent(type,function (e) {
			var target=e.target;
			while (target!=this && target!=null) {
				if (check(target)) {
					if (preventDefault) e.preventDefault();
					if (stopPropagation) e.stopPropagation();
					return fn.apply($(target),[e]);
				}
				target=target.parentNode;
			}
		}.bind(this));
	},

	getThisOrParent:function (selector) { return Element.match(this,selector) ? this : this.getParent(selector); },
	getThisOrChild:function (selector) { return Element.match(this,selector) ? this : this.getElement(selector); },
	show:function () { return this.set("display",true); },
	hide:function () { return this.set("display",false); },
	// overrides toQueryString 
	toQueryString: function(){
		var queryString=[];
		this.getElements('input, select, textarea',false).each(function(el){
			if (!el.name || el.disabled) return;
			
			var value;
			if (el.tagName.toLowerCase() == 'select') value=Element.getSelected(el).map(function(opt){
				return opt.value;
			});
			else if (el.type == 'radio' || el.type == 'checkbox') {
				if (!el.checked) value=el.type == 'radio' ? null : "";
				else value=el.value;
			}
			else value=el.value;

			$splat(value).each(function(val){
				queryString.push(el.name+'='+encodeURIComponent(val));
			});
		});
		return queryString.join('&');
	}
});

Element.Properties.extend({
	visibility:{
		set:function (visible) {
			if (visible==="toggle") {
				this.set("visibility",!this.get("visibility"));
				return;
			}
			if (visible) this.removeClass("visibility-hidden").setStyle("visibility","");
			else this.addClass("visibility-hidden").setStyle("visibility","hidden");
		},
		get:function () {
			// check all parents for one that's not visible
			var p=this;
			while (p && p.get("tag")!="body" && p.getStyle("display")!="none" && p.getStyle("visibility")!="hidden") {
				p=p.getParent();
			}
			return p && p.get("tag")=="body";
		}
	},
	display:{
		set:function (visible) {
			if (visible==="toggle") {
				this.set("display",!this.get("display"));
				return;
			}
			if (visible) this.removeClass("display-none").setStyle("display","");
			else this.addClass("display-none").setStyle("display","none");
		},
		get:function () {
			return this.get("visibility");
		}
	}
});

Element.fromMarkup=function (html,options) {
	if (options===true) {
		options={ multipleElements:true };
	}
	options=$extend({
		js:true,
		css:true,
		callback:$empty
	},options);

	html=html || "";
	
	html=Element.execHTML(html,options.callback);

	var div=new Element("div").set("html",html);
	if (options.multipleElements) return div.getChildren();
	else {
		if (div.childNodes.length>1) return div;
		else return div.getFirst();
	}
};

// executes all external resources and returns the rest of the html
// supports callback when all external resources are loaded
Element.execHTML=function (html,callback) {
	var srcRx=/\ssrc=('|"|)(.*?)\1/gi,
		hrefRx=/\shref=('|"|)(.*?)\1/gi;
	var src;
	var inlineScripts=[],
		externalScripts=[],externalCss=[];

	html=html.replace(/\s*<script([^>]*?)>([\s\S]*?)<\/script>\s*/gi,function(all,attrs,content) {
		if (src=srcRx.exec(attrs)) externalScripts.push(src[2]);
		else inlineScripts.push(content);
		return '';
	})
	.replace(/\s*<link([^>]*?)\srel=('|"|)stylesheet\1([^>]*?)>\s*/gi,function(all,attrs1,attrs2) {
		if (relStylesheetRx.test(all)) 
		if (src=hrefRx.exec(attrs+" "+attrs2)) externalCss.push(src[2]);
		return '';
	}).trim();

	inlineScripts=inlineScripts.join("\n");

	var externalFilesCount=externalScripts.length+externalCss.length;
	
	var onLoad=function () {
		if (callback) callback();
		if (inlineScripts.length) $exec(inlineScripts);
	}.create({delay:10});

	var checkIfDone=function checkIfDone() {
		externalFilesCount--;
		if (externalFilesCount<=0) onLoad();
	};

	var javascriptLoader=Element.execHTML.javascript || Asset.javascript,
		cssLoader=Element.execHTML.css || Asset.css;
	
	if (externalFilesCount>0) {
		var opts=callback ? { onload:checkIfDone } : null;
		externalScripts.each(function (item) { javascriptLoader(item,opts); });
		externalCss.each(function (item) { cssLoader(item,opts); });
	}
	else onLoad();
	
	return html;
};

// patch
Asset._oldCss=Asset.css;
$extend(Asset,{
	css: function(source, properties){
		properties = $extend({
			onload: $empty
		}, properties);

		// Create a style element instead of a link element
		var style_node = new Element('style', $merge({
			'media': 'screen', 'type': 'text/css'
		}, properties)).inject(document.head);
		// Not sure why this is needed, but it is
		style_node.onload = properties.onload;
		delete properties.onload;
		
		if (source.indexOf("data:")==0) return Asset._oldCss(source);

		// Fetch CSS using XHR
		var request = new Request({
				method: 'get',
				url: source,
				style_node:style_node,
				onSuccess:function( css_text ){
					// Set the CSS
					// From http://yuiblog.com/blog/2007/06/...
					if (this.styleSheet) {
						this.styleSheet.cssText = css_text;
					} else {
						this.appendChild(document.createTextNode(css_text));
					}
					this.onload();
				}.bind( style_node )
			}
		);
		request.send();

		return style_node;
	}
});

$extend(Element.prototype,{
	clone: function(contents, keepid){
		switch ($type(this)){
			case 'element':
				var attributes={};
				for (var j=0, l=this.attributes.length; j < l; j++){
					var attribute=this.attributes[j], key=attribute.nodeName.toLowerCase();
					if (Browser.Engine.trident && (/input/i).test(this.tagName) && (/width|height/).test(key)) continue;
					var value=(key == 'style' && this.style) ? this.style.cssText : attribute.nodeValue;
					if (!$chk(value) || key == 'uid' || (key == 'id' && !keepid)) continue;
					if (value != 'inherit' && ['string', 'number'].contains($type(value))) attributes[key]=value;
				}
				var element=new Element(this.nodeName.toLowerCase(), attributes);
				if (contents !== false){
					for (var i=0, k=this.childNodes.length; i < k; i++){
						if (/script/i.test(this.childNodes[i].tagName)) continue; // this is the change

						var child=Element.clone(this.childNodes[i], true, keepid);
						if (child) element.grab(child);
					}
				}
				return element;
			case 'textnode': return document.newTextNode(this.nodeValue);
		}
		return null;
	}
});
function $clone(o) {
	if(typeof(o)!="object") return o;
	if(o==null) return o;
	var newO={};
	for (var i in o) newO[i]=$clone(o[i]);
	return newO;
}

String.implement({
	escapeHtml:function (isHtml) { 
		var str=this;
		if (!isHtml) str=str.replace(/>/g,"&gt;").replace(/</g,"&lt;");
		else str=str.replace(/\r?\n/g,"<br/>");
		str=str.replace(/"/g,"&quot;").replace(/'/g,"&#39;");
		return str;
	}
});

// make static object to have events
Events.makeObjectEventable=function (obj) { $extend(obj,Events.prototype); };

// make static object to have options get/set
Options.makeObjectOptionable=function (obj) { $extend(obj,Options.prototype); };

// make any class to have options get/set as a static member
Options.makeClassOptionable=function (cls) {
	cls.setOptions=function () {
		Options.prototype.setOptions.apply(cls.prototype,arguments);
	};
};

$(document.body);
var Config={};
/* domready events { */
function $domready(func,priority) {
	if (!$domready._list) $domready._list=[];
	if ($domready._loaded) func.delay(1);
	if (priority===undefined) priority=5;
	$domready._list.push({func:func,priority:priority});
}
$domready.loadAll=function () {
	$domready._loaded=true;
	if (!$domready._list) return;

	$domready._list.sort(function (a,b) {
		return a.priority-b.priority;
	});

	$domready._list.each(function (o) {
		o.func.call(window);
	});
	delete $domready._list;
};

function $DL() { $domready.apply(this,arguments); }

window.addEvent("domready",$domready.loadAll);
/* } domready events */

function addNamespace(ns) {
	if (!ns) return null;
	var levels=ns.split(".");
	var root=window;
	for (var i=0;i<levels.length;i++) {
		if (root[levels[i]]==undefined) root[levels[i]]={};
		root=root[levels[i]];
	}
	return root;
}

// TODO: make a function to add key and get new url without redirecting
// TODO: inherit Hash?
var QueryManager=new Class({
	data:null,
	// data - a key value collection
	initialize:function (data) {
		this.data=data;
	},
	add:function (keyValueCollection) {
		if (!(keyValueCollection instanceof Hash)) keyValueCollection=new Hash(keyValueCollection);

		this.data.extend(keyValueCollection);

		this.data.each(function (value,key) {
			if (key===null) this.data.erase(key);
		},this);

		this._redirect();
	},
	remove:function (names) {
		for (var i=0;i<names.length;i++) this.data.erase(names[i]);
	},
	get:function (key) {
		return this.data.get(key);
	},
	_redirect:function () {
		location.href=this.getUrl();
	},
	// abstract function
	getUrl:function () { throw new Error("not implemented"); }
});

var QueryStringManager=new Class({
	Extends:QueryManager,
	
	initialize:function () {
		var data=new Hash();
		location.search.substr(1).replace(/(.*?)=(.*?)(?:&|$)/g,function (match,key,value) {
			data.set(key,decodeURIComponent(value));
		});
		this.parent(data);
	},
	remove:function (names) {
		this.parent(names);
		return this;
	},
	getUrl:function () {
		return "?"+this.data.toQueryString();
	}
});

/* UrlQuery Manager */
var UrlQueryManager=new Class({
	Extends:QueryManager,

	initialize:function () {
		var data=new Hash();
		if (UrlQueryManager.query) UrlQueryManager.query.replace(/(.*?)(?:=(.*?))?(?:&|\/|$)/g,function (match,key,value) {
			if (key) data.set(key,value ? decodeURIComponent(value) : null);
		});
		this.parent(data);
	},
	get:function (key) {
		if (typeof(key)=="number") return this.data.getKeys()[key];
		return this.parent(key);
	},
	getUrl:function () {
		var extLess=UrlQueryManager.useExtensionlessUrlRewriting;
		var qsPrefix=extLess ? "/" : "?",
			delimeter=extLess ? "/" : "&";

		var urlQuery=[];
		this.data.each(function (value,key) {
			$splat(value).each(function(val) {
				if (val) val=val.toString().trim();
				if (!val && extLess) urlQuery.push(key); // only key
				else urlQuery.push(key+"="+encodeURIComponent(val)); // key and value
			});
		});
		
		return UrlQueryManager.pageUrl+qsPrefix+urlQuery.join(delimeter);
	}
});

// UQ.add({a:1})

var QS,UQ;
$domready(function () {
	QS=new QueryStringManager();
	UQ=new UrlQueryManager();
},2);

function h(o,funcs,showFuncContent) {
	if (o==null) return "[null]";
	var s=[];
	for (var i in o) {
		switch ($type(o[i])) {
			case "function":
				if (funcs) s.push(i+"\t\t"+(showFuncContent ? o[i] : o[i].toString().trim().substr(0,o[i].toString().search(/\r|\n|\{/))));
				break;
			case "array": s.push(i+"\t\t["+o[i].length+"]");
				break;
			default: s.push(i+"\t\t"+o[i]);
		}
	}
	return "\r\n"+s.join("\r\n")+"\r\n";
}
function r() { alert($A(arguments).join("\n")); };


// default failure for ajax requests
function $ajaxFailure(xh) {
	//r.apply(null,arguments);
	var err="";
	if (/<title>(.*?)<\/title>/.test(xh.responseText)) err=RegExp.$1;
	err=err.replace(/<.*?>/g,"");
	alert("Ajax Error: "+err);
}

function getAjaxDate(dateString) {
	var date=null;
	if (/Date\((\d+)\)/.test(dateString)) date=new Date(+RegExp.$1);
	return date;
}

function $waitUntil(waitUntil,onComplete,delay,timeout) {
	switch ($type(waitUntil)) {
		case "string":
			if (waitUntil.contains(",")) waitUntil=waitUntil.split(",");
			else waitUntil=[waitUntil];
			$waitUntil(waitUntil,onComplete,delay);
			return;
		case "array":
			var variableNames=waitUntil;
			waitUntil=function () {
				// check for all types, if one of them is undefined, return false
				for (var i=0;i<variableNames.length;i++) {
					var name=variableNames[i];
					if (name.contains(".")) {
						var parts=name.split(".");
						var temp=window;
						// if one of the parts of the object is still undefined, return false
						for (var j=0;j<parts.length;j++) {
							temp=temp[parts[j]];
							if (!temp) return false;
						}
					}
					if (window[name]==undefined) return false;
				}
				return true;
			};
			$waitUntil(waitUntil,onComplete,delay);
			return;
		case "function":
			break;
		default:
			throw new Error("Argument "+waitUntil+" is invalid for $waitUntil");
	}

	if (waitUntil()) {
		onComplete();
		return;
	}

	var timeoutPointer;
	var intervalPointer=function () {
		if (!waitUntil()) return;
		intervalPointer=$clear(intervalPointer);
		if (timeoutPointer) timeoutPointer=$clear(timeoutPointer);
		onComplete();
	}.periodical(delay || 100);
	// if after timeout ms function doesn't return true, abort
	if (timeout) timeoutPointer=function () {
		dbug.log("$waitUntil timed out.",waitUntil);
		intervalPointer=$clear(intervalPointer);
	}.delay(timeout);
}

/*
var glob=0;

$waitUntil(
	function () {
		console.log('glob');
		return glob==1;
	},
	function () {
		alert('glob!');
	},
	100
);
(function () { glob=1; }).delay(2000);
*/

// all external links opened in new window by default
// if you want to override this default behavior, put event on a link and stop propagation
/*$domready(function () {
	$(document.body).delegateEvent("click","a",function (e) {
		if (this.rel=="External" || this.hasClass("external") || !this.href.indexOf(Config.siteUrl)==0) {
			open(this.href);
			e.stop();
		}
	});
});*/

/* fix selects and labels on ie6 */
if (Browser.Engine.trident4) {
	$$("select").addEvents({
		focusin:function (e) { this.store("tempIndex",this.selectedIndex); },
		focus:function (e) { this.selectedIndex=this.retrieve("tempIndex"); }
	});
}

/*<debug>*/
(function () {
	if (!Browser.Engine.gecko) return;
	var _emptyTags=["img","br","input","meta","link","param","hr"];
	HTMLElement.prototype.__defineGetter__("outerHTML",function () {
		var attrs=this.attributes;
		var tag=this.tagName.toLowerCase();
		var str="<"+tag;
		for (var i=0;i<attrs.length;i++) str+=" "+attrs[i].name+"=\""+attrs[i].value+"\"";
		if (_emptyTags.indexOf(tag)) return str+"/>";
		return str+">"+this.innerHTML+"</"+tag+">";
	});
})();
/*</debug>*/
var Resources=function () {
	function init() {
		// register all in-page script.src and link.hrefs
		$$("script").each(function (tag) { if (tag.src) _rememberFiles(tag.src); });
		$$("link").each(function (tag) { if (tag.href) _rememberFiles(tag.href); });
	}

	var _registeredFiles=[];
	var _loc=location.protocol+"//"+location.host;

	function _alreadyRegistered(url) {
		url=url.toLowerCase();
		return _registeredFiles.contains(url);
	}
	
	// gets a handlerish (csshandler.ashx?files=/css/1.css,/css/2.css&r=12390) or simplish (/js/x.js / /js/x.js?r=65781) url
	// and returns an array with all paths
	function _getFilePaths(url) {
		// since ff returns urls including domain, remove this prefix of the urls to prevent 2nd registration
		if (url.indexOf("http://")==0) url=url.substr(_loc.length);
		// remove random number, and trailing ?/&
		url=url.contains("?") ? url.replace(/(\?)r=\d+&?|&r=\d+/,"$1").replace(/[\?&]$/,"") : url;
		if (url.contains("=")) url=url.substr(url.indexOf("=")+1);
		url=url.toLowerCase();
		return url.split(",");
	}

	function _rememberFiles(url) {
		var urls=_getFilePaths(url);
		_registeredFiles.extend(urls);
	}

	function _rememberFile(path) {
		_registeredFiles.push(path);
	}

	// gets a url and a method to register this file
	//    url: a url can be either with a handler such as csshandler.ashx?files=/css/1.css,/css/2.css
	//         or a simple url such as /js/x.js
	function _registerFiles(url,method,properties) {
		if (!url) return;
		//dbug.log("_registerFiles",url);

		var urls=_getFilePaths(url);

		// the url variable contains the full url to register. it can contain a handler like
		// jshandler.ashx?files=a.js,b.js
		// the purpose of this replacement is removing all files that already been registered
		// urls array contains all files from the qs of the handler or just a single url
		var removed=[];
		urls.each(function (path) {
			if (_alreadyRegistered(path)) {
				url=url.replace(new RegExp(path.escapeRegExp(),"gi"),"").replace(/,{2,}/g,"");

				removed.push(path);
			}
			_rememberFile(path);
		});

		// filter urls that already been registered to this page
		removed.each(function (remove) { urls.erase(remove); });

		//dbug.log("method",url);
		// check if there are urls left after filtering those that already registered
		if (urls.length) method(url,properties);
	}
	
	$domready(init,1);

	return {
		RegisterCssFile:function (href,properties) {
			_registerFiles(href,Asset.css,properties);
		},
		RegisterJsFile:function (src,properties) {
			_registerFiles(src,Asset.javascript,properties);
		},
		RegisteredFiles:_registeredFiles
	};
}();

if (Element.execHTML) $extend(Element.execHTML,{
	javascript:Resources.RegisterJsFile,
	css:Resources.RegisterCssFile
});

//Resources.RegisteredFiles

