// ajil ("agile") - (a)nother (j)avascr(i)pt (l)ibrary
var ajil = (function() {
	var buildCache = {};
	
	function build(nodeName, attributes, content) {
		var element;
		
		if(!(nodeName in buildCache)) {
			buildCache[nodeName] = document.createElement(nodeName);
		}
		
		if(nodeName == 'input' && attributes['type'] == 'radio') {
			try {
				element = document.createElement('<input type="radio" name="' + attributes['name'] + '" />');
			} catch(err) {
				element = buildCache[nodeName].cloneNode(false);
			}
		} else {
			element = buildCache[nodeName].cloneNode(false);
		}
		
		if(attributes != null) {
			for(var attribute in attributes) {
				element[attribute] = attributes[attribute];
			}
		}
		
		if(content != null) {
			if(content.constructor == Array) {
				ajil.foreach(content, function(item) {
					if(typeof(content) == 'object') {
						element.appendChild(item);
					} else {
						element.innerHTML = item;
					}
				});
			} else if(typeof(content) == 'object') {
				element.appendChild(content);
			} else {
				element.innerHTML = content;
			}
		}
		
		return element;
	}
	
	return function() {
		this.build = build;
		
		if(window.console == null) {
			window.console = { log: alert };
		}
	}
})();

ajil.prototype = {
	indexOf: function(haystack, needle) {
		if(Array.indexOf) {
			return haystack.indexOf(needle);
		}
		
		var i = 0, x = haystack.length;
		for(i; i < x; i++) {
			if(haystack[i] === needle) {
				return i;
			}
		}
		
		return -1;
	},
	
	ajax: function(method, uri, callback, postData) {
		var getXHR = function() {
			var http;
			try {
				http = new XMLHttpRequest;
				getXHR = function() {
					return new XMLHttpRequest;
				};
			}
			catch(e) {
				var msxmls = [
					'MSXML2.XMLHTTP.3.0',
					'MSXML2.XMLHTTP',
					'Microsoft.XMLHTTP'
				];
				ajil.foreach(msxmls, function(msxml) {
					try {
						http = new ActiveXObject(msxml[i]);
						getXHR = function() {
							return new ActiveXObject(msxml[i]);
						};
					}
					catch(e) {}
				});
			}
			return http;
		};
		
		function handleReadyState(o, callback) {
			if (o && o.readyState == 4 && o.status == 200) {
				if (callback) {
					callback(o);
				}
			}
		}
		
		var http = getXHR();
		http.open(method, uri, true);
		http.onreadystatechange = function () { handleReadyState(http, callback); };
		http.send(postData || null);
		return http;
	},
	
	/**
	 * Generic function with multiple ways to select elements
	 */
	get: function(selector) {
		var selectors = ajil.filter(selector.split(/(\s|,)/gi)),
		matches = [];
		
		ajil.foreach(selectors, function(selector) {
			if(selector != ',') {
				if(selector.indexOf('#') != -1) {
					matches.push(document.getElementById(selector.substring(1)));
				} else if(selector.indexOf('.') != -1) {
					matches.push(ajil.getElementsByClassName(selector.substring(1)));
				} else {
					matches.push(document.getElementsByTagName(selector));
				}
			}
		});
	},
	
	/**
	 * Searches through the DOM for elements with the specified class name and optionally
	 * filters the results by tag name and parent. Uses document.getElementsByClassName
	 * if it is available.
	 * 
	 * @param {String} className		the element class name(s) you are searching for
	 * @param {String} [tagName]		html tag to limit the results by only searching 
	 * 									for elements with that tag name
	 * @param {HTMLElement} [parent]	parent to further limit the results by only
	 * 									searching under that parent
	 * 
	 * @return {Array} an array of the elements found
	 */
	getElementsByClassName: function(className, tagName, parent) {
		var elements = [];
		
		if(document.getElementsByClassName) {
			elements = (parent || document).getElementsByClassName(className);
		} else {
			var nodes = (parent || document).getElementsByTagName(tagName || '*'),
			regex = new RegExp("(^|\\s)" + className + "(\\s|$)");
			
			ajil.foreach(nodes, function(node) {
				if(regex.test(node.className)) {
					elements[elements.length] = node;
				}
			});
		}
		
		if(tagName && tagName != '*') {
			var filteredElements = []; 
			tagName = tagName.toUpperCase();
			if(Array.filter) {
				filteredElements = Array.filter(elements, function(element) { return element.nodeName == tagName; });
			} else {
				ajil.foreach(elements, function(element) {
					if(element.nodeName == tagName) {
						filteredElements[filteredElements.length] = element;
					}
				});
			}
			return filteredElements;
		}
		return elements;
	},
	
	/**
	 * Retrieve the parent node of an element. If a node name is provided
	 * then the function recurses each parent node until it finds the
	 * appropriate node.
	 * 
	 * @param {HTMLElement} element
	 * @param {String} [nodeName]
	 * 
	 * @return {HTMLElement} the respective parent node
	 * @return null if the parent node could not be found
	 */
	getParentNode: function(element, nodeName) {
		if(nodeName) {
			nodeName = nodeName.toUpperCase();
			var parentNode = element.parentNode;
			if(parentNode.nodeName != nodeName) {
				while(parentNode.nodeName != nodeName) {
					parentNode = parentNode.parentNode;
					if(parentNode == null) {
						break;
					}
				}
			}
			return parentNode;
		}
		return element.parentNode;
	},
	
	getChildren: function(parent, nodeName) {
		var children = [],
		childNodes = parent.childNodes,
		i = 0, x = childNodes.length;
		
		for(i; i < x; i++) {
			if(childNodes[i].nodeType == 1) {
				children[children.length] = childNodes[i];
			}
		}
		
		if(nodeName) {
			nodeName = nodeName.toUpperCase();
			var r = [];
			for(i = 0, x = children.length; i < x; i++) {
				if(children[i].nodeName == nodeName) {
					r[r.length] = children[i];
				}
			}
			return r;
		}
		return children;
	},
	
	/**
	 * Removes extra whitespace from the beginning and end of a string.
	 * 
	 * @param {String} string
	 * 
	 * @return {String}
	 */
	trim: function(string) {
		return string.replace(/^\s+|\s+$/g, '');
	},
	
	/**
	 * Check if an element has a specific class name.
	 * 
	 * @param {HTMLElement} element
	 * @param {String} className
	 * 
	 * @return {Boolean}
	 */
	hasClass: function(element, className) {
		// TODO: update with regex check
		return (element.className.indexOf(className) != -1) ? true : false;
	},
	
	/**
	 * Add a class name to an element. If the element already has the
	 * class name it will not add it.
	 * 
	 * @param {HTMLElement} element
	 * @param {String} className
	 * 
	 * @return {Boolean}
	 */
	addClass: function(element, className) {
		element.className = ajil.trim(element.className);
		element.className += ((!element.className) ? className : ((element.className.indexOf(className) == -1) ? ' ' + className : ''));
		return true;
	},
	
	/**
	 * Remove a class name from an element.
	 * 
	 * @param {HTMLElement} element
	 * @param {String} className
	 * 
	 * @return {Boolean}
	 */
	removeClass: function(element, className) {
		var regex = new RegExp('(.*)\\s?(' + className + ')\\s?(.*)');
		element.className = ajil.trim(element.className.replace(regex, '$1 $3'));
		return true;
	},
	
	/**
	 * Loop through an array and call a callback function for each item.
	 * 
	 * @param {Array} items
	 * @param {Function} callback
	 */
	foreach: function(items, callback) {
		var i = 0, x = items.length;
		for(i; i < x; i++) {
			if(callback(items[i], i) === false) {
				break;
			}
		}
	},
	
	/**
	 * Filter out empty items from an array. An empty item is considered an
	 * empty string, null or undefined.
	 * 
	 * @param {Array} items
	 * 
	 * @return {Array}
	 */
	filter: function(items) {
		var filteredArray = [];
		if(Array.filter) {
			filteredArray = Array.filter(items, function(item) { return (item != '' && item != ' ' && item != null && item != undefined); });
		} else {
			ajil.foreach(items, function(item) {
				if(item != '' && item != ' ' && item != null && item != undefined) {
					filteredArray.push(item);
				}
			});
		}
		return filteredArray;
	}
};

var ajil = new ajil();