/**
 * Version: 1.0;
 * Last updated: 30/01/08;
 * 
 * DOM functions to ease interaction with the Document Object Model,(DOM). 
 * Pulling together many common dom manipulation functions and techniques 
 * into one centralised Object. It is heavily enddebted to jQuery and 
 * Professional Javascript Techniques both by John Resig;
 */
var DOM = Base.extend({

	constructor: function() {
	    return this;
	},

	getId: function( id ) {
		return document.getElementById( id );
	},

	getTags: function( name, node ) {
		var context = node || document;
		return context.getElementsByTagName( name );
	},
	
	before: function( newElem, targetElem ){
	    return targetElem.parentNode.insertBefore( newElem, targetElem );
	},
	
	/** 
	 * based on the method described by Jeremy Keith, http://adactio.com/
	 */
	after: function( newElem, targetElem  ){
	    var parent = targetElem.parentNode;
	    if( parent.lastChild === targetElem ){
	        this.append( newElem, parent );
	    }else{
    	   this.before( newElem, targetElem.nextSibling );
	    }
	},

	/**
	 * Based on Dustin Diaz' getElementsByClass();
	 * @param  elem HTMLElement    optional element to limit search type by;
	 * @param  name String         class name;
	 * @return Array               contains list of matched elements;
	 */
	getClass: function( name, node, elem ){
		var elems = new Array(),
			tags = this.getTags( elem || '*', node );
		    
		for( var i = 0, tl = tags.length; i < tl; i++ ){
			if( this.hasClass( tags[i], name ) ){
				elems.push(tags[i]);
			}
		}
		return elems;
	},

	hasClass: function( elem, name ){
		var re = new RegExp('(^|\\s)'+ name + '(\\s|$)');
		return re.test( elem.className );
	},

	addClass: function( elem, name ){
		if( ! this.hasClass( elem, name ) ){
			if( elem.className ){
				elem.className += " " + name;
			}else{
				elem.className = name;
			}
		}
		return elem;
	},

	removeClass: function( elem, name ){
		if( this.hasClass( elem, name ) ){
			// **for my ref**
			// \s   matches a single white space character, including 
			//      space, tab, form feed, line feed. Equivalent to;
			// $    matches end of input;
			// ^    matches beginning of input;
			var re = new RegExp('(^|\\s)'+ name + '(\\s|$)');
			elem.className = elem.className.replace( re, "" );
		}
		return elem;
	},

	replaceClass: function( elem, oldname, newname ){
		if( this.hasClass( elem, oldname ) ){
			elem = this.removeClass( elem, oldname );
			elem = this.addClass( elem, newname );
		}
		return elem;
	},

	/**
	 * **TO DO** get style, get computer style;
	 */
	getAttr: function( elem, name ){
		if( ! name || name.constructor !== String ){
			return null;
		}
		name = this.fixProtected( name );
		return elem[key] ? elem[key] : null;
	},
	
	getCSS: function( elem, name ){
	    //if style set as a DOM property;
	    if( elem.style[name] ){
	        return elem.style[name];
	    }
	    //else if IE...;
	    else if( elem.currentStyle ){
	        return elem.currentStyle[name];
	    }//...else if w3c, if it exists;
	    else if(    document.defaultView && 
	                document.defaultView.getComputedStyle ){
	      name = name.replace(/([A-Z])/g, "-$1");
	      name = name.toLowerCase();
	      var s = document.defaultView.getComputedStyle( elem, "" );
	      return s && s.getPropertyValue(name);
	    }else{
	       return null; 
	    }
	},

	/**
	 * resolves clashes between html attributes and 
	 * javascript keywords returning the DOM equivilent;
	 * @param   name String    name of supply attribute;
	 * @return  String         resolved DOM att. name;
	 */
	fixProtected: function( name ){
		return {
			"class": "className",
			"for": "htmlFor",
			"float": "styleFloat" //**TO DO** needs updating for IE;
		}[name] || name;
		return name;
	},

	setAttr: function( elem, name, value ){
		if( ! name || name.constructor !== String ){
			return null;
		}
		if( name === "style" ){
			return this.setCSS( elem, value );
		}
		name = this.fixProtected( name );
		if( typeof value !== 'undefined' ){
			elem[name] = value;
		}
		return elem[name];
	},

	setCSS: function( elem, styles ){
	    var re, selector, val;
		if( styles && styles.constructor === Object ){
			for( var i in styles ){
                
                val = styles[i];
                selector = i;
                
				if( styles[i].constructor === Number ){
				    // ? - preseeding char optional;
				    // i - case insensitive ;
				    // nicked from jQuery, ta Jon;   
				    re = /z-?index|font-?weight|opacity|zoom|line-?height/i;
				    ! re.test(i) ? val += "px" : val;
				}
				
				// (\w) -   matches and remembers any non-word character 
				//          the '-' in this case the next char after the -;
				// g -      searched the entire string (global);
				
				// Didn't want to go down the browser sniffing route 
				// for all the obvious reasons, and this is the best 
				// alternative I could think of;
				if( selector == "opacity" && 
				    typeof elem.style.filter !== "undefined" ){
				        elem.zoom = 1;
				        selector = "filter";
				        val = 'alpha(opacity = ' + val * 100 + ')';
				};
				
				re = /\-(\w)/g;
			    selector = i.replace( re, function( m, c, n, s ){
				                               return c.toUpperCase(); });
				                      
				selector = this.fixProtected(selector);                      
				elem.style[selector] = val;
			}
		}
		return elem;
	},

	/**
	 * creates a new dom element, inc. styles and child element;
	 * @param  el HTMLElement      ref to target element;
	 * @param  obj Object          Object literal containing 
	 *                             elem attributes,optional;
	 * @param  node String/Object  new elements child, optional;
	 * @return HTMLElement         new element;
	 */
	add: function( el, obj, node ) {
	    var elem = document.createElement( el );
		if( obj && obj.constructor === Object ){
			for( var i in obj ){
			this.setAttr( elem, i, obj[i] );
			}
		}
		if( node ){
			this.append( node, elem );
		}
		return elem;
	},
	
	remove: function( elem ){
         return  elem.parentNode.removeChild(elem);
	},

	append: function( elem, parent ){
		return parent.appendChild( this.checkText( elem ) );
	},

	checkText: function( node ){
		return node && node.constructor == String ?
			document.createTextNode( node ) : node;
	},
	
	getHeight: function( elem ){
		return parseInt( this.getCSS( elem, 'height' ) );
	},
	
	getWidth: function( elem ){
		return parseInt( this.getCSS( elem, 'width' ) );
	},
	
	fullHeight: function( elem ){
		if( this.getCSS( elem, 'display' ) != 'none' ){
			return elem.offsetHeight || this.getHeight( elem );
		};
		
		var old = this.resetCSS( elem, {
			display: 'block',
			visibility: 'hidden',
			position: 'absolute'
		});
		
		var h = elem.clientHeight || this.getHeight( elem );
		this.restoreCSS( elem, old );
		
		return h;
	},
	
	fullWidth: function( elem ){
		if( this.getCSS( elem, 'display' ) != 'none' ){
			return elem.offsetWidth || this.getWidth( elem );
		};
		
		var old = this.resetCSS( elem, {
			display: 'block',
			visibility: 'hidden',
			position: 'absolute'
		});
		
		var w = elem.clientWidth || this.getWidth( elem );
		this.restoreCSS( elem, old );
		
		return w;
	},
	
	resetCSS: function( elem, prop ){
		var old = {};
		
		for( var i in prop ){
			old[i] = elem.style[i];
			elem.style[i] = prop[i];
		};
		return old;	
	},
	
	restoreCSS: function( elem, prop ){
		for( var i in prop ){
			elem.style[i] = prop[i];
		};
	},
	
	/**
	 * returns value relative to entire page based on 
	 * PPK's find position function 
	 * http://www.quirksmode.org/js/findpos.html
	 */
	elemX: function( elem ){
	    var curleft = 0;
	    if ( elem.offsetParent ) {
		    do {
			    curleft += elem.offsetLeft;
		    } while ( elem = elem.offsetParent );
	    }
	    return curleft;
	},
	
	elemY: function( elem ){
	    var curtop = 0;
	    if ( elem.offsetParent ) {
		    do {
			    curtop += elem.offsetTop;
		    } while ( elem = elem.offsetParent );
	    }
	    return curtop;
	},
	
	/**
     * **IMPORTANT** IE6 and a standard complient DOCTYPE  
     * requires document.documentElement not document.body see,
     * http://www.quirksmode.org/js/doctypes.html
     */
	getMouseX: function( e ){
	    e = e || window.event;
    	return e.pageX || e.clientX + document.documentElement.scrollLeft;
	},

	getMouseY: function( e ){
    	e = e || window.event;
    	return e.pageY || e.clientY + document.documentElement.scrollTop;
	},
	
	getViewHeight: function(){
	
		var de = document.documentElement;
		
		return self.innerHeight || 
			( de && de.clientHeight ) ||
				document.body.clientHeight;
	},
	
	getScrollY: function(){
	
	    var de = document.documentElement;
	    
	    return self.pageYOffset ||
	        ( de && de.scrollTop ) ||
	            document.body.scrollTop;
	}
});