function HTMLBuilder() {

	var closingTags		= ["a", "b", "button", "div", "form", "h1", "h2", "h3", "i", "iframe", "label", "li", "option", "p", "pre", "select", "span", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "u", "ul"];
	var nonClosingTags	= ["br", "hr", "img", "input"];
	
	// Generate a method for each closing tag
	for( var i in closingTags ) {
	
		// Only closing tags can take tags inside them
		eval( "this." + closingTags[i] + " = function() {return MakeClosingTag( '" + closingTags[i] + "', arguments )};" );
	}
	
	// Generate a method for each non-closing tag
	for( var i in nonClosingTags ) {
		eval( "this." + nonClosingTags[i] + " = function(o) {return MakeNonClosingTag( '" + nonClosingTags[i] + "', o )};" );
	}
	
	// Custom methods (custom tags)
	this.gelButton = function(attributes, text) {
		return gelButton( attributes.id, text, attributes.onclick );	
	};
	
	this.columns = function( tableAttributes, columnObjects ) {
	
		// Creates a single row table with each member of the list a cell
		
		var cells = new Array();

		for( var i=0; i<columnObjects.length; i++ ) {
			cells[i] = this.td( columnObjects[i].attributes, columnObjects[i].content );
		}
	
		return this.table( 
			tableAttributes, 
			this.tr( cells.join("") ) 
		);
	};	
}

function EscapeHTMLEntities( text ) {

	text = text.replace( /&/g, "&amp;" );
	text = text.replace( /</g, "&lt;" );
	text = text.replace( />/g, "&gt;" );
	
	return text;
}

function UnescapeHTML( s ) {

	s = s.replace( /&lt;/g,		"<" ); 
	s = s.replace( /&gt;/g,		">" );
	s = s.replace( /&quot;/g,	"\"" );
	s = s.replace( /&nbsp;/g,	" " );
	
	return s;
}

function MakeAttributeString( attributes ) {

	// Create attribute string
	var attribute = new Array();
	for( var i in attributes ) {
	
		var name = i;
		var value = attributes[i];
		
		if( value == null ) {
			// Non key value style attribue (such as <td nowrap>)
			attribute[attribute.length] = name;
		} else {
			attribute[attribute.length] = StringFormat( "{0}=\"{1}\"", name, EscapeHTMLEntities( value.toString() ) );
		}
	}
	
	var attributeString = attribute.join(" ");
		
	// Add space if we have an attribute string
	if( attribute.length > 0 ) attributeString = " " + attributeString;
	
	return attributeString;
}

function IsObject( variable ) {
	return typeof variable == "object";
}

function IsArray( variable ) {
	return variable instanceof Array;
}

function IsObjectAndNotArray( variable ) {
	return IsObject( variable ) && !IsArray( variable );
}


function MakeClosingTag(tag, args){
	
	// The method is the same for all the HTML/XML tag methods: 
	// Pass attributes in a hash reference (using undef as a value for attributes without a value) as the first argument.
	
	// If args[o] is an object (hashtable) it becomes the parameters
	
	var attributeString = "";
	var content = new Array();
	
	if( args ) {
		// Create an attribute string if the first element is an object, but not an array
		if( IsObjectAndNotArray( args[0] ) ) {
			attributeString = MakeAttributeString( args[0] );
		}
		
		// Skip the first arg if it's an object (the attributes) and not an array
		var startpos = IsObjectAndNotArray( args[0] ) ? 1 : 0;
		
		// Join all the content together as a big string
		var multiple = false;
		
		for( var i=startpos; i<args.length; i++ ) {
			if( IsArray( args[i] ) ) {
				multiple = true;
				for( var j=0; j<args[i].length; j++ ) {
					content[content.length] = MakeClosingTag( tag, [args[i][j]]);
				}
			} else {
			
				// Normal style, just scalar content
				content[content.length] = args[i];
			}
		}
	}

	// Emit the tag
	if( multiple ) {
		return content.join("");
	} else {	
		return StringFormat( "<{0}{1}>{2}</{0}>", tag, attributeString, content.join("") );
	}
}


function MakeNonClosingTag(tag, attributes){
	return StringFormat( "<{0}{1}>", tag, MakeAttributeString( attributes ) );
}

var HTML = new HTMLBuilder();


function MakeAlphaPNG( imageUrl, width, height, attributes ) {

	// Create image attribute string
	var attributeString = MakeAttributeString( attributes );

	return StringFormat( 
		"<span style='width:{0}px;height:{1}px;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src={2})'>" + 
		"<img src='{2}' width='{0}' height='{1}' style='filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);' {3}>" + 
		"</span>",
		width, height, imageUrl, attributeString
	);			

}
 
// ----------------------------------------------------------------------------
// Button functions
// ----------------------------------------------------------------------------

function gelButton( id, text, onclick ) {

	// id is used if an event needs to be attached to the button			
	return HTML.table({cellspacing:0, cellpadding:0, id:id, "class":"gelButton", 
			onclick:		onclick, 
			onmousedown:	"this.className='gelButton_mousedown'", 
			onmouseup:		"this.className='gelButton_mouseup'", 
			onmouseover:	"this.className='gelButton_mouseover'", 
			onmouseout:		"this.className='gelButton_mouseout'",
			onselectstart:	"return false", 
			ondragstart:	"return false"},
		HTML.tr(
			HTML.td( {"class":"gelButtonLeft"} ) + 
			HTML.td( {"class":"gelButtonCenter"}, text ) + 
			HTML.td( {"class":"gelButtonRight"} )
		)
	);		
}

// ----------------------------------------------------------------------------

function ImageBorder2x2( attributes, content ) {

	// limited to the maximum size of the image, can have a nice effect fading from the top left across the middle, content background comes from the image

	// attributes object (class, imageUrl, a.leftColumnWidth, a.topRowHeight, a.rightColumnWidth, a.bottomRowHeight)

	// the class doesn't set any styling in this code, just lets the whole image border take a class for z-index etc

	with( HTML ) {
		return table( {id:attributes.id, cellpadding:0, cellspacing:0, "class":attributes["class"], style:"border-collapse: collapse"},
			tr(		// header row
				td({style:StringFormat("background-image: url({0}); background-position: top left; width:{1}px; height:{2}px", attributes.imageUrl, attributes.leftColumnWidth, attributes.topRowHeight)}) + 
				td({style:StringFormat("background-image: url({0}); background-position: top right;", attributes.imageUrl)})
			) + 
			tr(		// content and bottom row
				td({style:StringFormat("background-image: url({0}); background-position: bottom left", attributes.imageUrl)}) + 
				td({style:StringFormat("background-image: url({0}); background-position: bottom right; padding-right:{1}px; padding-bottom:{2}px", attributes.imageUrl, attributes.rightColumnWidth, attributes.bottomRowHeight)}, content )
			)
		);
	}
}

/*
function ImageBorder3x3( a, content ) {

	// can stretch to any size, needs the content to have a solid background

	// a => attributes object (id, class, imageUrl, a.leftColumnWidth, a.topRowHeight, a.rightColumnWidth, a.bottomRowHeight)

	// the class doesn't set any styling in this code, just lets the whole image border take a class for z-index etc

	var bgi = StringFormat("background-image: url({0}); background-position: ", a.imageUrl);

	with( HTML ) {
		return table( {id:a.id, cellpadding:0, cellspacing:0, "class":a["class"], style:"border-collapse: collapse"},
			tr(		// header row
				td({style:bgi + StringFormat("top left; width:{0}px; height:{1}px", a.leftColumnWidth, a.topRowHeight)}) + 
				td({style:bgi + "top center; background-repeat: repeat-x"}) + 
				td({style:bgi + "top right;"})
			) + 
			tr(		// content row
				td({style:bgi + "center left; background-repeat: repeat-y"}) + 
				td( content ) + 
				td({style:bgi + "center right; background-repeat: repeat-y"})
			)+
			tr(		// bottom row
				td({style:bgi + "bottom left"}) + 
				td({style:bgi + "bottom center; background-repeat: repeat-x"}) + 
				td({style:bgi + StringFormat("bottom right; width:{0}px; height:{1}px", a.rightColumnWidth, a.bottomRowHeight)})
			)
		);
	}
}
*/

function urlLooksLikeImage( url ) {
	if( url == null ) return false;
	return url.match( /\.(bmp|png|gif|jpeg|jpg)\s*$/i ) != null;
}

function textLooksLikeUrl( text ) {
	if( text == null ) return false;
	return text.match( new RegExp( "^\\s*(" + PROTOCOLS.join( "|" ) + "):.+", 'i' ) ) != null;
}

function embedObject( parent, url ) {

	// images are never sized, iframes take 100% of the parent's dimension

	var objectElement;
	
	if( urlLooksLikeImage(url) ) {
	
		// If url is an image add an image to the overview panel
		objectElement = document.createElement("img");
	} else {
	
		// Else make an iframe
		objectElement = document.createElement("iframe");
		objectElement.width			= "100%";
		objectElement.height		= "100%";
		objectElement.scrolling		= "auto";
		objectElement.frameBorder = 0;
	}
	
	objectElement.src = url;
	parent.appendChild( objectElement );
}

function ElementOffscreenX( element ) {

	// returns true if the element offscreen
	var width = parseInt( element.clientWidth );
	var right = parseInt( element.style.left ) + width;
	
	return ( right > parseInt( dimensions.GetDocumentWidth() ) );
}

function ElementOffscreenY( element ) {

	// returns true if the element offscreen
	var height = parseInt( element.clientHeight );
	var bottom = parseInt( element.style.top ) + height;
	
	return ( bottom > parseInt( dimensions.GetDocumentHeight() ) );
}


function ShowSprite( imageElement, spriteWidth, spriteHeight, row, column, top, left ) {

	var clipTop		= spriteHeight*row;
	var clipRight	= spriteWidth*(column+1);
	var clipBottom	= spriteHeight*(row+1);
	var clipLeft	= spriteWidth*column;
						
	imageElement.style.clip	= StringFormat("rect({0}px {1}px {2}px {3}px)", clipTop, clipRight, clipBottom, clipLeft);
	
	imageElement.style.top	= top - (spriteHeight*row);
	imageElement.style.left	= left - (spriteWidth*column);
}

function GetSelectOptionByText( selectControl, text ) {

	for( var i=0; i<selectControl.options.length; i++ ) {
		if( selectControl.options[i].text == text ) return selectControl.options[i];
	}
	
	return null;
}
