JSLIB.namespace("JSLIB.dom");

/**
 * @class Provides helper methods for DOM elements.
 */
JSLIB.dom = function() {
   var 
	ua = navigator.userAgent.toLowerCase(),isOpera = (ua.indexOf('opera') > -1),isSafari = (ua.indexOf('safari') > -1),isIE = (window.ActiveXObject),
	id_counter = 0,util = JSLIB.util;
   
   return {
		// Get HTML element reference
		// elem: a string (id of element), an element, or an array of IDs
		// return HTML element reference or array of references if found, null if not found.
		get: function(elem) {
			var o,arr,i;
			// return if elem contains nothing
			if (!elem) {
				return null;
			}
			// is elem a string? if so, look up the element by ID
			if (util.isString(elem)) {
				return document.all ? document.all[elem] : document.getElementById(elem);
			}
			// is elem an array? if so, look up each array element
			else if (util.isArray(elem)) {
				arr = [];
				for (i in elem) {
					o = this.get(elem[i]);
					if (o) {
						arr[arr.length] = o;
					}
				}
				return arr;
			}
			// nothing else? assuming it is an element reference
			else {
				return elem;
			}
			return null;
		},

        // get HTML elements that match criteria
        // testfunc - an element will be passed to testfunc. the element will be included in result iff testfunc return true.
        // tag (optional) - return elements with tagName 'tag'
        // root (optional) - root element to start searching from
        // return - array of elements
        // note: for performance reason, specifies tag and root if possible.
        getElementsBy : function(testfunc, tag, root) {
            var result = [],elements,i;
            if (tag && root && root.getElementsByTagName) {
                elements = root.getElementsByTagName(tag);
            }
            else if (tag) {
                elements = document.getElementsByTagName(tag);
            }
            else {
                elements = document.getElementsByTagName("*");
            }
            
            for (i = 0; i < elements.length; i ++) {
                if (testfunc(elements[i])) {
                    result[result.length] = elements[i];
                }
            }
            
            return result;
        },
        
        // get HTML elements that has class 'className'
        // className - return element that has this className specified by this argument
        // tag (optional) - return elements with tagName 'tag'
        // root (optional) - root element to start searching from
        // return - array of elements
        // note: for performance reason, specifies tag and root if possible.
        getElementsByClassName : function(className, tag, root) {
            return this.getElementsBy(
                new Function("return JSLIB.dom.hasClass(arguments[0], '" + className + "')"), 
                tag, root);
        },

		// get the style property (not the computed one) of an elemnt
        // elem - element or name of element
        // property - css property to retrieve
        // return string
		getStyle: function(elem, property) {
			var obj = this.get(elem);
			if (!obj) {
				return null;
			}
			return obj.style[property];
		},
		
		// get the computed style property of an elemnt
		getComputedStyle: function(elem, property) {
			var dv = document.defaultView,obj = this.get(elem),value,computed;
			if (!obj) {
				return null;
			}
            if (isIE && obj.currentStyle && obj.currentStyle[property]) { // isIE to workaround broken Opera 9 currentStyle
               value = obj.currentStyle[property];
            }
            else if (dv && dv.getComputedStyle) {
               computed = dv.getComputedStyle(obj, '');
               if (computed && computed[property]) {
                    value = computed[property];
               }
            }
            else if (obj.style[property]) {
               value = obj.style[property];
            }
			return value;
		},
   
		// set the style of an element
		setStyle: function(elem, property, val) {
			var obj = this.get(elem);
			switch(property) {
			// provides a standard way to update the opacity property
			case 'opacity' :
				if (isIE && typeof obj.style.filter == 'string') { // in case not appended
					obj.style.filter = 'alpha(opacity=' + val * 100 + ')';
					if (!obj.currentStyle || !obj.currentStyle.hasLayout) {
						obj.style.zoom = 1; // when no layout or cant tell
					}
				} 
				else {
					obj.style.opacity = val;
					obj.style['-moz-opacity'] = val;
					obj.style['-khtml-opacity'] = val;
				}
				break;
			default :
				obj.style[property] = val;
				break;
			}
		},

		// get the position of an element in page coordinates. this method does not take into account any scrolling layer.
		// elem: string ID or element reference
		// return: array [x,y]
		getXY: function(elem) {
			var elm = this.get(elem),mOL,mOT,mOP;
			if (!elm) {
				return null;
			}
			mOL=elm.offsetLeft;
			mOT=elm.offsetTop;
			mOP=elm.offsetParent;
			while(mOP){
				mOL+=mOP.offsetLeft
				mOT+=mOP.offsetTop;
				mOP=mOP.offsetParent
			}
			return [mOL,mOT];
		},
      
		// get the x-coordinate of an element in page coordinates
		getX: function(el) {
			return this.getXY(el)[0];
		},

		// get the y-coordinate of an element in page coordinates
		getY: function(el) {
			return this.getXY(el)[1];
		},

		// set the xy coordinate of an abs positioned element
		setXY: function(elem, pos) {
			var elm = this.get(elem);
			if (!elm) {
				return null;
			}
			if (!util.isNull(pos[0])) {
				this.setStyle(elm, "left", pos[0]+"px");
			}
			if (!util.isNull(pos[1])) {
				this.setStyle(elm, "top", pos[1]+"px");
			}
		},

		// set the x coordinate of an abs positioned element
		setX: function(el, x) {
			this.setXY(el, [x, null]);
		},
      
		// set the y coordinate of an abs positioned element
		setY: function(el, y) {
			this.setXY(el, [null, y]);
		},

		// returns whether element has particular class
		// elem: string ID or element reference
		// return: boolean (true - hasClass, false otherwise)
		hasClass: function(elem, className) {
			var obj = this.get(elem),re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
			if (!obj) {
				return false;
			}
			return re.test(obj['className']);
		},
		
		addClass: function(elem, className) {
			var obj = this.get(elem);
			if (!obj) {
				return;
			}
			// has the class already. just return.
			if (this.hasClass(elem, className)) {
				return;
			}
			// concatenate className if there is something set.
			if (obj.className && obj.className.length > 0) {
				obj.className += " " + className;
			}
			else {
				obj.className = className;
			}
		},
		
		removeClass: function(elem, className) {
			var obj = this.get(elem),tmpClass;
			if (!obj) {
				return;
			}
			while (this.hasClass(elem, className)) {
	            tmpClass = obj['className'];
    	        obj['className'] = tmpClass.replace(className, ' ');
			}
		},
		
		replaceClass: function(elem, oldClassName, newClassName) {
			this.removeClass(elem, oldClassName);
			this.addClass(elem, newClassName);
		},

		// set an ID to the specified element if none exists
		generateId: function(elem, prefix) {
			prefix = prefix || 'JSLIB-gen-';
			var obj = this.get(elem);
			if (obj) {
				if (!obj.id) {
					obj.id = prefix + this.id_counter;
					this.id_counter ++;
				}
				return obj.id
			}
			return null;
		},
		
		isAncestor: function(haystack, needle) {
			haystack = this.get(haystack);
			needle = this.get(needle);
			
			if (!haystack || !needle) {
				return false;
			}
			
			if (this.isIE && haystack.contains) {
				return haystack.contains(needle);
			}
			else {
               var parent = needle.parentNode;
               while (parent) {
                  if (parent == haystack) {
                     return true;
                  }
                  parent = parent.parentNode;
               }
               return false;			
			}
		},
		
		inDocument: function(elem) {
			return this.isAncestor(document.documentElement, elem);
		},
		
		// Returns the height of the document.
		// @return {Int} The height of the actual document (which includes the body and its margin).
		getDocumentHeight: function() {
			var D=document,
				scrollHeight=-1,windowHeight=-1,bodyHeight=-1,mode = D.compatMode,
				marginTop = parseInt(JSLIB.dom.getStyle(D.body, 'marginTop'), 10),
				marginBottom,h;
			if (isNaN(marginTop)) {
				marginTop = 0;
			}
			marginBottom = parseInt(JSLIB.dom.getStyle(D.body, 'marginBottom'), 10);
			if (isNaN(marginBottom)) {
				marginBottom = 0;
			}

			if ( (mode || isIE) && !isOpera ) { // (IE, Gecko)
				switch (mode) {
				   case 'CSS1Compat': // Standards mode
					  scrollHeight = ((window.innerHeight && window.scrollMaxY) ?  window.innerHeight+window.scrollMaxY : -1);
					  windowHeight = [D.documentElement.clientHeight,self.innerHeight||-1].sort(function(a, b){return(a-b);})[1];
					  bodyHeight = D.body.offsetHeight + marginTop + marginBottom;
					  break;
				   
				   default: // Quirks
					  scrollHeight = D.body.scrollHeight;
					  bodyHeight = D.body.clientHeight;
				}
			} 
			else { // Safari & Opera
				scrollHeight = D.documentElement.scrollHeight;
				windowHeight = self.innerHeight;
				bodyHeight = D.documentElement.clientHeight;
			}

			h = [scrollHeight,windowHeight,bodyHeight].sort(function(a, b){return(a-b);});
			return h[2];
		},
      
		// Returns the width of the document.
		// @return {Int} The width of the actual document (which includes the body and its margin).
		getDocumentWidth: function() {
			var D=document,
				docWidth=-1,bodyWidth=-1,winWidth=-1,mode = D.compatMode,
				marginRight = parseInt(JSLIB.dom.getStyle(D.body, 'marginRight'), 10),
				marginLeft, w;
			if (isNaN(marginRight)) {
				marginRight = 0;
			}			
			marginLeft = parseInt(JSLIB.dom.getStyle(D.body, 'marginLeft'), 10);
			if (isNaN(marginLeft)) {
				marginLeft = 0;
			}
			
			if (mode || isIE) { // (IE, Gecko, Opera)
				switch (mode) {
				   case 'CSS1Compat': // Standards mode
					  docWidth = D.documentElement.clientWidth;
					  bodyWidth = D.body.offsetWidth + marginLeft + marginRight;
					  winWidth = self.innerWidth || -1;
					  break;
					  
				   default: // Quirks
					  bodyWidth = D.body.clientWidth;
					  winWidth = D.body.scrollWidth;
					  break;
				}
			} 
			else { // Safari
				docWidth = D.documentElement.clientWidth;
				bodyWidth = D.body.offsetWidth + marginLeft + marginRight;
				winWidth = self.innerWidth;
			}

			w = [docWidth,bodyWidth,winWidth].sort(function(a, b){return(a-b);});
			return w[2];
		},

		// other reference
		// http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
	  
		// Returns the current height of the viewport.
		// @return {Int} The height of the viewable area of the page (excludes scrollbars).
		getViewportHeight: function() {
			var height = -1,mode = document.compatMode;

			if ( (mode || isIE) && !isOpera ) {
				switch (mode) { // (IE, Gecko)
				   case 'CSS1Compat': // Standards mode
					  height = document.documentElement.clientHeight;
					  break;

				   default: // Quirks
					  height = document.body.clientHeight;
				}
			} 
			else { // Safari, Opera
				height = self.innerHeight;
			}

			return height;
		},
      
		// Returns the current width of the viewport.
		// @return {Int} The width of the viewable area of the page (excludes scrollbars).
		getViewportWidth: function() {
			var width = -1,mode = document.compatMode;

			if (mode || isIE) { // (IE, Gecko, Opera)
				switch (mode) {
				case 'CSS1Compat': // Standards mode 
				   width = document.documentElement.clientWidth;
				   break;
				   
				default: // Quirks
				   width = document.body.clientWidth;
				}
			} 
			else { // Safari
				width = self.innerWidth;
			}
			return width;
		},
	  
		getScrollXY: function() {
			var sx = 0, sy = 0, D=document, W=window;
			if( typeof( W.pageYOffset ) == 'number' ) {
				//Netscape compliant
				sy = W.pageYOffset;
				sx = W.pageXOffset;
			} 
			else if( D.body && ( D.body.scrollLeft || D.body.scrollTop ) ) {
				//DOM compliant
				sy = D.body.scrollTop;
				sx = D.body.scrollLeft;
			} 
			else if( D.documentElement && ( D.documentElement.scrollLeft || D.documentElement.scrollTop ) ) {
				//IE6 standards compliant mode
				sy = D.documentElement.scrollTop;
				sx = D.documentElement.scrollLeft;
			}
			return [sx, sy];
		},
	  
		createInvisibleDiv: function (id) {
			var em;
			if (id) {
				em = document.getElementById(id);
				if (em) {
					return em;
				}
			}
			em = document.createElement("div");
			if (id) {
				em.id = id;
			}
			em.style.position = "absolute";
			em.style.visibility = "hidden";
			em.style.top = "-5000px";
			em.style.left = "0px";
			em.style.width = "50px";
			em.style.height = "50px";
			em.style.overflow = "hidden";
			document.body.appendChild(em);
			return em;
		},

		preloadImage: function (url) {
			// create the image and put it in a hidden div
			var em = document.getElementById("JSLIB_preload_div"), img;
			if (!em) {
				em = JSLIB.dom.createInvisibleDiv("JSLIB_preload_div");
			}
			img = document.createElement("img");
			img.src = url;
			em.appendChild(img);
		},
		
		createScriptTag: function (src, id, charset, callback) {
			var D=document,elem,pN;
		    if (id) {
		        if (D.getElementById(id)) {
		            return;
		        }
		    }
		    elem = D.createElement("script");
		    elem.src = src;
		    if (id) {
		        elem.id = id;
		    }
		    if (charset) {
		        elem.setAttribute("charset", charset);
		    }
			if (typeof callback != "undefined") {
			    var loaded = false,func = function() {if (!loaded) {loaded=true; setTimeout(callback, 1);}};
			    elem.onload = function() { func(); };
			    elem.onreadystatechange = function() { if ((this.readyState == "loaded") || (this.readyState == "complete")) { func(); } };
			}
			pN=D.getElementsByTagName("head")[0];
			if (!pN) {
				pN = document.body;
			}
		    pN.appendChild(elem);
		},

		createStyleSheet: function (fileName, id) {
			var D=document,em,h;
		    if (id) {
		        if (D.getElementById(id)) {
		            return;
		        }
		    }
			// IE way
			if (D.createStyleSheet) {
				D.createStyleSheet(fileName);
			}
			else {
			    em = D.createElement("link");
			    em.href = fileName;
				em.rel = "stylesheet";
				em.type = "text/css";
			    if (id) {
			        em.id = id;

			    }
				h = D.getElementsByTagName("head")[0];
				if (!h) {
					h = document.documentElement;
				}
				if (h) {
					h.appendChild(em);
				}
			}
		}		

   };
}();

var JS$ = function (id) {
	return JSLIB.dom.get(id);
};