
/*
 JUG Core library

 Copyright (c) 2010, Simon Wiseman
 
 This software is provided 'as-is', without any express or implied
 warranty. In no event will the authors be held liable for any damages
 arising from the use of this software.
 
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:
 
    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.
 
    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.
 
    3. This notice may not be removed or altered from any source distribution.
*/

//==============================================================================================================
//	JugLog - function for sending a message to the debug console
//==============================================================================================================

function JugLog( 
	message		// string to be output to debug console if one is available
	)
{
	if( window.console )
	{
		// Safari or IE with console
		window.console.log( message );
	}
	else
	{
//		alert( "IE log: " + message );
	}
}

//==============================================================================================================
//	JugAddEvent - function for adding an event handler to an object
//==============================================================================================================

// function JugAddEvent( 
//		object, 		// object whose event handler is to be set
//		eventName, 		// DOM name of event to set
//		eventHandler 	// function to set as event handler 
//		)

var JugAddEvent;	// next this is set to the appropriate function object depending on the kind of browser

if( window.addEventListener )
{
	// DOM
	JugAddEvent = function( object, eventName, eventHandler )
	{
		object.addEventListener( eventName, eventHandler, true );
	}
}
else if( window.attachEvent )
{
	// Explorer
	JugAddEvent = function( object, eventName, eventHandler )
	{
		object.attachEvent('on' + eventName, eventHandler);
	}
}
else
{
	JugAddEvent = function( object, eventName, eventHandler )
	{
		object.onload = eventHandler;
	}
}

//==============================================================================================================
//	HTTP - class for making HTTP requests
//		::Get - make a GET request, returning the response text
//		::GetAsync - make a GET request, asynchronously passing the response text to a completion routine
//		::Post - make a POST request, returning the response text
//		::PostAsync - make a POST request, asynchronously passing the response text to a completion routine
//==============================================================================================================

function HTTP()
{
	// Constructor - allocates an HTTP Request object
	if( window.XMLHttpRequest ) 
	{
		this.Request = new XMLHttpRequest();
	} 
	else if( window.ActiveXObject ) 
	{
		this.Request = new ActiveXObject("Microsoft.XMLHTTP");
	}
	else
	{
		throw new Error( "Cannot create HTTP object" );
	}
}

// Synchronous Get
HTTP.prototype.Get = function( 
	url 		// string URL of page to fetch
	// RETURNS		string containing fetched data
	)
{
	this.Request.open( "GET", url, false );
	this.Request.send("");
	return this.Request.responseText;
}

// Asynchronous Get
HTTP.prototype.GetAsync = function( 
	url, 		// string URL of page to fetch
	completion	// function(text) that is called asychronously once data is loaded
				//	function is given fetched data as a parameter
	)
{
	this.Request.onreadystatechange = function()
	{
		if( this.readyState == 4 )
		{
			if( this.status == 200 )
			{
				completion( this.responseText );
			}
			else
			{
				completion( "" );
			}
		}
	}
	this.Request.open( "GET", url, true );
	this.Request.send("");
}

// Synchronous Post
HTTP.prototype.Post = function( 
	url, 			// string URL of resource to be posted
	data,			// data to be posted
	contentType 	// optional content type of posted data, by default "text/xml" is used
	// RETURNS			string containing response data
	)
{
	this.Request.open( "POST", url, false );
	if( contentType )
	{
		this.Request.setRequestHeader( "Content-Type", contentType ); 
	}
	else
	{
		this.Request.setRequestHeader( "Content-Type", "text/xml" ); 
	}
	this.Request.send(data);
	return this.Request.responseText;
}

// Asynchronous Post
HTTP.prototype.PostAsync = function( 
	url, 			// string URL of resource to be posted
	data, 			// data to be posted
	completion, 	// function(text) that is called asychronously once data is loaded
	contentType 	// optional content type of posted data, by default "text/xml" is used
	)
{
	this.Request.onreadystatechange = function()
	{
		if( this.readyState == 4 )
		{
			if( this.status == 200 )
			{
				completion( this.responseText );
			}
			else
			{
				completion();
			}
		}
	}
	this.Request.open( "POST", url, true );
	if( contentType )
	{
		this.Request.setRequestHeader( "Content-Type", contentType ); 
	}
	else
	{
		this.Request.setRequestHeader( "Content-Type", "text/xml" ); 
	}
	this.Request.send(data);
}

//==============================================================================================================
//	JugGetInstallationName - get the name of the installation, which is the path between the site's base URL and the JUG Library
//==============================================================================================================

function JugGetInstallationName()
{
	return JugGetInstallationName.Installation;
}

// Figure out the base URL of this installation and the installation name
{	
	// Find out the path of this script - this must be loaded in a SCRIPT tag as so is the last script tag currently in the document
	var scripts = document.getElementsByTagName("script");	// get all script tags
	var scriptSource = scripts[scripts.length-1].src;		// get the source URL of the last tag, ie. this one

	if( scriptSource.substring( 0, 5 ).toLowerCase() != 'http:' )
	{
		// script source is relative (presumably IE), normalise by loading an image tag src
		var url = window.location.href;
		var queryPos = url.indexOf( "?" );	// remove parameter
		var urlBase = ( queryPos == -1 ? url : url.substring( 0, queryPos ) );

		var addr = urlBase + '/../' + scriptSource;
		var img = document.createElement( 'IMG' );
		img.src = addr;
		scriptSource = img.src;
	}

	var baseURL = scriptSource.substring( 0, scriptSource.indexOf( "/JUG%20Library", 0 ) );	// get path up to JUG Library, where this script lives

	// work out the base URL of the site
	var url = window.location.href;			// the page's URL including any parameters
	var dotPos = url.indexOf( "." );		// the domain name must have a dot, so start search for slash there to avoid initial slashes
	var siteBase = url.substring( 0, url.indexOf( "/", dotPos ) );
	
	// installation name is difference between site base URL and page base URL
	JugGetInstallationName.Installation = baseURL.substring( siteBase.length + 1 );	// omit leading slash
}

//==============================================================================================================
//	JugGetVersionInfo - get version information
//==============================================================================================================

function JugGetVersionInfo()
{
	return "$Rev: 145 $";
}

//==============================================================================================================
//	JugGetUniqueId - function for generating a unique ID
//==============================================================================================================

function JugGetUniqueId( 
	prefix 			// string prefix for the unique ID to be generated
	// RETURNS		A string that is unique and starts with the given prefix
	)
{
	JugGetUniqueId.Counter += 1;
	if( prefix === undefined || prefix == "" )
	{
		return "UID" + JugGetUniqueId.Counter;
	}
	else
	{
		return prefix + JugGetUniqueId.Counter;
	}
}

JugGetUniqueId.Counter = 0;		// the number of unique IDs allocated so far

//==============================================================================================================
//	JugElem - function to find a document element or a Jug detached element that has a given ID
//==============================================================================================================

function JugElem( 
	id 	// string giving id of element to find
	// RETURNS		element with the given ID that is in the document or is a Jug detached element
	// EXCEPTION	thrown if element not found
	)
{
	var elem = document.getElementById( id );
	if( elem == null )
	{
		elem = JugElem.DetachedElements[ id ];
		if( !elem )
		{
			throw new Error( "Element " + id + " not found" );
		}
	}
	return elem;
}

JugElem.DetachedElements = {};		// map from ID of detached element to the element, recording "hidden" elements

//==============================================================================================================
//	JugDetach - function to detach an element from the DOM and remember it as a Jug detached element
//		Once detached from the DOM, the element can still be found using JugElem
//==============================================================================================================

function JugDetach( 
	element 	// the element to detach from the DOM
	)
{
	element.parentNode.removeChild( element );			// remove the element from the DOM
	JugElem.DetachedElements[ element.id ] = element;
}

//==============================================================================================================
//	JugKeepDetached - function to keep an element that is not in the DOM as a Jug detached element
//		Although not in the DOM, the element can subsequently be found using JugElem
//==============================================================================================================

function JugKeepDetached(
	element		// the element to keep as a detached object
	)
{
	if( element.id )
	{
		JugElem.DetachedElements[ element.id ] = element;
	}
	else
	{
		throw new Error( "JugKeepDetached - element has no id" );
	}
}

//=================================================================================================================
//	JugElemExists - function to check if an element with a given ID exists in the DOM or as a Jug detached element
//=================================================================================================================

function JugElemExists( 
	id 				// string giving id of element to find
	// RETURNS		whether the DOM contains an element with the given ID or there is a Jug detached element has that ID 
	)
{
	var elem = document.getElementById( id );
	if( elem == null )
	{
		elem = JugElem.DetachedElements[ id ];
		if( !elem )
		{
			return false;	// element not found
		}
	}
	return true;	// element found in document or set of detached elements
}

//=================================================================================================================
//	JugClearId - function that clears an element's ID
//		the element with the given ID may be in the DOM or be a Jug detached object
//=================================================================================================================

function JugClearId( 
	id			// string giving id of element to find
	)
{
	var elem = document.getElementById( id );
	if( elem == null )
	{
		delete JugElem.DetachedElements[ id ];
	}
	else
	{
		elem.id = "";	// clear element's id so it is no longer named in the DOM
		if( document.getElementById( id ) )
		{
			alert( "element id not changed" );
		}
	}
}

//=================================================================================================
// JugPrintMap - create a string representation of a map, of the form "{ k1 : v1, ... }"
//=================================================================================================

function JugPrintMap( 
	map 		// the map to print
	// RETURNS	string representation of the map
	)
{
	var result = "{";
	var sep = "";
	var key;
	for( key in map )
	{
		result = result + sep + key + ":" + map[ key ];
		sep = ", ";
	}
	return result + "}";
}

//=================================================================================================
// JugInvertMap - invert a map from k-to-v to produce a map from v-to-k
//=================================================================================================

function JugInvertMap( 
	map 			// map to invert
	// RETURNS		inverted map
	)
{
	var newMap = {};
	for( elem in map )
	{
		newMap[ map[ elem ] ] = elem;
	}
	return newMap;
}

//=================================================================================================
// JugKeysOfMap - return an array holding the key values of a map
//=================================================================================================

function JugKeysOfMap( 
	map 			// map whose keys are to be returned
	// RETURNS		an array holding the map's key values
	)
{
	var result = [];
	for( var k in map )
	{
		result.push( k );
	}
	return result;
}

//=================================================================================================
// JugValuesOfMap - return an array holding the values of a map
//=================================================================================================

function JugValuesOfMap( 
	map 			// map whose values are to be returned
	// RETURNS		an array holding the map's values
	)
{
	var result = [];
	for( var k in map )
	{
		result.push( map[k] );
	}
	return result;
}

//=================================================================================================
// JugJoinMaps - return a map that is the join of two given maps
//=================================================================================================

function JugJoinMaps( 
	map1, 			// first map to join
	map2 			// second map to join
	// RETURNS		a map that includes the elements from map1 and map2
	//				where a key is in both map1 and map2, the key is given the value from map2 in the result
	)
{
	var result = {};
	for( var k1 in map1 )
	{
		result[ k1 ] = map1[ k1 ];
	}
	for( var k2 in map2 )
	{
		result[ k2 ] = map2[ k2 ];
	}
	return result;
}

//=================================================================================================
// JugSetToString - return a string representation of a set of simple strings (a map from string to true)
//=================================================================================================

function JugSetToString( 
	map 			// map to convert
	// RETURNS		a string representation of the map as a ; separated list of values in the set
	)
{
	var str = '';
	var sep = '';
	for( var k in map )
	{
		if( map[ k ] )
		{
			str = str + sep + k;
			sep = ';';
		}
	}
	return str;
}

//=================================================================================================
// JugStringToSet - return a set (a map from string to true) as described by a string representation of it
//=================================================================================================

function JugStringToSet( 
	str 			// map to convert
	// RETURNS		a string representation of the map as a ; separated list of values in the set
	)
{
	var map = {};
	if( str != '' )
	{
		var elements = str.split( ';' );
		for( var i in elements )
		{
			map[ elements[ i ] ] = true;
		}
	}
	return map;
}

//=================================================================================================
// JugGetWindowArea - get size of page (may not all be visible
//=================================================================================================

function JugGetWindowArea(
	// RETURNS { integer left, integer top, integer width, integer height }
	)
{
	var w;
	var h;
	if( typeof( window.innerWidth ) == 'number' )
	{
	    // Netscape
		w = window.innerWidth;
		h = window.innerHeight;
	} 
	else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) 
	{
	    //IE 6+ in 'standards compliant mode'
		w = document.documentElement.clientWidth;
		h = document.documentElement.clientHeight;
	} 
	else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) 
	{
	    //IE 4 compatible
		w = document.body.clientWidth;
		h = document.body.clientHeight;
	}

	var x;
	var y;
	if( typeof( window.pageYOffset ) == 'number' ) 
	{
		//Netscape compliant
		x = window.pageXOffset;
		y = window.pageYOffset;
	} else if( document.body && document.body.scrollLeft !== undefined && document.body.scrollTop !== undefined )
	{
		//DOM compliant
		x = document.body.scrollLeft;
		y = document.body.scrollTop;
	} else if( document.documentElement && document.documentElement.scrollLeft !== undefined && document.documentElement.scrollTop !== undefined )
	{
		//IE6 standards compliant mode
		x = document.documentElement.scrollLeft;
		y = document.documentElement.scrollTop;
	}
	else
	{
		x = 0;
		y = 0;
	}

	return { left : x, top : y, width : w, height : h };
}

//=================================================================================================
// JugGetElementArea - get area of an element
//=================================================================================================

function JugGetElementArea( elem )
{
	// elem    integer element ID of some element
	// RETURNS { integer left, integer top, integer width, integer height } giving area of element
	if( elem.offsetParent )
	{
		// the element has a parent so its top left coordinates are relative to the those of the parent object
		var p = elem;	// walk the ancestor list, starting with the given element
		var left = 0;	// accumulate the coordinates of the top left corner
		var top = 0;
		for(;;)
		{
			left += p.offsetLeft;
			top += p.offsetTop;
			p = p.offsetParent;		// climb the ancestor tree
			if( p == null )
			{
				return { top: top, left: left, width: elem.offsetWidth, height : elem.offsetHeight };
			}
		}
	}
	else
	{
		// the element has no parent object, so its coordinates are absolute
		return { top: elem.y, left: elem.x, width: elem.width, height : elem.height };
	}
}

//=================================================================================================
// JugWithin - check if a point is within some area
//=================================================================================================

function JugWithin( point, area )
{
	return area.left <= point.x && point.x < area.left + area.width
	   	&& area.top <= point.y && point.y < area.top + area.height;
}

//=================================================================================================
// JugConstrainToPage - constrain area to page
//=================================================================================================

function JugConstrainToPage( area )
{
	// area    { integer left, integer top, integer width, integer height } describing some area
	// RETURNS { integer left, integer top, integer width, integer height } giving area trimmed to fit page
	
	var page = JugGetWindowArea();
	
	if( area.top < 0 )
	{
		area.height += area.top;	// reduce height so next test works
		area.top = 0;
	}
	if( area.top + area.height >= page.height - 1 )	
	{
		area.top = page.height - area.height - 1;
	}

	if( area.left < 0 )
	{
		area.width += area.Left;	// reduce width so next test works
		area.left = 0;
	}
	if( area.left + area.width >= page.width - 1 )
	{
		area.left = page.width - area.width - 1;
	}
	
	return area;
}

//=================================================================================================
// JugConstrainToWindow - constrain area to window
//=================================================================================================

function JugConstrainToWindow( area )
{
	// area    { integer left, integer top, integer width, integer height } describing some area
	// RETURNS { integer left, integer top, integer width, integer height } giving area trimmed to fit window
	
	var win = JugGetWindowArea();

	if( area.top < win.top )
	{
		area.height -= win.top - area.top;	// reduce height so next test works
		area.top = win.top;
	}
	if( area.top + area.height >= win.top + win.height - 20 )				// lost an extra 20 since IE counts scroll bars
	{
		area.top = win.top + win.height - area.height - 20;
	}

	if( area.left < win.left )
	{
		area.width -= win.left - area.left;	// reduce width so next test works
		area.left = win.left;
	}
	if( area.left + area.width >= win.left + win.width - 20 )
	{
		area.left = win.left + win.width - area.width - 20;
	}
	
	return area;
}

//=================================================================================================
// toHex - convert a number to a hex string representation of at least a given width
//=================================================================================================

function toHex( n, width )
{
	var h = n.toString( 16 );
	while( h.length < width )
	{
		h = "0" + h;
	}
	return h;
}

//=================================================================================================
// colourToHex - convert an rgb or rgba colour value to a hex colour value
//=================================================================================================

function colourToHex( c )
{
	function hexrgba(r,g,b,a)
	{
		return toHex( r, 2 ) + toHex( g, 2 ) + toHex( b, 2 );
	}
	function hexrgb(r,g,b)
	{
		return toHex( r, 2 ) + toHex( g, 2 ) + toHex( b, 2 );
	}
	return "#" + eval( "hex" + c );
}

//=================================================================================================
// getStyle - 
//=================================================================================================

function getStyle( elem ) 
{
    if( elem.currentStyle ) 
	{
        return elem.currentStyle;
    }
	else if( window.getComputedStyle ) 
	{
        return window.getComputedStyle( elem, "" );
    }
}

//=================================================================================================
// getStyleElement - 
//=================================================================================================

var styleMap = { "background-color":"backgroundColor", "color":"color" };

function getStyleElement( elem, cssStyle ) 
{
    if( elem.currentStyle ) 
	{
        return elem.currentStyle[ styleMap[ cssStyle ] ];
    }
	else if( window.getComputedStyle ) 
	{
        return window.getComputedStyle( elem, "" ).getPropertyValue( cssStyle );
    }
}

//=================================================================================================
// JugNumberToStringPadZero - Format a number as a string with leading zeroes
//=================================================================================================

function JugNumberToStringPadZero( value, width )
{
	var s = "" + value;
	while( s.length < width )
	{
		s = "0" + s;
	}
	return s;
}


