/**
 * Workaround for creating elements with a name attribute in Internet Explorer.
 */
if (document.createNamedElement == undefined) {
	document.createNamedElement = (function () {
		try {
			var elm = document.createElement('<a name="test">');
			if (elm.tagName == 'A' && elm.name == 'test') {
				return function (type, name) {
					return document.createElement('<'+type+' name="'+name+'">');
				};
			}
		}
		catch (e) {}

		return function (type, name) {
			var element = document.createElement(type);
			element.setAttribute('name', name);
			return element;
		}
	})();
}

if (!Array.prototype.each) {
	Array.prototype.each = function (fn) {
		for (var i = 0; i < this.length; i++) {
			fn(this[i]);
		}
	};
}

if (!Array.prototype.concat) {
	Array.prototype.concat = function () {
		var len = arguments.length;
		for (var i = 0; i < len; i++) {
			if ((arguments[i] instanceof Array) == false) {
				this.push(arguments[i]);
				continue;
			}
			
			var ilen = arguments[i].length;
			for (var j = 0; j < ilen; j++) {
				this.push(arguments[i][j]);
			}
		}
	}
}

if (!Array.fromObject) {
	Array.fromObject = function (obj) {
		if (obj instanceof Array) return obj;
		else if (obj.length != undefined) {
			var a = [];
			var len = obj.length;
			for (var i = 0; i < len; i++) {
				a.push(obj[i]);
			}
			return a;
		}
		else {
			var a = [];
			for (var i in obj) {
				if (obj.hasOwnProperty(i)) a.push(obj[i]);
			}
			return a;
		}
		
		return null;
	};
}

var fDOM = (function () {
	var pub = {};
	var strict = false;
	var allowedChildren = {
		inline: 'span em strong',
		block: 'div p ul ol',
		ul: 'li',
		ol: 'li'
	};
	var emptyElements = '|area|br|col|hr|img|input|';

	function NotAllowedChildException (parentType, childType) {
		this.parentType = parentType;
		this.childType = childType;
	}
	
	/**
	 * Checks if a certain HTMLElement is allowed according to the HTML 4.01 specification to
	 * be a child element of the specified parent element type.
	 * @private
	 * @static
	 * @param string type			The type of the parent element, should be a lower case element name.
	 * @param HTMLElement child		The child element to validate.
	 * @return boolean	TRUE if the child is allowed to be a child of the parent element type,
	 * 					FALSE if not.
	 */
	function isAllowedChild (type, child) {
		var allowed = allowedChildren[type];

		if (strict && allowed != undefined) {
			// Set up regular expression for the child element to search for
			var re = new RegExp('\\b' + child.nodeName.toLowerCase() + '\\b');

			// Return first match of allowed element or false
			return (allowed.match(re) != null)
				|| (allowed.indexOf('inline') && allowedChildren.inline.match(re))
				|| (allowed.indexOf('block') && allowedChildren.block.match(re))
				|| false;
		}

		return true;
	}
	
	function el (type) {
		if (type == undefined) return null;
		
		var attr = ((typeof arguments[1] == 'object') && arguments[1].nodeType == undefined ?
			arguments[1] : null);
		
		// Create the element of the specified type, making sure name attribute is set when given
		var elm = (attr && attr.name ?
			document.createNamedElement(type, attr.name) : document.createElement(type));
				
		if (attr) decorate(elm, attr);
		
		// Skip processing of children on empty elements
		if (strict && emptyElements.indexOf('|'+type+'|') != -1) {
			return elm;
		}
		
		for (var i = (attr ? 2 : 1); i < arguments.length; i++) {
			if (typeof arguments[i] == 'string') {
				if (arguments[i].substr(0, 1) == '>' && arguments[i].substr(-1) == '<') {
					elm.innerHTML = arguments[i].substr(1, -1);
					continue;
				}
				arguments[i] = document.createTextNode(arguments[i]);
			}
			else if (isAllowedChild(type, arguments[i]) !== true) {
				throw new NotAllowedChildException(type, arguments[i].nodeName);
			}
		
			if (arguments[i] && arguments[i].nodeType) elm.appendChild(arguments[i]);
		}
		
		return elm;
	};
	pub.el = el;
	
	/**
	 * Adds attributes, style properties and event handlers to an element.
	 * An example of a object with all these kinds of element decorations:
	 *	{
	 *		href: 'http://somesite.com/',
	 * 		id: 'mylink',
	 *		style: {
	 *			color: '#ff0000'
	 *		},
	 *		on: {
	 *			click: myClickHandler
	 *		}
	 *	}
	 *
	 * @private
	 * @static
	 * @param HTMLElement elm	The element to decorate.
	 * @param Object attrs		The decorations, as in the example given.
	 */
	function decorate (elm, attrs, isStyle, isEvents) {
		isStyle = isStyle || false;
		isEvents = isEvents || false;
		
		for (var name in attrs) {
			if (isStyle) {
				setStyle(elm, name, attrs[name]);
			}
			else if (isEvents) {
				addListener(elm, name, attrs[name]);
			}
			else if (typeof attrs[name] == 'object') {
				decorate(elm, attrs[name], (name == 'style'), (name == 'on'));
			}
			else {
				switch (name) {
					case 'class':
						elm.className = attrs[name];
						break;
					default:
						elm.setAttribute(name, attrs[name]);
				}
			}
		}
	}
	
	/**
	 * Function abstraction for adding an event listener to an element. Uses the YUI library to
	 * handle the hookup, but you could override fDOM.addListener with a custom function provided it
	 * has the same signature.
	 * @public
	 * @static
	 * @param HTMLElement elm	The element to hook up the event listener on.
	 * @param String type		The type of event, without the 'on' prefix.
	 * @param Function fn		The function that should handle the event.
	 * @return Boolean	<code>TRUE</code> if the listener was added, <code>FALSE</code> otherwise.
	 */
	function addListener(elm, type, fn) {
		return YAHOO.util.Event.on(elm, type, fn);
	}
	pub.addListener = addListener;
	
	/**
	 * Function abstraction for setting a style property on an element. Uses the YUI library to
	 * handle the actual modification, but you could override fDOM.setStyle with a custom function provided
	 * it has the same signature.
	 * @public
	 * @static
	 * @param HTMLElement elm	The element to add the style property to.
	 * @param String name		The name of the CSS property.
	 * @param String val		The value of the CSS property, complete with unit value when relevant.
	 */
	function setStyle (elm, name, val) { YAHOO.util.Dom.setStyle(elm, name, val) }
	pub.setStyle = setStyle;
	
	/**
	 * Sets strict mode on or off in fDOM. With strict mode on, you will only be allowed to nest
	 * elements as allowed by the HTML4.01 specification, and you can't nest elements inside
	 * elements that are required to be empty (ie. img, br, hr). Strict mode on is only advised during
	 * development to ensure healthy HTML is produced. The default setting is off.
	 * @public
	 * @static
	 * @param Boolean isStrict	<code>TRUE</code> if you want strict mode on, <code>FALSE</code> otherwise.
	 */
	function setStrict (isStrict) {
		if (typeof isStrict == 'boolean') strict = isStrict;		
	}
	pub.setStrict = setStrict;
	
	['blockquote', 'div', 'dl', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'ol', 'p', 'ul',	// Typical block
	 'a', 'abbr', 'acronym', 'address', 'br', 'caption', 'cite', 'dd', 'dl', 'dt', 'em',	// Typical inline
	 'hr', 'img', 'q', 'span', 'strong', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'].each(
		function (type) {
			pub[type] = function () { return el.apply(this, [type].concat(Array.fromObject(arguments))) };
		}
	);
	
	return pub;
})();

