/* Silently create the mcd namespace if it does not exist */
if (typeof mcd === 'undefined') {
	var mcd = {};
}

/**
 * mcd-js DOM Utilities
 * 
 * FYI: This is a module. http://yuiblog.com/blog/2007/06/12/module-pattern/
 * 
 * @author Michael Girouard (mgirouard@mcdpartners.com)
 */
mcd.dom = function () {
	
	/**
	 * Private member declarations
	 * @private
	 */
	var _this = {
		util : {
			trimStr : function (str) {
				return str.replace(/(^\s+|\s+$)/g, '');
			}
		},
		
		/**
		 * DOM ready settings
		 * 
		 * @private
		 */
		ready : {
			
			/**
			 * Timer for WebKit polling
			 * 
			 * @private
			 */
			timer : null,
			
			/**
			 * Callback queue for DOM ready methods
			 * 
			 * @private
			 */
			queue : [],
			
			/**
			 * Runs all callbacks in the queue
			 * 
			 * This method should be explicitly executed by call() in order to
			 * resolve scope issues.
			 * 
			 * @private
			 */
			queueRunner : function () {
				if (this.timer) {
					clearInterval(this.timer);
				}
		
				for (var i = 0; i < this.queue.length; i++) {
					this.queue[i]();
				}
			},
			
			/**
			 * DOM ready initializer
			 * 
			 * @private
			 */
			init : function () {
				/* WebKit (safari, etc) */
				if (/WebKit/i.test(navigator.userAgent)) {
					_this.ready.timer = setInterval(function () {
						if (/loaded|complete/.test(document.readyState)) {
							_this.ready.queueRunner.call(_this.ready);
						}
					}, 10);
				}
		
				/* IE */
				else if ('all' in document && 'ActiveXObject' in window) {
					document.write('<script id="__ie_onload" defer src="//:"><\/script>');
					document.getElementById('__ie_onload').onreadystatechange = function () {
						if (this.readyState === 'complete') {
							_this.ready.queueRunner.call(_this.ready);
						}
					};
				}
		
				/* W3 DOM support */
				else if ('addEventListener' in document) {
					document.addEventListener('DOMContentLoaded', function () {
						_this.ready.queueRunner.call(_this.ready);
					}, false);
				}
		
				/* Everyone else */
				else {
					var oldUnload = window.onload || function () {};
					window.onload = function () {
						oldUnload();
						_this.ready.queueRunner.call(_this.ready);
					}
				}
			}
		}
	};
	
	/* Call the initializer */
	_this.ready.init();
	
	/* Public methods */
	return {
		
		/**
		 * Calls the provided method when the DOM is complete
		 * 
		 * @param {Function} callback
		 */
		ready : function (callback) {
			if (typeof callback === 'function') {
				_this.ready.queue.push(callback);
			}
		},
		
		/**
		 * Returns an element reference
		 * 
		 * @param  {String|HTMLElement} element
		 * @return {HTMLElement}
		 */
		getElement : function (element) {
			if (typeof element === 'string') {
				element = document.getElementById(element);
			}
			
			return element;
		},
		
		/**
		 * Locates nodes by their attribute values
		 * 
		 * @param  {String} attribute
		 * @param  {String} value
		 * @param  {String|HTMLElement} parent
		 * @param  {String} nodeName
		 * @return {Array}
		 */
		getElementsByAttribute : function (attribute, value, parent, nodeName, looseMatch) {
			parent     = this.getElement(parent || document);
			nodeName   = nodeName || '*';
			looseMatch = !!looseMatch;
			
			var out  = [];
			var nodeList = parent.getElementsByTagName(nodeName);
			
			for (var i = 0; i < nodeList.length; i++) {
				if (attribute === 'class') {
					var attributeValue = nodeList[i].className;
				}
				else {
					var attributeValue = nodeList[i].getAttribute(attribute);
				}
				
				if (looseMatch) {
					var isAMatch = (attributeValue.indexOf(value) > -1);
				}
				else {
					var isAMatch = (attributeValue === value);
				}
				
				if (isAMatch) {
					out.push(nodeList[i]);
				}
			}
			
			return out;
		},


		/**
		 * Locates nodes based on having attributes
		 * 
		 * @param  {String} attribute
		 * @param  {String|HTMLElement} parent
		 * @param  {String} nodeName
		 * @return {Array}
		 */
		getElementsHaveAttribute : function (attribute, parent, nodeName) {
			parent     = this.getElement(parent || document);
			nodeName   = nodeName || '*';
			
			var out  = [];
			var nodeList = parent.getElementsByTagName(nodeName);
			
			for (var i = 0; i < nodeList.length; i++) {
				var hasAttribute = new String(nodeList[i].getAttribute(attribute));
				if (!!hasAttribute && hasAttribute.length > 0) {
					out.push(nodeList[i]);
				}
			}
			
			return out;
		},
		
		/**
		 * Returns the X,Y coordinates of an element
		 * 
		 * Originally developed by PPK: http://quirksmode.org/js/findpos.html
		 * 
		 * @param  {String|HTMLElement}
		 * @return {Array}
		 */
		getPosition : function (element) {
			element = this.getElement(element);
	
			var curleft = 0;
			var curtop  = 0;
	
			if (element.offsetParent) {
				do {
					curleft += element.offsetLeft;
					curtop  += element.offsetTop;
				} 
				while (element = element.offsetParent);
			}
	
			return [ curleft, curtop ];
		},
		
		/**
		 * Sets the position of an element
		 * 
		 * This requires the element to be absolutely positioned. This can be 
		 * forced by passing 'true' to the third parameter.
		 * 
		 * @param  {String|HTMLElement}
		 * @param  {Array} position
		 * @param  {Boolean} forcePosition (optional)
		 * @return {HTMLElement}
		 */
		setPosition : function (element, position, forcePosition) {
			element = this.getElement(element);
			
			if (forcePosition) {
				element.style.position = 'absolute';
			}
			
			element.style.left = position[0] + 'px';
			element.style.top  = position[1] + 'px';
			
			return element;
		},
		
		/**
		 * Tests if an element has a class applied
		 * 
		 * @param  {String|HTMLElement} element
		 * @param  {String} className
		 * @return {Boolean}
		 */
		hasClass : function (element, className) {
			return this.getElement(element).className.match(
				new RegExp('(\\s|^)' + className + '(\\s|$)')
			);
		},
		
		/**
		 * Adds a class to an element
		 * 
		 * @param  {String|HTMLElement} element
		 * @param  {String} className
		 * @return {Bool} True if the class was applied, False otherwise.
		 */
		addClass : function (element, className) {
			element = this.getElement(element);
			
			if (!this.hasClass(element, className)) {
				element.className += ' ' + className;
				element.className = _this.util.trimStr(element.className);
				return true;
			}
			else {
				return false;
			}
		},
		
		/**
		 * Removes a class from an element
		 * 
		 * @param  {String|HTMLElement} element
		 * @param  {String} className
		 * @return {Bool} True if the class was removed, False otherwise.
		 */
		removeClass : function (element, className) {
			element = this.getElement(element);
			
			if (this.hasClass(element, className)) {
				element.className = element.className.replace(
					new RegExp('(\\s|^)' + className + '(\\s|$)'), ' '
				);
				
				element.className = _this.util.trimStr(element.className);
				return true;
			}
			else {
				return false;
			}
		},
		
		/**
		 * Toggles a class on an element
		 * 
		 * @param {String|HTMLElement} element
		 * @param {String} className
		 */
		toggleClass : function (element, className) {
			if (!this.removeClass(element, className)) {
				this.addClass(element, className);
			}
		}
		
	}
	
}();