(function() {
     $ = jQuery;
     
     $.fn.extend(
         {
	     join: function(){
		 return [].join.apply(this, arguments);
	     },
	     
	     reverse: function() {
		 return [].reverse.apply(this, arguments);
	     },
	     
	     hierarchy: function() {
                 var all = [];
                 var parent, index;
                 var element = this;
                 while(( parent = element.parent()) && parent[0] != document && parent.parent()[0] && !parent.is('html'))  {
                     index = $(parent).children(':not(.perpetually_ui)').index(element);
                     all.push(index);
                     element = parent;
                 }
                 return all.reverse();
             },
	     
	     canonical_selector: function(){
		 var selector = 'body';
		 var hierarchy = $(this).hierarchy();
		 for (var i=0; i < hierarchy.length; i++) {
		     selector += '>*:not(iframe):not(script):nth-child('+(hierarchy[i])+')';
		 };
                 return selector;
	     },
	     
	     perpetually_filter: function() {
                 if(Perpetually.Database.xpath(this.canonical_selector())) {
                     return this;
		 }

                 var parent = this.parents(':not(body, html):first');
                 if(parent.length) {
                     return parent.perpetually_filter();
                 }
                 else return $([])
	     },
	     
	     perpetually_ui: function(){
		 if(this.is('html,body')) return true;
		 return this.parents(':not(body, html)').andSelf().filter('.perpetually-ui').length;
	     },		

	     force_positioning: function(){
		 if(!this.css('position').match(/absolute|relative|fixed/)) this.css('position', 'relative');
		 return this;
	     },
	     
	     make_styles_explicit: function() {
		 var element = this[0];
	         var styles;
		 var prop, i;
		 if ( element.currentStyle )
	    	     styles = el.currentStyle // For Internet Explorer
	         else if ( element.ownerDocument.defaultView.getComputedStyle )
	    	 styles = element.ownerDocument.defaultView.getComputedStyle( element, null ) // For the standard

		 for (var i = TrackingStyles.length - 1; i >= 0; i--) {
		     prop = TrackingStyles[i];
		     element.style[prop] = styles[prop];	
		 }
		 $(element).children().each(function(which, child) {
				                $(child).make_styles_explicit();
			                    });
		 return this;
	     },
	     
	     mask: function(message) {
		 var mask = $('<div class="perpetually-ui mask"></div>')
		     .appendTo($('body'))
		     .width(this.outerWidth(true))
		     .height(this.outerHeight(true))
		     .css({
		              top: this.offset().top,
		              left: this.offset().left
		          })
		     .hide()
		     .fadeIn('fast');
		 
		 var data = $$(this);
		 if(!data.mask) data.mask = [];
		 data.mask.push(mask);
		 return mask;
	     },
	     
	     unmask: function() {
		 var data = $$(this);
		 if(!data.mask) data.mask = [];
		 $(data.mask).each(function() {
		                       $(this).remove();
		                   });
		 return this;
	     },
	     
	     outerHTML: function(){
		 var doc = this[0] ? this[0].ownerDocument : document;
	         return $('<div>', doc).append(this.eq(0).clone()).html();
	     },
	     
	     outline2 : function(opts){
		 var nudge = opts.nudge || 0;
                 var params = $.extend(
                     {css: {
                          "position": "absolute",
                          "z-index": 2147483645},
                      className: ""
                     }, opts),
                 offset = this.offset(),
		 top = offset.top,
		 left = offset.left,
		 width = this.outerWidth(),
		 height = this.outerHeight(),
                 elemId = "js-outline-id-" + new Date().getTime() + Math.ceil(Math.random() * 100);
                 var defaults = {top : offset.top, left : offset.left, 
                                 width : this.outerWidth(), height : this.outerHeight()};
                 function doCss(side, props) {
                     var appliedCss = {}; 
                     jQuery.each(defaults, function(key,value){
                                     if(typeof props[key] == "number"){
                                         appliedCss[key] = (value+props[key]).toString()+"px";}});
                     //console.dir(appliedCss);
                     return $("div class='js-outline-"+side+ "'></div>").css(appliedCss);};
                 var $elems = $(
                     [doCss("top",    {top:0,        left:-nudge,    width:0  }),
                      doCss("right",  {top:0,        left: width,    height:-nudge}),
                      doCss("bottom", {top:height,   left:-nudge,    width:0  }),
                      doCss("left",   {top:0,        left:-nudge,    height:-nudge})]);

		 if(this.data("outline.elemId")){ this.removeOutline(); }
		 this.data("outline.elemId", elemId);
		 $elems.each(function(){
				 $(this)
				     .css(params.css)
                                     .addClass(elemId)
                                     .addClass(params.className)
				     .appendTo("body");});		 
		 return this;},
	     
	     outline : function(opts){
		 var nudge = opts.nudge || 0;
                 var params = $.extend({
                                           css: {
                                               "position": "absolute",
                                               "z-index": 2147483645
                                           },
                                           className: ""
                                       }, opts),
                 offset = this.offset(),
		 top = offset.top,
		 left = offset.left,
		 width = this.outerWidth(),
		 height = this.outerHeight(),
                 elemId = "js-outline-id-" + new Date().getTime() + Math.ceil(Math.random() * 100),
		 $t = $("<div class='js-outline-top'></div>")
		     .css({ "top": top + opts.top, "left": left - nudge, "width": width }),
		 $r = $("<div class='js-outline-right'></div>")
		     .css({ "top": top +opts.top, "left": left + width, "height": height - nudge }),
		 $b = $("<div class='js-outline-bottom'></div>")
		     .css({ "top": top + height+opts.top, "left": left - nudge, "width": width }),
		 $l = $("<div class='js-outline-left'></div>")
		     .css({ "top": top + opts.top, "left": left - nudge, "height": height - nudge }),
		 $elems = $([$t, $r, $b, $l]);
		 if(this.data("outline.elemId")){ this.removeOutline(); }
		 
		 this.data("outline.elemId", elemId);
		 
		 $elems.each(function(){
				 $(this)
				     .css(params.css)
                                     .addClass(elemId)
                                     .addClass(params.className)
				     .appendTo("body");
			     });
		 
		 return this;
	     },

	     removeOutline: function(){
		 if(this.data("outline.elemId")){
                     var elemId = this.data("outline.elemId");
                     $("body > div." + elemId).remove();
                     this.data("outline.elemId", null);
		 }	
		 return this;
	     },
	     
	     // form element handling
	     disable: function(){
                 this.attr('disabled', 'disabled');
                 return this;
	     },
	     // form element handling
	     enable: function(){
		 this.removeAttr('disabled');
                 return this;
	     },
	     // form element handling
	     blank: function() {
		 var blank = /^[\s]+$|^$/;
		 if(this.is('input'))
		     return this.val().match(blank);
		 else
		     return this.text().match(blank);
	     }
	 });
 })();

String.prototype.interpolate = function(object) {
    return this.replace(/#\{(.*?)\}/g, function(_, match) {
		            return object[match];
	                });	
};			

function $$(param) {
    var node = $(param)[0];
    var id = $.data(node);
    $.cache[id] = $.cache[id] || {};
    $.cache[id].node = node;
    return $.cache[id];
}

jQuery.query_params = function() {
    var query = window.location.search.replace('?', '');
    var params = {};
    jQuery(query.split('&')).each(function(which, value) {
                                      var  pair = value.split('=');
                                      params[pair[0]] = pair[1]; 
                                  });
    return params
};
