/* @package xcap.utils ****************************************************** */

/**
 * @fileoverview The XCAP Utils package.
 *
 * @version $Id: xcap.utils.js,v 1.1 2011-05-24 11:56:41 bjorkman Exp $
 */

/**
 * @ignore
 */
xcap.utils =
{
	// Keep for JSDoc
};

/**
 * Web application context path, made available for JavaScripts by manually
 * setting the value in a HEAD declaration.
 *
 * @type String
 */
xcap.utils.contextPath = '';

/**
 * XCAP web application user profile base path.
 *
 * @type String
 */
xcap.utils.profilePath = '';

/**
 * Set checked state of all checkboxes with a given name within the form.
 *
 * @param formId The element ID of a FORM
 * @param fieldName The name of some INPUT field array to check
 * @param checked Boolean if fields should be checked or not (unchecked)
 */
xcap.utils.checkAllCheckboxes = function(/*String*/ formId, /*string*/ fieldName, /*boolean*/ checked) 
{
    var formElement = document.getElementById(formId);

    if (formElement === null) 
    {
        return false;
    }
    else if(typeof formElement[fieldName].length == "undefined")
    {
    	var oneCheckbox = formElement[fieldName];
    	oneCheckbox.checked = checked;
    }
    else
    {
	    for (var i = 0; i < formElement[fieldName].length; i++) 
	    {
	        formElement[fieldName][i].checked = checked;
	    }
	}
};

/**
 * Enables simple form validation by form element ID.
 
 * @param source A FORM element
 * @param formElementIdsAndAlerts Array list of form field names and invalid alerts <code>{'name': 'Please enter your name!', 'id', 'Please enter a valid id!'}</code>
 *
 * @return <code>true</code> if the form validates as not having empty fields
 * @type boolean
 */
xcap.utils.validateEmptyFieldsOnSubmit = function(/*DOM Element*/ form, /*Array*/ formElementIdsAndAlerts) {

	var debug = false;
	
	var validationMessage = '';
	var isValid = true;
	
	for (formElementId in formElementIdsAndAlerts) {
	
		var formElement = form[formElementId];
		var formValue   = form[formElementId].value;
		
		if (debug) {
			debug += '\n' + formElement + ': ' + formValue;
		}
		
		formValue = xcap.utils.Trim(formValue);
		
		if ((formValue !== null || formValue !== undefined) 
				&& formValue.length === 0) {
				validationMessage += formElementIdsAndAlerts[formElementId] + '\n';
			isValid = false;
		}
	}
	
	if (debug) {
		alert(debug);
	}
	
	if (!isValid) {
		alert(validationMessage);
	}
	
	return isValid;
};

/**
 * Find the previous element that is a sibling of the supplied one.
 * 
 * @param sibling A DOM Element
 *
 * @return Previous sibling element
 * @type DOM Element
 */
xcap.utils.previousElement = function(/*DOM Element*/ sibling)
{
    if (sibling === null)
    {
        return null;
    }
    
    for (var e = sibling.previousSibling; e !== null; e = e.previousSibling)
    {
        if (e.nodeType == 1)
        {
            return e;
        }
    }
    
    return null;
};

/** 
 * Find the next element that is a sibling of the supplied one
 *
 * @param sibling A DOM Element
 *
 * @return Next sibling element
 * @type DOM Element
 */
xcap.utils.nextElement = function(/*DOM Element*/ sibling)
{
    if (sibling === null)
    {
        return null;
    }
    
    for (var e = sibling.nextSibling; e !== null; e = e.nextSibling)
    {
        if (e.nodeType == 1)
        {
            return e;
        }
    }
    
    return null;    
};

/** 
 * Get the first parent with the given class name 
 * 
 * @param child A DOM Element
 * @param className String class name
 *
 * @return Any found parent element or <code>null</code> if none.
 * @type DOM Element
 */
xcap.utils.getParentElementWithClass = function(/*DOM Element*/ child, /*String*/ className)
{
    if (child === null)
    {
        return null;
    }
    
    for (var t = child.parentNode; t !== null; t = t.parentNode)
    {
        if (t.className !== null && t.className.match('(\ |^)' + className + '(\ |$)'))
        {
            return t;
        }
    }
    return null;
};

/** 
 * Get the text of the element and all it's children in a way that should work 
 * on most browsers.
 *
 * @param element A DOM Element
 *
 * @return The text of this element and all child elements.
 * @type String
 */
xcap.utils.getTextContent = function(/*DOM Element*/ element)
{
    if (typeof element.textContent != 'undefined')
    {
        return element.textContent;
    }
    
    if (typeof element.innerText != 'undefined')
    {
        return element.innerText;
    }
    
    return '';
};

/** 
 * Sets the text of an element in a way that should work on most browsers
 *
 * @param element A DOM Element
 * @param text String text to set
 */
xcap.utils.setTextContent = function(element, text)
{
    if (typeof element.textContent != 'undefined')
    {
        element.textContent = text;
    }
    
    if (typeof element.innerText != 'undefined')
    {
        element.innerText = text;
    }
};

/** 
 * Checks an element for the existance of a specific class name. 
 *
 * @param element A DOM Element
 * @param className String the CSS class to look for
 *
 * @return <code>true</code> if the element has the specified CSS class.
 * @type boolean
 */
xcap.utils.hasClass = function(/*DOM Element*/ element, /*String*/ className)
{
    if (element.className === null)
    {
        return false;
    }
    
    var re = new RegExp('(\ |^)' + xcap.utils.escapeRegExp(className) + '(\ |$)');
    var r = element.className.match(re);
    return (r !== null);
};

/** 
 * Escape special regexp chars in the string.
 *
 * <p>Example: "<code>a|b</code>" is escaped to "<code>a\|b</code>"</p>
 *
 * @param str A string to escape
 *
 * @return A safe text to use as a constant expression in a reg exp.
 * @type String
 */
xcap.utils.escapeRegExp = function(/*String*/ str)
{
    if (str === null)
    {
        return str;
    }
    
    if (!xcap.utils.escapeRe)
    {
        var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];        
        xcap.utils.escapeRe = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
    }
    
    return str.replace(xcap.utils.escapeRe, '\\$1');
};

/**
 * Gets the position of an element.
 *
 * TODO: Document: Is this scroll view or window relative coordinates.
 *
 * @param obj A DOM Element.
 *
 * @return An array with the coordinates for [left, top].
 * @type Array
 */
xcap.utils.getPos = function(obj) 
{
    var curleft = 0;
    var curtop = 0;
    
    if (obj.offsetParent) 
    {
        curleft = obj.offsetLeft;
        curtop = obj.offsetTop;
        while ((obj = obj.offsetParent))
        {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        }
    }
    return [curleft, curtop];
};

/**
 * Get the mouse coordinates.
 *
 * TODO: Document: Is this scroll view or window relative coordinates.
 *
 * @param e A DOM Mouse Event
 *
 * @return an array with the coordinates of the mouse pointer [left, top].
 * @type Array
 */
xcap.utils.getMousePos = function(/*DOM Event*/ e)
{
    var posx = 0;
    var posy = 0;

    if (e.pageX || e.pageY)
    {
        posx = e.pageX;
        posy = e.pageY;
    } 
    else if (e.clientX || e.clientY)
    {
        posx = e.clientX + document.body.scrollLeft +
                document.documentElement.scrollLeft;
                
        posy = e.clientY + document.body.scrollTop +
                document.documentElement.scrollTop;
    }
    
    return [posx, posy];
};

/**
 * URL encode the string using the encoding best matching the encoding 
 * specified in the current <code>document</code>.
 *
 * @param str A text to encode
 *
 * @return An encoded text
 * @type String
 */
xcap.utils.encodeURIComponent = function(/*String*/ str)
{
    var enc = null;
    if (document['inputEncoding'])
    {
        enc = document['inputEncoding'];
    }
    else if (document['characterSet'])
    {
        enc = document['characterSet'];
    }       
    
    if (enc !== null && (enc.match(/iso-?8859-?1/i) || enc.match(/windows-?1252/i) || enc.match(/macroman/i)))
    {
        return escape(str);
    }
    else
    {
        return encodeURIComponent(str);
    }
};

/**
 * Scroll vertically to the end of the element.
 * 
 * @param e A DOM Element
 */
xcap.utils.scrollToEnd = function(/*DOM Element*/ e)
{
    e.scrollTop = e.scrollHeight - e.clientHeight;
};

/**
 * Get the server base URL (protocol, host and port) of an URL
 *
 * @param url An URL, if left out the <code>document</code> URL is used
 * 
 * @return The base URL, protocol, host and port number
 * @type String
 */
xcap.utils.getServerUrl = function(/*String*/ url)
{
	var url = url || document.location['href'];
	var protoSep = url.indexOf("://") + 3;
	var substr = url.substr(protoSep);
	var idx = substr.indexOf('/');
    return url.substring(0, protoSep + idx);
};
   
/**
 * Toggles a field depending on the status of a checkbox. Used to transform a 
 * checkbox to a numeric value.
 *
 * @param sourceElement A DOM Element, source for checking
 * @param targetElement A DOM Element, target for the transformed value
 */
xcap.utils.toggleCheckedToInt = function(sourceElementId, targetElementId)
{
	var sourceElement = document.getElementById(sourceElementId);
	var targetElement = document.getElementById(targetElementId);
	
	if (sourceElement.checked == 'checked' || sourceElement.checked === true)
	{
		targetElement.value = '1';
	}
	else
	{
		targetElement.value = '0';
	}
};
   
   
/**
 * Limits the length of a field for input.
 *
 * @param field The target field to limit
 * @param fieldShadow The field shadow
 * @param max Number of allowed characters
 *
 * @deprecated Use <code>{@link #enforceMaxLength}</code> instead.
 */
xcap.utils.limitCharacters = function(field, fieldShadow, max) 
{
	if (max <= 0)
	{
		return;
    }
    
	if (field.value.length > max && fieldShadow.value.length > 0)
	{
		field.value = fieldShadow.value.substring(0, max); 
	}
	else
	{
		fieldShadow.value = field.value;
    }
};

/**
 * Limits the amount of characters an element can receive by binding a checking
 * function to its <code>onkeydown</code>, <code>onkeyup</code> and 
 * <code>onkeypress</code> events, blocking further input when a max length is 
 * reached.
 *
 * <p>An optional callback function is called on each length enforcement 
 * check, passing three parameters: <code>event</code>, <code>element</code> and
 * <code>isEnforced</code>.</p>
 * 
 * <p>Prefered use, binding to an element with Behaviour:</p>
 *
 * <pre>
 * Behaviour.register({
 *    '#xcapMessageSubject' : function(e) { xcap.utils.checkMaxLength(e, 150) },
 *    '#xcapMessageText' : function (e) { xcap.utils.checkMaxLength(e, 250, 
 *       function(event, element, isEnforced)
 *       {
 *          //...Do other stuff
 *       });
 *    }
 * });
 * </pre>
 * 
 * @param element 		The DOM form element to enforce length on
 * @param maxLength 	Max character length allowed
 * @param callback		Function called on enforcement check
 */
xcap.utils.enforceMaxLength = function(/*DOM*/ element, /*int*/ maxLength, /*Function*/ callback)
{
	// Variables initialized on element binding
	
	var keyCode;

	var isEnforced;
	
	var keyUpLength = keyDownLength = 0;
	
	// Events check for enforcement
	
	element.onkeydown = function(event)
	{
		keyDownLength = element.value.split("").length;
		
		try
		{
			event = (event || window.event);
			keyCode = (event.keyCode || event.wich);
		}
		catch (err) {}
		
		// On key down check length plus new character
		if (keyUpLength + 1 > maxLength && keyCode != 8 && keyCode != 46 && keyCode != 9 && keyCode != 13 && keyCode != 45 && keyCode != 35 && keyCode != 36 && keyCode != 37 && keyCode != 39)
		{
			isEnforced = true;
		}
		else
		{
			isEnforced = false;
		}
		
		if (callback !== undefined)
		{
			try
			{
				callback(event, element, isEnforced);
			} 
			catch (err) {}
		}
		
		if (isEnforced)
		{
			element.value = element.value.substr(0, maxLength);
		}
	};
	
	element.onkeyup = function(event)
	{
		keyUpLength = element.value.split("").length;

		// On key up check on current length
		if (keyUpLength > maxLength)
		{
			element.value = element.value.substr(0, maxLength);
		}

		if (callback !== undefined)
		{
			try
			{
				callback(event, element, isEnforced);
			} 
			catch (err) {}
		}
	};
	
	element.onkeypress = function(event)
	{
		if (callback !== undefined)
		{
			try
			{
				callback(event, element, isEnforced);
			} 
			catch (err) {}
		}
		
		if (isEnforced)
		{
			return false;
		}
	};
};

/**
 * Simplified <code>MessageFormat</code>-type of text formatter for formatting
 * of small text messages in JavaScript.
 *
 * <p>Usage:</p>
 * <pre>
 *    var str = 'We found {0} foo and {1} bar.';
 *    document.write(xcap.utils.format(str, [3, 2]));
 * </pre>
 * Results in: <code>We found 3 foo and 2 bar.</code>
 *
 * @param pattern A message pattern
 * @param args Array of objects to be formatted and substituted
 *
 * @returns String
 */
xcap.utils.format = function(/*String*/ pattern, /*Array*/ args)
{
	for (var arg in args)
	{
		pattern = pattern.replace('{' + arg + '}', args[Number(arg)]);
	}
	
	return pattern;
};

/**
 * Array of english month names.
 *
 * TODO: Make this resource dependent.
 *
 * @private
 */
xcap.utils.MONTH_NAMES = new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');

/**
 * Array of english day names.
 *
 * TODO: Make this resource dependent.
 *
 * @private
 */
xcap.utils.DAY_NAMES = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');

/**
 * Pads number with zeros for NN formatting.
 *
 * @param x A number
 * @return The number padded with 0 for values 10 > x < 0
 * @type String
 */
xcap.utils.LZ = function(x) { return ( x < 0 || x > 9 ? "" : "0" ) + x; };

/**
 * These functions use the same 'format' strings as the 
 * <code>java.text.SimpleDateFormat</code> class, with minor exceptions.
 * The format string consists of the following abbreviations:
 *
 * <pre>
 * Field        | Full Form          | Short Form
 * -------------+--------------------+-----------------------
 * Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
 * Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
 *              | NNN (abbr.)        |
 * Day of Month | dd (2 digits)      | d (1 or 2 digits)
 * Day of Week  | EE (name)          | E (abbr)
 * Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
 * Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
 * Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
 * Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
 * Minute       | mm (2 digits)      | m (1 or 2 digits)
 * Second       | ss (2 digits)      | s (1 or 2 digits)
 * AM/PM        | a                  |
 *
 * NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
 * Examples:
 *  "MMM d, y" matches: January 01, 2000
 *                      Dec 1, 1900
 *                      Nov 20, 00
 *  "M/d/yy"   matches: 01/20/00
 *                      9/2/00
 *  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
 * ------------------------------------------------------------------
 * </pre>
 * 
 * Fully credited to:
 * <a href="http://www.mattkruse.com/">http://www.mattkruse.com/</a>.
 *
 * @author Matt Kruse <matt@mattkruse.com>
 *
 * @param date A <code>Date</code> object
 * @param format A date format string
 * 
 * @return A date in the output format specified.
 * @type String
 */
xcap.utils.formatDate = function(/*Date*/ date, /*String*/ format)
{
	format=format+"";
	var result="";
	var i_format=0;
	var c="";
	var token="";
	var y=date.getYear()+"";
	var M=date.getMonth()+1;
	var d=date.getDate();
	var E=date.getDay();
	var H=date.getHours();
	var m=date.getMinutes();
	var s=date.getSeconds();
	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
	
	// Convert real date parts into formatted versions
	var value=new Object();
	if (y.length < 4) {y=""+(y-0+1900);}
	value["y"]=""+y;
	value["yyyy"]=y;
	value["yy"]=y.substring(2,4);
	value["M"]=M;
	value["MM"]=xcap.utils.LZ(M);
	value["MMM"]=xcap.utils.MONTH_NAMES[M-1];
	value["NNN"]=xcap.utils.MONTH_NAMES[M+11];
	value["d"]=d;
	value["dd"]=xcap.utils.LZ(d);
	value["E"]=xcap.utils.DAY_NAMES[E+7];
	value["EE"]=xcap.utils.DAY_NAMES[E];
	value["H"]=H;
	value["HH"]=xcap.utils.LZ(H);
	if (H==0){value["h"]=12;}
	else if (H>12){value["h"]=H-12;}
	else {value["h"]=H;}
	value["hh"]=xcap.utils.LZ(value["h"]);
	if (H>11){value["K"]=H-12;} else {value["K"]=H;}
	value["k"]=H+1;
	value["KK"]=xcap.utils.LZ(value["K"]);
	value["kk"]=xcap.utils.LZ(value["k"]);
	if (H > 11) { value["a"]="PM"; }
	else { value["a"]="AM"; }
	value["m"]=m;
	value["mm"]=xcap.utils.LZ(m);
	value["s"]=s;
	value["ss"]=xcap.utils.LZ(s);
	while (i_format < format.length) {
		c=format.charAt(i_format);
		token="";
		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
			token += format.charAt(i_format++);
			}
		if (value[token] != null) { result=result + value[token]; }
		else { result=result + token; }
		}
	return result;
};

/* @class xcap.utils.ArrayList ********************************************** */

/**
 * @class Simple array list implementation.
 *
 * @author Olle Törnström olle@josh.se
 * @constructor
 *
 * @return A new <code>ArrayList</code> instance
 */
xcap.utils.ArrayList = function()
{
	this.list = [];
	
	return this;
};

/**
 * Returns the number of elements in the array list.
 */
xcap.utils.ArrayList.prototype.size = function()
{
	return this.list.length;
};

/**
 * Adds an element to the end of this list.
 * @param element The object to add
 * @return The object added.
 */
xcap.utils.ArrayList.prototype.add = function(/*Object*/ element)
{
   return this.list.push(element);
};

/**
 * Returns the element of a certain position of the list.
 * @param index The <code>int</code> index of the element to get (0 <= index < list.length).
 * @return An object or <code>undefined</code> if index is out of bounds.
 */
xcap.utils.ArrayList.prototype.elementAt = function(/*int*/ index)
{
   if (index > -1 && index < this.list.length)
      return this.list[index];
   else
      return undefined;
};

/**
 * Clears the current list.
 */
xcap.utils.ArrayList.prototype.clear = function()
{
   this.list = [];
};

/**
 * Removes the element at a certain position in the list.
 * @param index The <code>int</code> index (0 <= index < list.length)
 */
xcap.utils.ArrayList.prototype.removeAt = function(/*int*/ index)
{
	var _size = this.list.length;

	if (_size > 0 && index > -1 && index < this.list.length)
		this.list.splice(index, 1);
};

/**
 * Insert an element at a certain position in the list.
 * @param element The object to insert.
 * @param index The <code>int</code> index to insert at (0 <= index < list.length)
 * @return The new size of the list
 */
xcap.utils.ArrayList.prototype.insert = function(/*Object*/ element, /*int*/ index)
{
	if (index > -1 && index < this.list.length)
	{
		switch (index)
		{
			case 0 :
				this.list.unshift(element);
				break;
				
			case this.list.length : 
				this.list.push(element);
				break;
				
			default :
				this.list.splice(index, 0, element);
		}
	}
		
	return this.list.length;
}

/**
 * Returns the first found index of an element in the list
 * @param element 	The object to look for
 * @param start		Start index when looking, optiona defaults to 0
 * @return The <code>int</code> index or -1 if the object is not found 
 */
xcap.utils.ArrayList.prototype.indexOf = function(/*Object*/ element, /*int*/ start)
{
	var result = -1;

	start = start || 0;

	if (start > -1 && start < this.list.length)
	{
		for (var i = start; i < this.list.length; i++)
		{
			if (this.list[i] == element)
			{
				result = i;
				break;
			}
		}
	}
	
	return result;
}

/**
 * Returns the last found index of an element in the list, looking from the end
 * of the list.
 * @param element	The object to look for
 * @param start		Start index when looking, optional defaults to list.length
 * @return The <code>int</code> index or -1 if the object is not found
 */
xcap.utils.ArrayList.prototype.lastIndexOf = function(/*Object*/ element, /*int*/ start)
{
	var result = -1;
	
	start = start || this.list.length - 1;

	if (start > -1 && start < this.list.length)
	{
		for (var i = start; i >= 0; i--)
		{
			if (this.list[i] == element)
			{
				result = i;
				break;
			}
		}
	}
	
	return result;
}

/* ************************************************************************** */
/* @class xcap.utils.Map **************************************************** */
/**
 * @class Simple implementation of a String to Object Map.
 * @author Olle Törnström olle@josh.se
 * @constructor
 */
xcap.utils.Map = function()
{
	/**
	 * @ignore
	 */
	this.map = {};

	/**
	 * @ignore
	 */
	this._size = 0;
	
	return this;
};

/**
 * Returns the current map size
 */
xcap.utils.Map.prototype.size = function()
{
	return this._size;
};

/**
 * Clear all the elements out of the map.
 */
xcap.utils.Map.prototype.clear = function()
{
	this.map = {};
};

/**
 * Removes the mapping for this key, if found.
 * @param key	The <code>String</code> key of the mapping to remove.
 * @return The previously mapped value, or <code>null</code> if there was none.
 */
xcap.utils.Map.prototype.remove = function(/*String*/ key)
{
	if (this.map[key] === undefined)
		return null;
		
	var value = this.map[key][0];
	var newMap = {};

	for (var k in this.map)
	{
		if (k !== key && this.map[k][0] !== value)
		{
			newMap[String(k)] = this.map[k];
		}
	}
	
	this.map = newMap;
	this._size--;
	
	return value;
};

/**
 * Returns a <code>boolean</code> if the given key exists in this map.
 * @param key 	The <code>String</code> key to look for
 * @return <code>true</code> if the key exists otherwise <code>false</code>
 */
xcap.utils.Map.prototype.containsKey = function(/*String*/ key)
{
	return (this.map[key] !== undefined);
};

/**
 * Puts an element in the map binding it too the specified key
 * @param key 		The <code>String</code> key to bind the element to
 * @param value	The object to bind
 */
xcap.utils.Map.prototype.put = function(/*String*/ key, /*Object*/ value)
{
	if (this.map[key] === undefined)
		this._size++;
		
	this.map[String(key)] = [value];
};

/**
 * Returns the element bound in the map to the key.
 * @param key	The <code>String</code> key
 * @return The object found or <code>undefined</code> if no element is bound the the key
 */
xcap.utils.Map.prototype.get = function(/*String*/ key)
{
	if (this.map[key] !== undefined)
		return this.map[key][0];
	else
		return undefined;
};

/**
 * Returns an unordered array of the keys in this map.
 * @return Array of <code>String</code> keys
 */
xcap.utils.Map.prototype.keys = function()
{
	var result = [];
	
	for (var key in this.map)
	{
		result.push(key);
	}
	
	return result;
};

/**
 * Returns the values of this map.
 * @return Array of objects
 */
xcap.utils.Map.prototype.values = function()
{
	var result = [];
	
	for (var key in this.map)
	{
		result.push(this.map[key][0]);
	}
	
	return result;
};

/* ************************************************************************** */
/* @class xcap.utils.CachedMap ********************************************** */
/**
 * @class Simple cached, or retain count based, map of items that keeps more
 * often used items in favour of less used inside its limited capacity.
 *
 * @author Olle Törnström olle@josh.se
 * @requires xcap#extend
 * @extends xcap.utils.Map
 * @constructor
 * @param capacity	The total capacity of this map, defaults to 10.
 */
xcap.utils.CachedMap = function(/*int*/ capacity)
{
	xcap.utils.CachedMap.superConstructor.call(this);

	/**
	 * @ignore
	 */	 	
	this.capacity = capacity || 10;
	
	/**
	 * @ignore
	 */
	this.retains = {};
	
}; xcap.extend(xcap.utils.CachedMap, xcap.utils.Map);

/**
 * @overrides xcap.utils.Map#put
 * Also assures that the current capacity is not exhausted, rejecting the least
 * retained element to assure this.
 */
xcap.utils.CachedMap.prototype.put = function(/*String*/ key, /*Object*/ value)
{
	if (this.map[key] === undefined)
	{
		this._size++;
	
		if (this._size > this.capacity)
			this._truncate();
			
		this.retains[String(key)] = -1;
	}
	
	this.map[String(key)] = [value];
	this.retains[String(key)]++;
};

xcap.utils.CachedMap.prototype.get = function(/*String*/ key)
{
	if (this.map[key] !== undefined)
	{
		this.retains[key]++;
		return this.map[key][0];
	}
	else
	{
		return undefined;
	}
};

/**
 * @ignore
 * Truncates this map to fit in the current <code>capacity</code>, ejecting any
 * element or elements by their retain count, the least retained going first.
 */
xcap.utils.CachedMap.prototype._truncate = function()
{
	var reject = null;
	
	for (var key in this.retains)
	{
		if (!reject)
			reject = {'key' : key, retains : Number(this.retains[key])};

		if (reject.retains > this.retains[key])
			reject = {'key' : key, retains : Number(this.retains[key])};
	}

	var newRetains = {};

	for (var k in this.retains)
	{
		if (k !== reject.key)
			newRetains[String(k)] = this.retains[k];
	}
			
	this.retains = newRetains;
	this.remove(reject.key);
};

/* @class xcap.utils.ListCache ********************************************** */

/**
 * @class <p>Simple key-value <code>Array</code> based cache that defines a 
 * fixed capacity and caches objects based on retain count, keeping often used
 * items in the cache and expiring objects that fall out of the cache, based on
 * its capacity.</p>
 *
 * @author Olle Törnström olle@josh.se
 * @constructor
 *
 * @param capacity Number of objects allowed in the cache, defaults to 10
 * @return A new <code>ListCache</code> instance
 * @deprecated Use {@link #CachedMap} instead
 */
xcap.utils.ListCache = function(/*int*/ capacity)
{
	/**
	 * @ignore
	 */
	this.capacity = capacity || 10;

	/**
	 * @ignore
	 */
	this.size = 0;

	/**
	 * @ignore
	 */
	this.list = new Array(this.capacity);

	/**
	 * @ignore
	 */
	this.map = {};
	
	return this;
};

/**
 * Returns the capacity of this cache
 *
 * @type int
 */
xcap.utils.ListCache.prototype.getCapacity = function()
{
	return this.capacity;
};

/**
 * Returns the amount of key-value mappings in this cache.
 *
 * @type int
 */
xcap.utils.ListCache.prototype.getSize = function()
{
	return this.size;
};

/**
 * Puts an object in the cache.
 *
 * @param key <code>String</code> key mapping for the object
 * @param value An object
 * @return Any object already mapped on the passed <code>key</code>
 * @type Object
 */
xcap.utils.ListCache.prototype.put = function(/*String*/ key, /*Object*/ value)
{
	if (!key || value === undefined)
	{
		return;
	}
	
	if (this.containsKey(key))
	{
		// Must get old value unretained
		var index = this.map[key];
		var old = this.list[index].value;		
		this.list[index].value = value;
		return old;
	}

	if (this.getSize() > 0)
	{
		var newMap = {};

		// Make room in top for new object	
		this.list.unshift(null);

		// Shift out overflow
		for (var _key in this.map)
		{
			var index = this.map[_key] + 1;

			if (index < this.capacity)
			{
				newMap[_key] = index;
			}
		}
		
		this.map = newMap;
		this.list.length = this.capacity;
	}
	
	// Put item in collection
	this.list[0] = {'key' : key, 'value' : value};
	this.map[key] = 0;

	// Define new size
	this.size = (this.size < this.capacity) ? (this.size + 1) : this.size;
};

/**
 * Returns <code>true</code> if the cache contains a mapping for the key.
 *
 * @param key <code>String</code>
 * @type boolean
 */
xcap.utils.ListCache.prototype.containsKey = function(/*String*/ key)
{
	return (this.map[key] !== undefined);
};

/**
 * Returns the object found by key in the cache
 *
 * @param key <code>String</code> object key
 * @return An object from the cache
 */
xcap.utils.ListCache.prototype.get = function(/*String*/ key)
{
	var value = null;
	
	if (this.containsKey(key))
	{
		var index = this.map[key];
		value = this.list[index].value;

		if (index - 1 > 0)
		{
			var obj = this.list[index - 1];
			this.list[index - 1] = this.list[index];
			this.map[this.list[index - 1].key] = index - 1;
			
			this.list[index] = obj;
			this.map[this.list[index].key] = index;
		}
	}

	return value;
};


/**
 * @class Defines a basic observer, or a publisher/subscriber instance that
 * enables registration of listerner functions and provides an interface for
 * notification to all registererd observers.
 *
 * @author Olle Törnström olle@josh.se
 * @constructor
 */
xcap.utils.Observer = function()
{
	/**
	 * @private
	 */
	this.functions = [];
	
	/**
	 * @private
	 */
	this.contexts = [];
};

/**
 * Attaches some listener function to an Observer instance.
 * @param fn		The function to attach
 * @param observer	The observer instance to attach to
 * @param context	Optional context
 */
xcap.utils.Observer.attachTo = function(/*Function*/ fn, /*Observer*/ observer, /*Object*/ context)
{
	if (!observer)
		return;
	
	try
	{
		observer.attach(fn, context);
	} catch (err) {};
};

/**
 * Attach a function to this observer.
 * @param {Function} fn		The function to attach
 * @param {Object} context	Optional context used when the function is notified
 */
xcap.utils.Observer.prototype.attach = function(fn, context)
{
	if (fn === undefined)
		return;

	this.functions.push(fn);

	context = context || null;
	
	this.contexts.push(context);
};

/**
 * Detaches a function from this observer
 * @param fn 	The function to detach
 */
xcap.utils.Observer.prototype.detach = function(/*Function*/ fn)
{
	if (fn === undefined)
		return;

	var functions = [];
	var contexts = [];

	for (var f in this.functions)
	{
		if (this.functions[f] !== fn)
		{
			functions.push(this.functions[f]);
			contexts.push(this.contexts[f]);
		}
	}
	
	this.functions = functions;
	this.contexts = contexts;
};

/**
 * Returns <code>true</code> if the function is attached to this observer.
 * @param fn	The function to check if attached.
 * @returns <code>true</code> if attached otherwise <code>false</code>
 */
xcap.utils.Observer.prototype.isAttached = function(/*Function*/ fn)
{
	if (fn === undefined)
		return false;

	for (var f in this.functions)
	{
		if (this.functions[f] === fn)
			return true;
	}
	
	return false;
};

/**
 * Notifies all listener functions on this observer.
 * @param {Array} args		Optional arguments passed to the listener
 * @param {Object} context 	Context used when notifying, if no context is already paired with listener. Defaults to the observer self.
 */
xcap.utils.Observer.prototype.notify = function(args, context)
{
	context = context || this;
	
	for (var f in this.functions)
	{
		var passedContext = context;
		
		if (this.contexts[f])
			context = this.contexts[f];
		
		this.functions[f].call(context, args);
		
		context = passedContext;
	}
};


/**
* Changes visibility (block/none) for the element with id
* xcapToggleTarget123, where the class of the input element
* is xcapToggle123. 
*
* Supports multiple togglers and multiple toggler targets.
*  
* @param element <code>HTMLElement</code> toggler
*/
xcap.utils.ToggleElement = function(/*HTMLElement*/ element)
{
	element.onclick = function()
   	{
   		var classes = element.className.split(" ");
		
		for(i=0; i<classes.length; i++)
		{
			var re = new RegExp("xcapToggle([0-9]+)");
			
			var matcher = re.exec(classes[i]);	
			if(matcher)
			{
				var target = document.getElementById("xcapToggleTarget" + RegExp.$1);
		
				if(target.style.display == "block")
				{
					target.style.display="none";
				}
				else
				{
					target.style.display="block";
				}
			}
		}		
   	}
};
 
/**
* Validates if a checkbox is checked, if not an alert box with error message is 
* shown.
*
* @param element <code>HTMLElement</code>
* @param message <code>String</code>
*/

xcap.utils.validateCheckbox = function(/*HTMLElement*/ element, /*String*/ message)
{
	if (document.getElementById(element).checked){
		return true;
 	}
	alert(message);
 	return false;
};

/**
* Validate if a radiobutton is chosen, if not an alert box with an error message is shown
*
* @param formId The element ID of a FORM
* @param element <code>HTMLElement</code>
* @param message <code>String</code
*/
xcap.utils.validateRadioButton = function(/*HTMLForm*/ formId, /*HTMLElement*/ element, /*String*/ message)
{
	var form = document.getElementById(formId);
	var radioButtons = form[element];
	
	for (var i = 0; i < radioButtons.length; i++) {
		if (radioButtons[i].checked) {
			return true;
		}
	}
	alert(message);
	return false;
}
/**
* Trims a string of whitespace
*  
* @param str <code>String</code>
*/ 	
xcap.utils.Trim = function(/*String*/str)
{
	return str.replace(/(^\s+)|(\s+$)/g,"");
}

