// UnderscoreJS
// www.underscorejs.org

if (typeof UnderscoreJS=='undefined') {

	UnderscoreJS = function(frameObj) {  // default parameter is (window)
		// constructing with alternate frame allows UnderscoreJS to
		// perform it's actions against iframes seamlessly
		// iframe_js = new UnderscoreJS(frames['myframe']);
		
		// the window or frame used in mouse event, and window size functions
		this.frame = frameObj||window;
		
		// the document used in dom/xml/style functions
		this.document = frameObj.document;
		
		// store the url parameters for this page (eg. _js.param.page==2)
		if (this.frame.location) {
			this.param = this.getParams(this.frame.location.search);
			
			// set the debug level if "?debug=N" parameter is found
			if (this.param.debugLevel) this.debugLevel = this.param.debugLevel;
		}
		/* param EXAMPLE:
			// assume page loaded is index.html?var=myvariable1&debug=2
		
			alert(_js.param.var);  // result is "myvariable1"
			alert(_js.debugLevel);  // result is "2"
			
			//var mystr = "var1=myvariable1&var2=my+variable+2";
			//alert(_js.g
		*/
		
		// attach the onReady/onLoad event handlers if using the default _js object (window,document)

		//if (frameObj==window) {

			// set up the onready function stack
			var onreadys = [];
			
			// assign the _js.onReady() function
			this.onReady = function(f) {
				onreadys.push(f);
			}
			// handle the document's domcontentloaded event
			var doOnReady = function() {
				for (var i in onreadys) onreadys[i]();
			}
			
			if (this.document.addEventListener) {  //ff,opera,safari
				this.document.addEventListener("DOMContentLoaded", function () {
					if (/opera/i.test(navigator.userAgent) && this.document.styleSheets.length) {
						for (var i=0;i<this.document.styleSheets.length;i++)  // this trick was lifted from JQuery's opera handling
							if (this.document.styleSheets[i].disabled) {
								//alert('ss disabled ?');
								//setTimeout(arguments.callee,1);
								return;
							}
					}
					doOnReady();
				}, false);
			}
			else  {
				if (typeof this.document.onreadystatechange=='object') {  // ie6
					//alert('doing ie6 '+document.readyState);
					var dd = this.document;
					this.document.onreadystatechange = function() {
						// beware this will overwrite an existing readystatechange handler
						if (dd.readyState == "complete") doOnReady();
					}
				}
				else alert('supported browser');
			}
			
			// initialize the onload function stack
			var onloads = [];
			
			// assign the _js.onLoad() function
			this.onLoad = function(f) {
				onloads.push(f);
			}
			
			// handle the window onload event
			if (!window.onload) {  // will not overwrite an existing onload handler
				window.onload = function() {
					// call the onLoad functions
					for (var i in onloads) onloads[i]();
				}
			}

		//}

	}
	/* UnderscoreJS Constructor EXAMPLE:
		// assume an iframe exists in the document body
		<iframe name="otherframe" src="otherpage.html"></iframe>
		
		var iframe_js = new UnderscoreJS(frames.otherframe);
		alert(iframe_js.getScroll().y);  // shows the vertical scroll position of the iframe page
	*/

	UnderscoreJS.prototype = {
		
		/* HELPER FUNCTIONS */

		arrayContains : function(array,value) {
			for (var i=0; i<array.length; i++) {
				if (array[i]===value) return true;
			}
			return false;
		},

		// pass each element of an array to a function and return the array
		walk : function(a,f) {
			if (a.length && a.length>0) // simple array check
				for (var i=0;i<a.length;i++) 
					f(a[i]);
			else f(a); // if not an array just pass it directly to the function
			return a;
		},
		/* walk EXAMPLE:

			var numbers = [1,2,3];
			numbers.walk(function(number){
				alert(number)}
			);
		*/
		
		// trim whitespace from start and end of string
		trim : function(s) {
			return s.replace(/^\s+|\s+$/g,"");
		},
		/* trim EXAMPLE
		
			var mystring = "  my string  \n   ";
			mystring = _js.trim(mystring);   // result is "my string"
		*/
		
		/* BASE FUNCTIONS */
		debugLevel : 0,
		debug : function(msg) {
			if (this.debugLevel>0) {
				if (typeof msg=='object') msg = this.inspect(msg);
				else msg = "_js.debug : "+msg;
				if (this.debugLevel==2) {
					if (window.console) window.console.log(msg);  // firebug console logging
				}
				if (this.debugLevel==1) alert(msg);
			}
		},
		
		// return an examination of an object (2 levels deep) as a json string
		// this is only useful for debugging and shouldn't be used in production code and could be removed from future builds
		inspect : function(o,r) {
			var s = '{\n',t;
			var b = (r==false)?'\t\t':'\t';
			for (var i in o) {
				t = typeof o[i];
				if (t=='string') s += b+i+' : "'+o[i].replace('"','\"')+'"';
				else if (t=='number') s += b+i+' : '+o[i];
				else if (t=='object' && r==null) {
					s += b+i+' : '+this.inspect(o[i],false);
				}
				else s += b+i+' : "['+t+']"';
				s += ',\n';
			}
			s = s.substring(0,s.length-2)+'\n';
			if (r==false) s+= '\t';
			s += '}';
			return s;
		},
		/* inspect EXAMPLE
			
			var obj = {a:1, b:"b", c:function(){}, d:[], e:{}};
			alert(_js.inspect(obj)); // returns
		*/
		
		// browser feature checker
		features : {
			xhr : typeof XMLHttpRequest!='undefined',
			ax : typeof ActiveXObject!='undefined',
			dp : typeof DOMParser!='undefined',
			dv : typeof document.defaultView!='undefined',
			cd : (document.implementation&&typeof document.implementation.createDocument!='undefined'),
			xs : typeof XMLSerializer!='undefined',
			xsl : typeof XSLTProcessor!='undefined'
		},
			
		// add handlers to the window.onload event
		// note: this will not be called if window.load or <body onload=""> is used
		/*onLoad : function(f) {
			if (!this.onloads) this.onloads = [];
			this.onloads.push(f);
		},*/
		
		// add handlers to the DOMContentLoaded event, this is mostly equivalent to JQuery $(document).ready
		/*onReady : function(f) {
			//alert('adding onready '+f);
			if (!this.onreadys) this.onreadys = [];
			this.onreadys.push(f);
		},*/
		
		// call the onReady functions
		// internal use only
		/*doOnReady : function() {
			alert('doOnReady!');
			alert(this.onreadys.length);
			for (var i in this.onreadys) this.onreadys[i]();
		},*/
		
		// generic object property/value setter, returns object sent to it
		// _js.set(object,{property1:value1,property2,value2}) is equivalent to
		//			object.property1 = value1;
		//			object.property2 = value2;
		set : function(obj,val) {
			if (typeof obj=='string') obj=this.id(obj);
			if (typeof obj=='object' && typeof val=='object') {
				for(var i in val) {
					if (val[i]) obj[i]=val[i];
					else _js.debug('invalid value '+val+' for '+obj);
				}
			}
			else this.debug('_js.set() error:\nobj is undefined\nval = '+_js.inspect(val));
			return obj;
			//else if(o.constructor.toString().indexOf("Array")==-1) {
			//	for(var a in o)
			//		for(var i in v) 
			//			o[a][i]=v[i];
			//}	
		},
		
		// shortcut for document selector, internal use only
		_d : function(d) {
			return (!d?this.document:d);
		},
			
		// simply a shortcut for document.getElementById
		id : function(id, d){
			return this._d(d).getElementById(id);
		},
		
		select : function(o) {
			if (typeof o=='string') return this.id(o);
			else return o;
		},
			
		// parse url parameter to key.value pairs
		// "url?param1=value1&param2=value2" returns {param1:value1,param2:value2}
		getParams : function(url) {
			var a = {}, q = (url.length>1?url.substring(1).split("&"):[]);
			for(var i=0;i<q.length;i++) a[q[i].match(/^[^=]+/)] = unescape(q[i].replace(/^[^=]+=?/, ""));
			return a;
		},
		
		encodeParams : function(o) {
			var s = "";
			for (var i in o) {
				
			}
		},

		/* STYLE FUNCTIONS */
		// note: no error checking occurs in these functions by default
		// insert debug statements like this.debug(o.id+' = '+x+','+y) if you run into trouble
		
		// shortcut returns the element rather than the style object, useful for recursion
		style : function(o,s) {
			if (typeof o=='string') o = this.id(o);
			if (!o) return this.debug("null object sent to "+(arguments.callee.caller)?this.inspect(arguments.callee.caller):'style()');
			if (o.length) {  // use walk
				for (var i=0;i<o.length;i++) {
					this.set(o[i].style,s);
				}
			}
			else this.set(o.style,s);
			return o;
		},
		/* style EXAMPLE
			var elm = _js.id('myelm');
			_js.style(elm,{color:"red"});
		*/
		
		getStyle : function(o,s) {
			if (this.features.dv) {
				// return this.document.defaultView().getComputedStyle(o,"").getPropertyValue(s)
				var v = this.document.defaultView.getComputedStyle(o,"").getPropertyValue(s);
				alert(v);
			}
			else if (o.currentStyle) return o.currentStyle[s];
			else return o.style[s];
		},
		
		// keep track of highest z-index
		maxZIndex : 1000,

		// reset highest z-index  // _js.style(o,{zIndex:_js.maxZ()})
		maxZ : function() {
			return ++this.maxZIndex;
		},
		
		// sets width,height
		size : function(o,w,h) {
			return this.style(o,{width:this.psize(w),height:this.psize(h)});
		},

		// size() helper function, returns v+"px", min size is 0
		psize : function(v) {
			return typeof v=="number"?((v<0)? 0:v)+'px':v;
		},

		// sets left,top position
		pos : function(o,x,y) {
			return this.style(o,{left:parseInt(x)+'px',top:parseInt(y)+'px'});
		},

		// returns left,top position as {x:x,y:y}
		getPos : function(o) {
			return {x:parseInt(o.style.left),y:parseInt(o.style.top)};
		},

		// sets opacity 0 (transparent) to 1 (opaque)
		opacity : function(o,v) {
			o._opacity = v;
			return (window.ActiveXObject)? this.style(o,{filter:'alpha(opacity='+v*100+')'}):this.style(o,{opacity:v});
		},

		// sets visibility true (visible), false (hidden)
		visible : function(o,b) {
			/*alert('visible o='+o+' '+b);
			if (!o) {
				//_js.debug("_js.visible no object "+b);
				alert("_js.visible no object "+arguments.callee);
			}
			else */
			return this.style(o,{visibility:b?'visible':'hidden'});
		},

		// sets display true (block), false (none)
		display : function(o,b) {
			return this.style(o,{display:b?'block':'none'});
		},

		// sets clip as integer array [t,r,b,l]
		clip : function (o, c) {
			if (c&&c.length==4) return this.style(o,{clip:'rect('+c[0]+'px '+c[1]+'px '+c[2]+'px '+c[3]+'px)'});
			return this.style(o,{clip:'auto'});
		},

		// gets clip as integer array [t,r,b,l]
		getClip : function(o) {
			var c = o.style.clip;
			if (c && c.indexOf('rect(') == 0) {
				c = c.replace("rect(","");
				c = c.replace(")","");
				var v = c.split(" ");
				for (var i in v) v[i] = parseInt(v[i]);
				return v;
			}
			else return [0, o.offsetWidth, o.offsetHeight, 0];
		},
		
		/* ELEMENT/PAGE/WINDOW DIMENSIONS */

		// obtain page-absolute location and content size of an element, returned as {x:x,y:y,w:w,h:h}
		dimensions : function(o,d) {
			if (typeof o=='string') o = this.id(o);
			if (!d) d = this.document;
			var sx = (d.body.scrollLeft || d.documentElement.scrollLeft || 0);
			var sy = (d.body.scrollTop || d.documentElement.scrollTop || 0);
		
			var r;
			if (o.getBoundingClientRect) { // CSS3
				r = o.getBoundingClientRect();
				return {
					x:r.left + sx,
					y:r.top + sy,
					w:r.right - r.left,
					h:r.bottom - r.top
				};
			}
			else if (d.getBoxObjectFor) { // FF
				r = d.getBoxObjectFor(o);
				return {
					x:r.x,
					y:r.y,
					w:r.width,
					h:r.height
				};
			}
			else {
				var x=y=0;
				while(o.offsetParent) {
					x += o.offsetLeft;
					y += o.offsetTop;
					o=o.offsetParent;
				}
				return {
					x:x + sx,
					y:y + sy,
					w:o.offsetWidth,
					h:o.offsetHeight
				};
			}
		},
		
		getWindowSize : function(f) {
			f=!f?this.frame:f;
			//var o = (f.document.documentElement)? f.document.documentElement:f.document.body;
			o = f.document.documentElement;
			//var h=;  //window.innerHeight ||
			//var w=;
			return {
				w : o.clientWidth,
				h : o.clientHeight
			};
		},

		// finds max size of body's children (this is iframe-safe)
		getPageSize : function(d) {
			d = this._d(d);
			var n;
			var size = {w:0,h:0};
			if (!d.body || !d.body.childNodes) return size;
			for (var i=0;i<d.body.childNodes.length;i++) {
				n = d.body.childNodes[i];
				if (n.nodeType==1) {
					size.w = Math.max(size.w,n.offsetWidth);
					size.h = Math.max(size.h,n.offsetHeight);
				}
			}
			var db = _.dimensions(document.body);
			size.w = Math.max(db.w,size.w);
			size.h = Math.max(db.h,size.h);
			return size;
			/*
			//if(!d) d=_js.frame.document;
			d=!d?document:d;
			//var w = ;  //f.innerWidth,h:Math.max((f.innerHeight || 0);
			//var h = ; //f.document.body.clientHeight, f.document.documentElement.clientHeight, f.document.body.scrollHeight
			return {
				w : d.body.offsetWidth,
				h : d.body.offsetHeight
			};*/
		},
		
		scroll : function(x,y,d) {
			d = this._d(d);
			//var elm = d.body; //(!!d.documentElement && !/webkit/i.test(navigator.userAgent))?d.documentElement:d.body;
			d.documentElement.scrollLeft = d.body.scrollLeft = x;
			d.documentElement.scrollTop = d.body.scrollTop = y;
		},
		
		getScroll : function(d) {
			d = this._d(d);
			var elm = (d.body.scrollLeft!=null)? d.body:d.documentElement;
			//var b = (d.documentElement)?d.documentElement:d.body;
			var x=y=0;
			x = elm.scrollLeft;
			y = elm.scrollTop;
			
			/*if (b.scrollLeft!=null) {
				alert('a '+d.documentElement.scrollTop+' '+d.body.scrollTop);
				x = b.scrollLeft;
				y = b.scrollTop;
			}
			else if (d.body && d.body.scrollLeft!=null) {  // safari has documentElement but it's scroll values are on the body
				x = d.body.scrollLeft;
				y = d.body.scrollTop;
			}*/
			return {x:x,y:y};
		},

		/* EVENTS */

		mousePosition : function(e) {
			if (this.frame.event) {
				e = this.frame.event;
				var b = this.frame.document.body;
				return {
					x : e.clientX + b.scrollLeft,
					y : e.clientY + b.scrollTop
				};
			}
			else if (e) return {x:e.pageX,y:e.pageY};
			else return null;
		},

		mouseFrom : function(e) {
			var r = e.relatedTarget;
			if (r) {
				try {
					r.nodeName;
				}
				catch (e) {
					_js.debug("Caught Error : invalid node in mouseFrom related target");
					r = this.document;
				}
				return r;
			}
			if (window.event && window.event.fromElement) return window.event.fromElement;
			else return null;
		},
		mouseTo : function(e) {
			var r = e.relatedTarget;
			if (r) {
				try {
					r.nodeName;
				}
				catch (e) {
					this.debug("Caught Error : invalid node in mouseTo related target");
					r = this.document;
				}
				return r;
			}
			if (window.event && window.event.toElement) return window.event.toElement;
			else return null;
		},
		
		cancelEvent : function(e) {
			e.cancelBubble = true;
			e.returnValue = false;
			if (e.stopPropagation) e.stopPropagation();
			if (e.preventDefault) e.preventDefault();
			return false;
		},
		
		addEvent : function (o,e,h,p) {
			this.walk(o,function(i){
				if (i.addEventListener) i.addEventListener(e,h,(p==null)?false:p);
				else if (i.attachEvent) i.attachEvent("on"+e,h);
			});
			return o;
			/*
			if (o.length) {
				this.walk(o,function(i){
					t.addEvent(i,e,h,b);
				});
			}
			else {
				if(o.addEventListener) o.addEventListener(e,h,(b!=null)?b:true);
				else if(o.attachEvent) o.attachEvent("on"+e,h);
			}*/
		},
		
		removeEvent : function (o,e,h,p) {
			this.walk(o,function(i){
				if (i.removeEventListener) i.removeEventListener(e,h,(p==null)?false:p);
				else if (i.detachEvent) i.detachEvent("on"+e,h);
			});
			return o;
			/*
			var t = this;
			if (o.isArray()) {
				o.walk(function(i){
					t.removeEvent(i,e,h,b);
				});
			}
			else {
				if(o.removeEventListener) o.removeEventListener(e,h,(b!=null)?b:true);
				else if(o.detachEvent) o.detachEvent("on"+e,h);
			}
			return o;*/
		},
		
		eventTarget : function(e) {
			var t = (e && e.target)? e.target : this.frame.event.srcElement;
			return (t.nodeType == 3)? t.parentNode:t; // skip text nodes

			/*if (/webkit/i.test(navigator.userAgent)) {
				var t = e.target;
				return (t.nodeType == 3)? t.parentNode:t; // skip text nodes
			}
			if (e && e.target) return e.target;
			if (this.frame.event) return this.frame.event.srcElement;*/
		},
		
		
		fadeIn : function(o,fn,inc,limit,s) {
			//alert('fadein');
			this.opacity(o,0);
			this.visible(o,true);
			if (limit==null) limit = 1;
			if (inc==null) inc = 0.1;
			if (s==null) s = 30;
			this.fadeStep(o,fn,inc,limit,s);
		},
		fadeOut : function(o,fn,inc,limit,s) {
			this.opacity(o,1);
			this.visible(o,true);
			if (limit==null) limit = 0;
			if (inc==null) inc = -0.1;
			if (s==null) s = 30;
			this.fadeStep(o,fn,inc,limit,s);
		},
		fadeStep : function(o,fn,inc,limit,s) {
			//alert('o='+o._opacity+' inc='+inc+'l='+limit+'s='+s+' n=');
			var n = o._opacity+inc;
			
			this.opacity(o,n);
			
			//alert('step n='+n);
			var t = this;
			if ((inc>0 && n<limit) || (inc<0 && n>limit)) {
				o.fadeTimer = setTimeout(function() {
					t.fadeStep(o,fn,inc,limit,s);
				},s);
			}
			else if (fn) fn();
		}
	};

	// make the default UnderscoreJS object for the current window/document
	var _js = new UnderscoreJS(window,document);
	
	// unofficially you can also use _ to refer to the object
	var _ = _js;
}
