var DOM_COLSPAN = "%:%colspan%:%";

var DOM_NODE_TYPES = [1,3,9,11];									// see https://developer.mozilla.org/En/DOM/Node.nodeType for constants
var DOM_ELEMENT_TYPES = [1,9,11];
var DOM_NODE_TYPE_NAMES = [null,
	'ELEMENT_NODE','ATTRIBUTE_NODE','TEXT_NODE',
	'CDATA_SECTION_NODE','ENTITY_REFERENCE_NODE',
	'ENTITY_NODE','PROCESSING_INSTRUCTION_NODE',
	'COMMENT_NODE','DOCUMENT_NODE',
	'DOCUMENT_TYPE_NODE','NOTATION_NODE'
];

function DOM()
{
	var arglen = arguments.length;
	
	this.errors = [];
	this.type = (arglen >= 1) ? arguments[0] : null;
	this.data = (arglen >= 2) ? arguments[1] : null;
	
	this.setAttribute = DOM_SetAttribute;
	this.assignAttributes = DOM_AssignAttributes;
	this.createDocumentFragment = DOM_CreateDocumentFragment;
	this.createElement = DOM_CreateElement;
	this.elementsToFrag = DOM_ElementsToFrag;
	this.implodeToFrag = DOM_ImplodeToFrag;
	this.repeatElement = DOM_RepeatElement;
	this.isElement = DOM_IsElement;
	this.isTextNode = DOM_IsTextNode;
	this.isFrag = DOM_IsFrag;
	this.getElementsByAttribute = DOM_GetElementsByAttribute;
	this.getNodeTypeName = DOM_GetNodeTypeName;
	this.removeChildren = DOM_RemoveChildren;
	this.documentFragment = DOM_DocumentFragment;
	this.createDocumentFragmentArray = DOM_CreateDocumentFragmentArray;
	this.createDocumentFragmentFromArray = DOM_CreateDocumentFragmentFromArray;
	this.getAttributeArray = DOM_GetAttributeArray;
	this.getStyleArray = DOM_GetStyleArray;
	
	this.br = DOM_BR;
	this.p = DOM_P;
	this.div = DOM_Div;
	this.span = DOM_Span;
	this.img = DOM_Img;
	this.anchor = this.a = DOM_Anchor;
	this.label = DOM_Label;
	this.meta = DOM_Meta;
	
	this.italic = DOM_Italic;
	this.strong = DOM_Strong;

	this.form = DOM_Form;
	this.input = DOM_Input;
	this.checkbox = DOM_Checkbox;
	this.radio = DOM_Radio;
	this.formIMG = DOM_FormIMG;
	this.select = DOM_Select;
	this.option = DOM_Option;
	this.textarea = DOM_Textarea;
	this.button = DOM_Button;

	this.table = DOM_Table;
	this.tr = DOM_TR;
	this.td = DOM_TD;
	this.th = DOM_TH;

	this.h1 = DOM_H1;
	this.h2 = DOM_H2;
	this.h3 = DOM_H3;
	this.h4 = DOM_H4;
	this.h5 = DOM_H5;
	this.h6 = DOM_H6;
	
	this.createList = DOM_CreateList;
	this.ol = DOM_OL;
	this.ul = DOM_UL;
	this.li = DOM_LI;
}
   
/**
* @desc use this so you don't overwrite element attributes
*/
function DOM_SetAttribute(element, name, value)
{
	if ( element.hasAttribute(name) )
		value = element.getAttribute(name) +' '+ value;

	return(Boolean(element.setAttribute( name , value )));
}

/**
* attributes - 1 attribute with array('name', 'value') or multiple attributes with array( array('name', 'value') , array('name', 'value') )
*/

function DOM_AssignAttributes(element, attributes)
{
	if ( attributes.length == 2 && !(attributes[0] instanceof Array) )
	{
		if ( attributes[0] == 'style' )
		{
			try {
				var a, a2, arr = attributes[1].split(';');
				var i, len = arr.length;
				for (i = 0; i < len; i++)
				{
					try {
						a = arr[i].split(':');
						if ( a.length > 0 )
						{
							if ( a[0].indexOf('-') > - 1 )
							{
								a2 = a[0].split('-');
								a[0] = a2[0].trim() + a2[1].substring(0,1).toUpperCase() + a2[1].substring(1, a2[1].length).trim();
							}
							element.style[ a[0].trim() ] = a[1].trim();
						}
					} catch(e) { }
				}
			} catch(e) { }
		} else if ( attributes[0] == 'class' )
			element.className = attributes[1];
		else
			element.setAttribute(attributes[0], attributes[1]);
	} else {
		var i, len = attributes.length;
		for (i = 0; i < len; i++)
		{
			if ( attributes[i][0] == 'style' )
			{
				try {
					var a, arr = attributes[i][1].split(';');
					var x, xlen = arr.length;
					for (x = 0; x < len; x++)
					{
						try {
							a = arr[x].split(':');
							if ( a.length > 0 )
							{
								if ( a[0].indexOf('-') > - 1 )
								{
									a2 = a[0].split('-');
									a[0] = a2[0].trim() + a2[1].substring(0,1).toUpperCase() + a2[1].substring(1, a2[1].length).trim();
								}
								element.style[ a[0].trim() ] = a[1].trim();
							}
						} catch(e) { }
					}
				} catch(e) { }
			} else if ( attributes[i][0] == 'class' )
				element.className = attributes[i][1];
			else
				element.setAttribute(attributes[i][0], attributes[i][1]);
		}
	}
}

function DOM_GetNodeTypeName( node_type )
{
	if ( DOM_NODE_TYPE_NAMES[node_type] )
		return( DOM_NODE_TYPE_NAMES[node_type] );
	else
		return( false );
}

/**
* returns an array of styles
*
* @param frag the frag to read styles from
* @returns array of frag styles [ {'name':name,'value':value} ]
**/
function DOM_GetStyleArray( frag )
{
	if ( !this.isElement(frag) )
		return( [] );
	
	var arr = [], strarr = [];
	var styles = [
		'width','height','left','top','border','border-top','border-bottom','border-left','border-right','padding','padding-top','padding-bottom','padding-left','padding-right','background-color',
		'color','border-color','font-size','font-family','font-weight','line-height','text-decoration','outline','position','display','background-image','background-position','background-repeat',
		'border-size','background-attachment','border-bottom-color','border-top-color','border-left-color','border-right-color','clear','cursor','float','visibility','max-height','max-width',
		'min-width','min-height','font','font-size-adjust','font-stretch','font-variant','content','list-style','list-style-type','list-style-image','list-style-position','margin','margin-top',
		'margin-bottom','margin-right','margin-left','outline-color','outline-style','outline-width','clip','overflow','right','vertical-align','z-index','border-collapse','border-spacing',
		'caption-side','empty-cells','table-layout','direction','letter-spacing','text-indet','text-transform','white-space','word-spacing'
	];
	
	var style, x, xlen, str;
	var i, len = styles.length;
	
	for (i = 0; i < len; i++)
	{
		style = styles[i];
		if ( style.indexOf('-') > -1 )
		{
			style = style.split('-');
			xlen = style.length;
			str = '';
			
			for (x = 0; x < xlen; x++)
				str += (str != '') ? style[x].substring(0,1).toUpperCase() + style[x].substring(1,style[x].length) : style[x];
			
			style = str;
		}
		if ( frag.style && frag.style[style] != undefined && !empty(frag.style[style]) && strarr.hasValue(style) === false )
		{
			arr.push({
				'name':style ,
				'value':(getStyle(frag,style) || frag.style[style])
			});
			strarr.push( style );
		}
	}
	
	return( arr );
}

/**
* returns an array of attributes
*
* @param frag the frag to read attributes from
* @returns array of frag attributes [ {'name':name,'value':value} ]
**/
function DOM_GetAttributeArray( frag )
{
	if ( !this.isElement(frag) )
		return( [] );
	
	var arr = [], strarr = [];
	var chkattr = ['src','href','title','alt','name','id','className','offsetLeft','offsetTop','offsetWidth','offsetHeight'];
	var i, len = chkattr.length;
	
	for (i = 0; i < len; i++)
	{
		if ( frag[ chkattr[i] ] != undefined && !empty(frag[ chkattr[i] ]) && strarr.hasValue( chkattr[i] ) === false )
		{
			arr.push({
				'name':chkattr[i] ,
				'value':frag[ chkattr[i] ]
			});
			strarr.push( chkattr[i] );
		}
	}
	
	return( arr );
}

function DOM_CreateDocumentFragmentArray( frag , arr )
{
	if ( !frag || !this.isElement(frag) )
		return( [] );
	
	if ( !arr ) arr = [];
	
	var obj = {
		'nodeType':frag.nodeType ,
		'nodeTypeName':this.getNodeTypeName(frag.nodeType) ,
		'nodeName':frag.nodeName , 
		'attributes':this.getAttributeArray(frag) , 
		'styles':this.getStyleArray(frag)
	};
	
	if ( this.isTextNode(frag) )
		obj.textData = frag.nodeValue;

	var child, childarr = [];
	var i, len = frag.childNodes.length;
	for (i = 0; i < len; i++)
	{
		child = this.createDocumentFragmentArray(frag.childNodes[i]);
		if ( child.length > 0 )
			childarr = childarr.concat( child );
	}
	
	if ( this.isTextNode(frag) && frag.nodeValue.trim() == '' && childarr.length == 0 )
		return( [] );
	
	obj.childNodes = childarr;
	
	arr.push( obj );
	return( arr );
}

function DOM_CreateDocumentFragmentFromArray( arr )
{
	var i, len = arr.length;
	var x, xlen;
	
	var node, frag = this.createDocumentFragment();
	
	for (i = 0; i < len; i++)
	{
		node = true;
	}
}

function DOM_CreateDocumentFragment()
{
	var frag = document.createDocumentFragment();
	if ( arguments.length > 0 )
	{
		var i, arglen = arguments.length;
		for ( i = 0; i < arglen; i++ )
		{
			if ( this.isElement(arguments[i]) )
				frag.appendChild( arguments[i] );
			else if ( typeof arguments[i] == "string" )
				frag.appendChild( document.createTextNode( unescape(arguments[i]) ) );
		}
	}
	return( frag );
}

function DOM_CreateElement(name, data, attributes)
{
	if ( !data )
	{
		var node = document.createElement(name);

		if ( attributes )
			this.assignAttributes( node , attributes );

		return( node );
	} else if ( typeof data == "string" ) {
		var node = document.createElement(name);
		node.appendChild( document.createTextNode( unescape(data) ) );

		if ( attributes )
			this.assignAttributes( node , attributes );

		return( node );
	}
	
	var nonacceptedtypes = ["string","number","function","array","undefined"];
	
	var element = document.createElement(name);

	if ( data instanceof DOM )
		element.appendChild( data.documentFragment() );
	else if ( data && nonacceptedtypes.hasValue(typeof(data)) )                                                // strings, numbers, etc.
		element.appendChild( document.createTextNode( unescape(data) ) );
	else if ( typeof data == "array" )
	{
		var i, len = data.length;
		for (i = 0; i < len; i++)
		{
			if ( data[i] instanceof DOM )
				element.appendChild( data[i].documentFragment() );
			else if ( !nonacceptedtypes.hasValue(typeof(data[i])) )
				element.appendChild( document.createTextNode( unescape(data[i]) ) );
			else
			{
				try {
					if ( this.isElement(data[i]) && ( !this.isFrag(data[i]) || data[i].hasChildNodes() ) )
						element.appendChild( data[i] );                                                    // catch anything else (elements, nodes, frags, etc) - frags cannot be empty
				} catch(e) {
					this.errors.push( e );
				}
			}
		}
	} else {
		try {
			if ( this.isElement(data) && ( !this.isFrag(data) || data.hasChildNodes() ) )
				element.appendChild( data );
		} catch(e) {
			this.errors.push( e );
		}
	}

	if ( attributes )
		this.assignAttributes( element , attributes );

	return( element );
}

/**
* @param elements : array of elements
* @param type : string representing the type of element or NULL
*         - if the value of the array index isn't an element, createTextNode into an element of type and append that
*/
function DOM_ElementsToFrag(elements, type)
{
	var frag = this.createDocumentFragment();
	var len = elements.length;
	
	for ( i = 0; i < len; i++ )
	{
		if ( this.isElement(elements[i]) )
			frag.appendChild( elements[i] );
		else if ( type )
			frag.appendChild( this.createElement(type, elements[i]) );
	}
	return( frag );
}

/**
* @param elements : array of elements
* @param delim : the delimiter, may be omited
* @param type : the element type, may be omited
**/
function DOM_ImplodeToFrag(elements, delim, type)
{
	var frag = this.createDocumentFragment();

	var hav_delim = Boolean(delim);
	var len = elements.length;

	do {
		if ( this.isElement( (node = elements.pop()) ) )
			frag.appendChild( node );
		else if ( type )
			frag.appendChild( this.createElement(type, node) );

		if ( --len > 0 && hav_delim )
			frag.appendChild( delim.cloneNode(true) );
	} while ( len > 0 );

	if ( !frag.hasChildNodes() )
		frag.appendChild( document.createTextNode('') );

	return( frag );
}

function DOM_RepeatElement(element, amnt, delim)
{
	var frag = this.createDocumentFragment();
	var i = 0;

	while ( ++i <= amnt )
	{
		frag.appendChild( element.cloneNode(true) );

		if ( delim && i + 1 <= amnt )
			frag.appendChild( delim.cloneNode(true) );
	}
	return( frag );
}

function DOM_IsElement( data )
{
	return( typeof data.nodeType != "undefined" && DOM_NODE_TYPES.hasValue(data.nodeType) !== false );
}

function DOM_IsTextNode( data )
{
	return( typeof data.nodeType != "undefined" && data.nodeType == 3 );
}

function DOM_IsFrag( data )
{
	return( typeof data.nodeType != "undefined" && data.nodeType == 11 );
}

function DOM_GetElementsByAttribute( parent , attr , val )
{
	if ( !this.isElement(parent) || !parent.hasChildNodes() )
		return( false );

	var temp = [];
	var i, len = parent.childNodes.length;

	for ( i = 0; i < len; i++ )
	{
		n = parent.childNodes.item(i);
		if ( !this.isTextNode(n) && this.isElement(n) && n.hasAttribute(attr) && n.getAttribute(attr) == val )
			temp.push( n );
	}
	return( temp );
}

function DOM_RemoveChildren( parent )
{
	if ( parent.hasChildNodes() )
	{
		var i, len = parent.childNodes.length;
		for (i = 0; i < len; i++)
			parent.removeChild( parent.childNodes.item(i) );
	}
}

/**
* @param data = array(
*        array( "Row1 Column1" , "Row1 Column2" ) ,
*        array( "Row2 Column1" , "Row2 Column2" ) ,
*        array( [ object DOM ] , "Row3 Column2" ) ,
*        dom.createElement("tr") ,
*        array( dom.createElement("td" || "th") , "Row5 Column2" )
*         array( null , "Row6 Column1 colspan2" ) ,    // <tr> <td colspan="2">text</td> </tr>
*         array( "Row7 Column1" , null )                 // <tr> <td>text</td> <td></td> </tr>
*         array( null , dom.createDocumentFragment() ) ,
*         dom.createDocumentFragment()
*     );
**/
function DOM_Table(data, attributes)
{
	if ( data )
		return( this.createElement('table', null, attributes) );

	var i, len = data.length;
	var table = this.createElement('table', null, attributes);
	var tbody = this.createElement('tbody');

	for (i = 0; i < len; i++)
		tbody.appendChild( this.tr( data[i] ) );

	table.appendChild( tbody );
	return( table );
}

function DOM_TR(data, attributes)
{
	var tr = this.createElement("tr", null, attributes);

	if ( data instanceof DOM )
		tr.appendChild( data.documentFragment() );
	else if ( this.isElement(data) )
	{
		var tag = data.nodeName.toUpperCase();
		
		if ( tag == 'TR' )
			return( data );                                                // this is a TR element. just return it, we are finished here
		else if ( tag != 'TD' && tag != 'TH' )
			tr.appendChild( this.td( data ) );                            // if it's not a table cell, append the element to a table cell
		else
			tr.appendChild( data );                                        // otherwise, just append the element
	} else if ( data instanceof Array ) {
		var colspan = 1;
		var td = false;
		var i, len = data.length;
		
		for (i = 0; i < len; i++)                                        // each array index should be a table cell (DOMElement), a string, or NULL
		{
			if ( data[i] == DOM_COLSPAN )
			{
				if ( i >= len - 1 )
				{
					var td = this.td();

					if ( colspan > 1 )
						td.setAttribute('colspan', colspan);

					tr.appendChild( td );                                // last column in the row is empty so display an empty value with the accumulated colspan thus far and skip the rest of the loop
					break;
				}
				colspan += 1;
			} else if ( this.isElement( data[i] ) ) {
				var tag = data[i].nodeName.toUpperCase();
				if ( tag != 'TD' && tag != 'TH' )
					td = this.td( data[i] );
				else                                                        // if this index is not a DOMElement, use it, but beware of bad code causing errors (anything may slip through here)
					td = data[i];
			} else
				td = this.td( data[i] );

			if ( Boolean(td) )
			{
				if ( colspan > 1 )
				{
					td.setAttribute('colspan', colspan);
					colspan = 1;
				}
				try {
					tr.appendChild( td );
				} catch(e) {
					this.errors.push( e );
				}
				td = false;
			}
		}
	} else if ( data )
		tr.appendChild( data );

	return( tr );
}

function DOM_TD(data, attributes)
{
	// temporarily like this to show functionality.  needs to be reworked
	if ( data instanceof Array )  // TODO: this needs to be reworked on a dynamic level
	{
		// data should be, array( object , "id attribute", "class names here" );
		var cell = false;
		if ( this.isElement(data[0]) )
		{
			var tagname = data[0].nodeName.toUpperCase();
			if ( tagname == 'TH' || tagname == 'TD' )
				cell = data[0];
		}

		if ( cell === false )
			cell = this.createElement('td', data[0]);

		if ( attributes )
			this.assignAttributes(cell, attributes);

		var len = count(data);
		if ( len > 1 && data[1] && strlen(data[1]) > 0 )
			cell.setAttribute('id', data[1]);

		if ( len > 2 )
			cell.setAttribute('class', data[2]);

		return( cell );
	} else
		return( this.createElement('td', data, attributes) );
}

function DOM_TH(arg, attributes)
{
	return( this.createElement('th', arg, attributes) );
}

function DOM_DocumentFragment()
{
	switch( this.type )
	{
		case 'table' :
			return( this.table( this.data ) );
			break;

		// TODO : Add more supported types

		default :
			return( null );
	}
}

function DOM_BR(numtags, attributes)
{
	if ( isNaN(numtags) )
		numtags = 1;
	
	if ( parseInt(numtags) > 1 )
	{
		var frag = this.createDocumentFragment();

		for (var i = 0; i < numtags; i++)
				frag.appendChild( this.createElement('br', null, attributes) );

		return( frag );
	} else
		return( this.createElement('br', null, attributes) );
}

function DOM_P(arg, attributes)
{
	return( this.createElement('p', arg, attributes) );
}

function DOM_H1(arg, attributes)
{
	return( this.createElement('h1', arg, attributes) );
}

function DOM_H2(arg, attributes)
{
	return( this.createElement('h2', arg, attributes) );
}

function DOM_H3(arg, attributes)
{
	return( this.createElement('h3', arg, attributes) );
}

function DOM_H4(arg, attributes)
{
	return( this.createElement('h4', arg, attributes) );
}

function DOM_H5(arg, attributes)
{
	return( this.createElement('h5', arg, attributes) );
}

function DOM_H6(arg, attributes)
{
	return( this.createElement('h6', arg, attributes) );
}

/**
* @param type = string('ul' || 'ol')
* @param arg = array of list-elements or NULL
*
* @returns ul or ol element frag
*/
function DOM_CreateList(type, arg, attributes)
{
	var type = type.toLowerCase();
	if ( type != 'ul' && type != 'ol' )
		return( null );

	var list = this.createElement(type, null, attributes);

	if ( arg instanceof Array )
		list.appendChild( this.elementsToFrag(arg, 'li') );

	return( list );
}

/**
* @param arg : array of data, may be omitted
**/
function DOM_OL(arg, attributes)
{
	return( this.createList('ol', arg, attributes) );
}

/**
* @param arg : array of data, may be omitted
**/
function DOM_UL(arg, attributes)
{
	return( this.createList('ul', arg, attributes) );
}

function DOM_LI(arg, attributes)
{
	return( this.createElement('li', arg, attributes) );
}

function DOM_Span(arg, attributes)
{
	return( this.createElement('span', arg, attributes) );
}

function DOM_Div(arg, attributes)
{
	return( this.createElement('div', arg, attributes) );
}

function DOM_Img(src, alt, width, height, attributes)
{
	if ( !alt )
		alt = '';
	
	var img = this.createElement('img', null, attributes);
	img.src = src;

	img.setAttribute('alt', alt);        // since alt is required via w3c standards, it comes before width / height (in the arg list) since those may be styled via css

	if ( !isNaN(width) )
		img.setAttribute('width', width);

	if ( !isNaN(height) )
		img.setAttribute('height', height);

	return( img );
}

function DOM_Anchor(href, title, arg, attributes)
{
	if ( !href )
		return( this.createElement('a', null, attributes) );

	var a = this.createElement('a', arg, attributes);
	a.setAttribute('href', href);

	if ( title != null )
		a.setAttribute('title', title);

	return( a );
}

function DOM_Strong(arg, attributes)
{
	return( this.createElement('strong', arg, attributes) );
}

function DOM_Italic(arg, attributes)
{
	var span = this.span(arg, attributes);
	span.setAttribute('style', 'font-style:oblique;');
	return( span );
}

function DOM_Form(action, method, arg, attributes)
{
	// arg is expected to be a document fragment

	if ( !method )
		method = 'post';

	var node = this.createElement('form', arg, attributes);
	node.setAttribute('action', action);
	node.setAttribute('method', method);

	return( node );
}

function DOM_Input(type, name, value, attributes)
{
	var node = this.createElement('input', null, attributes);
	node.type = type;
	
	node.setAttribute('name', name);

	if ( value != null )
		node.value = value;

	return( node );
}

function DOM_Checkbox(name, value, checked, attributes)
{
	if ( isNaN(value) )
		value = 1;
	
	var node = this.input('checkbox', name, value, attributes);

	if ( Boolean(checked) )
		node.setAttribute('checked', 'checked');

	return( node );
}

function DOM_Radio(name, value, checked, attributes)
{
	if ( isNaN(value) )
		value = 1;
	
	var node = this.input('radio', name, value, attributes);

	if ( Boolean(checked) )
		node.setAttribute('checked', 'checked');

	return( node );
}

function DOM_FormIMG(src, name, attributes)
{
	if ( !name )
		name = 'btnImage';
	
	var node = this.input('image', name, null, attributes);
	node.setAttribute('src', src);

	return( node );
}

function DOM_Select(name, options, selected, multiple, attributes)
{
	// options can be a node or an array
	// selected can be an array, int, string
	// if multiple is other than false, it should be an integer

	var node = this.createElement('select', null, attributes);
	node.setAttribute('name', name);

	if ( !isNaN(multiple) && multiple !== false )
	{
		node.setAttribute('multiple', 'multiple');
		node.setAttribute('size', parseInt(multiple));
	}

	if ( options instanceof Array )
	{
		var i, len = options.length;
		for (i = 0; i < len; i++)
		{
			if ( options[i] instanceof Object )
			{
				// should be associative
				if ( typeof options[i].selected == 'undefined' )
					options[i].selected = false;

				if ( typeof options[i].value == 'undefined' )
					options[i].value = null;

				node.options[node.options.length] = this.option(options[i].text, options[i].value, Boolean(options[i].selected));
			} else
				node.options[node.options.length] = this.option(options[i]);
		}
	} else if ( options )
		node.appendChild( this.option(options) );

	return( node );
}

function DOM_Option(strText, value, selected, attributes)
{
	if ( value )
		var node = new Option(strText, value);
	else
		var node = new Option(strText, strText);
	
	if ( Boolean(selected) )
		node.selected = true;

	return( node );
}

function DOM_Textarea(name, rows, cols, value, attributes)
{
	var node = this.createElement('textarea', value, attributes);
	node.setAttribute('name', name);
	node.setAttribute('rows', rows);
	node.setAttribute('cols', cols);
	return( node );
}

function DOM_Button(arg, attributes)
{
	return( this.createElement('button', arg, attributes) );
}

function DOM_Label(arg, attributes)
{
	return( this.createElement('label', arg, attributes) );
}

function DOM_Meta(attributes)
{
	return( this.createElement('meta', null, attributes) );
}
