/*
 * JavaScriptUtil version 1.1
 *
 * The JavaScriptUtil is a set of misc functions used by the other scripts
 *
 * Author: Luis Fernando Planella Gonzalez (lfpg_dev@pop.com.br)
 * Home Page: http://javascriptools.sourceforge.net
 *
 * You may freely distribute this file, since you include this header 
 * along with the script
 */

///////////////////////////////////////////////////////////////////////////////
// Constants
var JST_CHARS_NUMBERS = "0123456789";
var JST_CHARS_LOWER = "abcdefghijklmnopqrstuvwxyzçáéíóúãõâêôàèìòùäëïöü";
var JST_CHARS_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZÇÁÉÍÓÚÃÕÂÊÔÀÈÌÒÙÄËÏÖÜ";
var JST_CHARS_LETTERS = JST_CHARS_LOWER + JST_CHARS_UPPER;
var JST_CHARS_ALPHA = JST_CHARS_LETTERS + JST_CHARS_NUMBERS;

//Number of milliseconds in a second
var MILLIS_IN_SECOND = 1000;

//Number of milliseconds in a minute
var MILLIS_IN_MINUTE = 60 * MILLIS_IN_SECOND;

//Number of milliseconds in a hour
var MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE;

//Number of milliseconds in a day
var MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR;

//Date field: milliseconds
var JST_FIELD_MILLISECOND = 0;

//Date field: seconds
var JST_FIELD_SECOND = 1;

//Date field: minutes
var JST_FIELD_MINUTE = 2;

//Date field: hours
var JST_FIELD_HOUR = 3;

//Date field: days
var JST_FIELD_DAY = 4;

//Date field: months
var JST_FIELD_MONTH = 5;

//Date field: years
var JST_FIELD_YEAR = 6;

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the reference to the named object
 * Parameters:
 *     name: The object's name
 *     source: The object where to search the name
 * Returns: The reference, or null if not found
 */
function getObject(objectName, source) {
    if (isEmpty(objectName)) {
        return null;
    }
	if(isEmpty(source)) {
        source = self;
    }
    //Check if the source is a reference or a name
	if(source.substring) {
        //It's a name. Try to find it on a frame
    	sourceName = source;
    	source = self.frames[sourceName];
    	if (source == null) source = parent.frames[sourceName];
      	if (source == null) source = top.frames[sourceName];
    	if (source == null) source = getObject(sourceName);
    	if (source == null) return null;
    }
    //Get the document
	var document = (source.document) ? source.document : source;
    //Check the browser's type
    if (document.all) {
        //Internet Explorer
        if (source[objectName]) return source[objectName];
        if (document[objectName]) return document[objectName];
        if (document.all[objectName]) return document.all[objectName];
    } else {
        //Netscape
        if (document.getElementById(objectName)) return document.getElementById(objectName);
        var collection = document.getElementsByName(objectName);
        if (collection.length == 1) return collection[0];
        if (collection.length > 1) return collection;
    }
	return null;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns if the object is an instance of the specified class
 * Parameters:
 *     object: The object
 *     clazz: The class
 * Returns: Is the object an instance of the class?
 */
function isInstance(object, clazz) {
    if ((object == null) || (clazz == null)) {
        return false;
    }
    if (object instanceof clazz) {
        return true;
    }
    var base = object.base;
    while (base != null) {
        if (base == clazz) {
            return true;
        }
        base = base.base;
    }
	return false;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns true if the object value represents a true value
 * Parameters:
 *     object: The input object. It will be treated as a string.
 *        if the string starts with 1, Y, N or S, it will be 
 *        considered true. False otherwise.
 * Returns: The boolean value
 */
function booleanValue(object) {
    if (object == true || object == false) {
        return object;
    } else {
        object = String(object);
        if (object.length == 0) {
            return false;
        } else {
            var first = object.charAt(0).toUpperCase();
            var trueChars = "T1YS";
            return trueChars.indexOf(first) != -1
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the index of the object in array, -1 if it's not there...
 * Parameters:
 *     object: The object to search
 *     array: The array where to search
 *     startingAt: The index where to start the search (optional)
 * Returns: The index
 */
function indexOf(object, array, startingAt) {
    if ((object == null) || !(array instanceof Array)) {
        return -1;
    }
    if (startingAt == null) {
        startingAt = 0;
    }
    for (var i = startingAt; i < array.length; i++) {
        if (array[i] == object) {
            return i;
        }
    }
    return -1;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns if the object is in the array
 * Parameters:
 *     object: The object to search
 *     array: The array where to search
 * Returns: Is the object in the array?
 */
function inArray(object, array) {
    return indexOf(object, array) >= 0;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the concatenation of two arrays
 * Parameters:
 *     array1: The array first array
 *     array1: The array second array
 * Returns: The concatenation of the two arrays
 */
function arrayConcat(array1, array2) {
    var ret = [];
    if (array1 != null) {
        for (i = 0; i < array1.length; i++) {
            ret[ret.length] = array1[i];
        }
    }
    if (array2 != null) {
        for (i = 0; i < array2.length; i++) {
            ret[ret.length] = array2[i];
        }
    }
    return ret;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Checks or unchecks all the checkboxes
 * Parameters:
 *     object: The reference for the checkbox or checkbox array.
 *     flag: If true, checks, otherwise, unchecks the checkboxes
 */
function checkAll(object, flag) {
	if (typeof(object) == "undefined") {
    	return;
    }
	if (typeof(object) == "object") {
    	if (object.type == "checkbox") {
        	object.checked = flag;
        } else {
        	for (i = 0; i < object.length; i++) {
            	object[i].checked = flag;
            }
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Gets the value of an element
 * Parameters:
 *     object: The reference for the element
 * Returns: The value or an Array containing the values, if there's more than one
 */
function getValue(object) {
    //Validates the object
	if (object == null) {
    	return null;
    }

    //Check if is the object name    
    if (typeof(object) == "string") {
        object = getObject(object);
    }

    //Check if object is an array
	if (object.length && !object.type) {
	    var ret = new Array();
	    for (var i = 0; i < object.length; i++) {
	        var temp = getValue(object[i]);
	        if (temp != null) {
	            ret[i] = temp;
	        }
	    }
	    return ret;
    }

    //Check the object type
    if (object.type) {
        //Select element
        if (object.type.indexOf("select") >= 0) {
            var ret = new Array();
            for (i = 0; i < object.options.length; i++) {
                if (booleanValue(object.options[i].selected)) {
                    ret[ret.length] = object[i].value;
                    if (!object.multiple) {
                        break;
                    }
                }
            }
            return ret.length == 0 ? null : ret.length == 1 ? ret[0] : ret;
        }
	
        //Radios and checkboxes
    	if (object.type == "radio" || object.type == "checkbox") {
        	return booleanValue(object.checked) ? object.value : null;
        } else {
            //Other input elements
        	return object.value;
        }
    } else if (object.innerHTML) {
        //Not an input
    	return object.innertHTML;
    }
    
    //Invalid object
	return null;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Sets the value of an element
 * Parameters:
 *     object: The reference for the element
 *     value: The value to be set
 */
function setValue(object, value) {

    //Validates the object
	if (object == null) {
    	return;
    }
    
    //Check if is the object name    
    if (typeof(object) == "string") {
        object = getObject(object);
    }

    //Use an array
    var values;
    if (isInstance(value, Array)) {
        values = value;    
    } else {
        values = [value];
    }

    //Check if object is an array
	if (object.length && !object.type) {
        while (values.length < object.length) {
            values[values.length] = "";
        }
	    for (var i = 0; i < object.length; i++) {
	        setValue(object[i], values[i]);
	    }
	    return;
    }

    //Check the object type
	if (object.type) {
	    //Check the input type
    	if (object.type.indexOf("select") >= 0) {
            //Select element
        	for (var i = 0; i < object.options.length; i++) {
                var match = false;
                for (var k = 0; !match && k < values.length; k++) {
                    if (object[i].value == values[k]) {
                        match = true;
                        break;
                    }                
                }
                object[i].selected = match;
            }
            return;
        } else if (object.type == "radio" || object.type == "checkbox") {
            //Radios and checkboxes    	
            var match = false;
            for (var i = 0; !match && i < values.length; i++) {
                if (object.value == values[i]) {
                    match = true;
                }                
            }
            object.checked = match;
            return;
        } else {
            //Other input elements: get the first value
        	object.value = values.length == 0 ? "" : values[0];
        	return;
        }
    } else if (object.innerHTML) {
        //The object is not an input
    	object.innertHTML = values.length == 0 ? "" : values[0];
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns if an object is an empty instance ("" or null)
 * Parameters:
 *     object: The object
 * Returns: Is the object an empty instance?
 */
function isEmpty(object) {
    return String(object) == "" || object == null || typeof(object) == "undefined";
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Replaces all the occurences in the string
 * Parameters:
 *     string: The string
 *     find: Text to be replaced
 *     replace: Text to replace the previous
 * Returns: The new string
 */
function replaceAll(string, find, replace) {
    return String(string).split(find).join(replace);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Removes all whitespaces on the left side
 * Parameters:
 *     string: The string
 * Returns: The new string
 */
function ltrim(string) {
    string = String(string);
    var pos = 0;
    while (string.charAt(pos) == " ") {
        pos++;
    }
    return string.substr(pos);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Removes all whitespaces on the right side
 * Parameters:
 *     string: The string
 * Returns: The new string
 */
function rtrim(string) {
    string = String(string);
    var pos = string.length - 1;
    while (string.charAt(pos) == " ") {
        pos--;
    }
    return string.substring(0, pos + 1);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Removes all whitespaces on both left and right sides
 * Parameters:
 *     string: The string
 * Returns: The new string
 */
function trim(string) {
    return ltrim(rtrim(string));
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Make the string have the specified length, completing with the 
 * specified character on the left
 * Parameters:
 *     string: The string
 *     size: The string size
 *     chr: The character that will fill the string
 * Returns: The new string
 */
function lpad(string, size, chr) {
    string = String(string);
    if (size <= 0) {
        return "";
    }
    if (isEmpty(chr)) {
        chr = " ";
    } else {
        chr = String(chr).charAt(0);
    }
    while (string.length < size) {
        string = chr + string;
    }
    return string;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Make the string have the specified length, completing with the 
 * specified character on the reight
 * Parameters:
 *     string: The string
 *     size: The string size
 *     chr: The character that will fill the string
 * Returns: The new string
 */
function rpad(string, size, chr) {
    string = String(string);
    if (size <= 0) {
        return "";
    }
    chr = String(chr);
    if (isEmpty(chr)) {
        chr = " ";
    } else {
        chr = chr.charAt(0);
    }
    while (string.length < size) {
        string += chr;
    }
    return string;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Removes the specified number of characters 
 * from a string after an initial position
 * Parameters:
 *     string: The string
 *     pos: The initial position
 *     size: The crop size (optional, default=1)
 * Returns: The new string
 */
function crop(string, pos, size) {
    string = String(string);
    if (size == null) {
        size = 1;
    }
    if (size <= 0) {
        return "";
    }
    return left(string, pos) + mid(string, pos + size);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Removes the specified number of characters from the left of a string 
 * Parameters:
 *     string: The string
 *     size: The crop size (optional, default=1)
 * Returns: The new string
 */
function lcrop(string, size) {
    if (size == null) {
        size = 1;
    }
    return crop(string, 0, size);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Removes the specified number of characters from the right of a string 
 * Parameters:
 *     string: The string
 *     size: The crop size (optional, default=1)
 * Returns: The new string
 */
function rcrop(string, size) {
    string = String(string);
    if (size == null) {
        size = 1;
    }
    return crop(string, string.length - size, size);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Capitalizes the text, uppercasing the first letter of every word
 * Parameters:
 *     text: The text
 * Returns: The new text
 */
function capitalize(text) {
    text = String(text);
    var out = "";
    var last = '';
    var spaces = [' ', '\t', '\r', '\n'];
    for (var i = 0; i < text.length; i++) {
        var current = text.charAt(0);
        if (inArray(last, spaces)) {
            out += current.toUpperCase();
        } else {
            out += current.toLowerCase();
        }
        last = current;
    }
    return out;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Checks if the string contains only the specified characters
 * Parameters:
 *     string: The string
 *     possible: The string containing the possible characters
 * Returns: Do the String contains only the specified characters?
 */
function onlySpecified(string, possible) {
    string = String(string);
    possible = String(possible);
    for (var i = 0; i < string.length; i++) {
        if (possible.indexOf(string.charAt(i)) == -1) {
            return false;
        }
    }
    return true;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Checks if the string contains only numbers
 * Parameters:
 *     string: The string
 * Returns: Do the String contains only numbers?
 */
function onlyNumbers(string) {
    var possible = JST_CHARS_NUMBERS;
    return onlySpecified(string, possible);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Checks if the string contains only letters
 * Parameters:
 *     string: The string
 * Returns: Do the String contains only lettersts?
 */
function onlyLetters(string) {
    var possible = JST_CHARS_LOWER + JST_CHARS_UPPER;
    return onlySpecified(string, possible);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Checks if the string contains only alphanumeric characters (letters or digits)
 * Parameters:
 *     string: The string
 * Returns: Do the String contains only alphanumeric characters?
 */
function onlyAlpha(string) {
    var possible = JST_CHARS_NUMBERS + JST_CHARS_LOWER + JST_CHARS_UPPER;
    return onlySpecified(string, possible);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the left most n characters
 * Parameters:
 *     string: The string
 *     n: The number of characters
 * Returns: The substring
 */
function left(string, n) {
    string = String(string);
    return string.substring(0, n);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the right most n characters
 * Parameters:
 *     string: The string
 *     n: The number of characters
 * Returns: The substring
 */
function right(string, n) {
    string = String(string);
    return string.substr(string.length - n);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns n characters after the initial position
 * Parameters:
 *     string: The string
 *     pos: The initial position
 *     n: The number of characters (optional)
 * Returns: The substring
 */
function mid(string, pos, n) {
    string = String(string);
    if (n == null) {
        n = string.length;
    }
    return string.substring(pos, pos + n);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Inserts a value inside a string
 * Parameters:
 *     string: The string
 *     pos: The insert position
 *     value: The value to be inserted
 * Returns: The updated
 */
function insertString(string, pos, value) {
    string = String(string);
    var prefix = left(string, pos);
    var suffix = mid(string, pos)
    return prefix + value + suffix;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns all properties in the object, sorted or not, with the separator between them.
 * Parameters:
 *     object: The object
 *     sort: Must be sorted?
 *     separator: The separator between properties
 * Returns: The substring
 */
function debug(object, sort, separator) {
	if (object == null) {
        return "";
    }
    sort = booleanValue(sort == null ? true : sort);
	if (separator == null) {
        separator = "\n";
    }
    //Get the properties
	var properties = new Array();
	for (var property in object) {
    	properties[properties.length] = property + " = " + object[property];
    }
    //Sort if necessary
    if (sort) {
        properties.sort();
    }
    //Build the output
	return properties.join(separator);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Escapes the string's special characters to their escaped form
 * ('\\' to '\\\\', '\n' to '\\n', ...) and the extraChars are escaped via unicode
 * (\\uXXXX, where XXXX is the hexadecimal charcode)
 * Parameters:
 *     string: The string to be escaped
 *     extraChars: The String containing extra characters to be escaped
 *     onlyExtra: If true, do not process the standard characters ('\\', '\n', ...)
 * Returns: The encoded String
 */
function escapeCharacters(string, extraChars, onlyExtra) {
    var ret = String(string);
    extraChars = String(extraChars || "");
    onlyExtra = booleanValue(onlyExtra);
    //Checks if must process only the extra characters
    if (!onlyExtra) {
        ret = replaceAll(ret, "\n", "\\n");
        ret = replaceAll(ret, "\r", "\\r");
        ret = replaceAll(ret, "\t", "\\t");
        ret = replaceAll(ret, "\"", "\\\"");
        ret = replaceAll(ret, "\'", "\\\'");
        ret = replaceAll(ret, "\\", "\\\\");
    }
    //Process the extra characters
    for (var i = 0; i < extraChars.length; i++) {
        var chr = extraChars.charAt(i);
        ret = replaceAll(ret, chr, "\\\\u" + lpad(new Number(chr.charCodeAt(0)).toString(16), 4, '0'));
    }
    return ret;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Unescapes the string, changing the special characters to their unescaped form
 * ('\\\\' to '\\', '\\n' to '\n', '\\uXXXX' to the hexadecimal ASC(XXXX), ...)
 * Parameters:
 *     string: The string to be unescaped
 *     onlyExtra: If true, do not process the standard characters ('\\', '\n', ...)
 * Returns: The unescaped String
 */
function unescapeCharacters(string, onlyExtra) {
    var ret = String(string);
    var pos = -1;
    var u = "\\\\u";
    onlyExtra = booleanValue(onlyExtra);
    //Process the extra characters
    do {
        pos = ret.indexOf(u);
        if (pos >= 0) {
            var charCode = parseInt(ret.substring(pos + u.length, pos + u.length + 4), 16);
            ret = replaceAll(ret, u + charCode, String.fromCharCode(charCode));
        }
    } while (pos >= 0);
    
    //Checks if must process only the extra characters
    if (!onlyExtra) {
        ret = replaceAll(ret, "\\n", "\n");
        ret = replaceAll(ret, "\\r", "\r");
        ret = replaceAll(ret, "\\t", "\t");
        ret = replaceAll(ret, "\\\"", "\"");
        ret = replaceAll(ret, "\\\'", "\'");
        ret = replaceAll(ret, "\\\\", "\\");
    }
    return ret;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Writes the specified cookie
 * Parameters:
 *     name: The cookie name
 *     value: The value
 *     document: The document containing the cookie. Default to self.document
 *     expires: The expiration date. Defaults: do not expires
 *     path: The cookie's path. Default: not specified
 *     domain: The cookie's domain. Default: not specified
 */
function writeCookie( cookieName, cookieValue, lifeTime, path, domain, isSecure ) {
	if( !cookieName ) { return false; }
	if( lifeTime == "delete" ) { lifeTime = -10; } //this is in the past. Expires immediately.
	/* This next line sets the cookie but does not overwrite other cookies.
	syntax: cookieName=cookieValue[;expires=dataAsString[;path=pathAsString[;domain=domainAsString[;secure]]]]
	Because of the way that document.cookie behaves, writing this here is equivalent to writing
	document.cookie = whatIAmWritingNow + "; " + document.cookie; */
	document.cookie = escape( cookieName ) + "=" + escape( cookieValue ) +
		( lifeTime ? ";expires=" + ( new Date( ( new Date() ).getTime() + ( 1000 * lifeTime ) ) ).toGMTString() : "" ) +
		( path ? ";path=" + path : "") + ( domain ? ";domain=" + domain : "") + 
		( isSecure ? ";secure" : "");
	//check if the cookie has been set/deleted as required
	if( lifeTime < 0 ) { if( typeof( readCookie( cookieName ) ) == "string" ) { return false; } return true; }
	if( typeof( readCookie( cookieName ) ) == "string" ) { return true; } return false;
}


///////////////////////////////////////////////////////////////////////////////
/*
 * Reads the specified cookie
 * Parameters:
 *     name: The cookie name
 *     document: The document containing the cookie. Default to self.document
 * Returns: The value
 */
function readCookie( cookieName ) {
//	/* retrieved in the format
//	cookieName4=value; cookieName3=value; cookieName2=value; cookieName1=value
//	only cookies for this domain and path will be retrieved */
	var cookieJar = document.cookie.split( "; " );
	for( var x = 0; x < cookieJar.length; x++ ) {
		var oneCookie = cookieJar[x].split( "=" );
		if( oneCookie[0] == escape( cookieName ) ) { return unescape( oneCookie[1] ); }
	}
	return null;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Removes the specified cookie
 * Parameters:
 *     name: The cookie name
 *     document: The document containing the cookie. Default to self.document
 *     path: The cookie's path. Default: not specified
 *     domain: The cookie's domain. Default: not specified
 */
function deleteCookie(name, document, path, domain) {
    writeCookie(name, null, document, path, domain);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the difference, as in date2 - date1
 * Parameters:
 *     date1: the first date
 *     date2: the second date
 *     field: The field. May be one of the constants JST_FIELD_*. Default to JST_FIELD_DAY
 * Returns: An integer number
 */
function dateDiff(date1, date2, field) {
    if (!isInstance(date1, Date) || !isInstance(date2, Date)) {
        return null;
    }
    if (field == null) field = JST_FIELD_DAY;
    if (field < 0 || field > JST_FIELD_YEAR) {
        return null;
    }
    if (field <= JST_FIELD_DAY) {
        var div = 1;
        switch (field) {
            case JST_FIELD_SECOND:
                div = MILLIS_IN_SECOND;
                break;
            case JST_FIELD_MINUTE:
                div = MILLIS_IN_MINUTE;
                break;
            case JST_FIELD_HOUR:
                div = MILLIS_IN_HOUR;
                break;
            case JST_FIELD_DAY:
                div = MILLIS_IN_DAY;
                break;
        }
        return Math.round((date2.getTime() - date1.getTime()) / div);
    }
    var years = date2.getFullYear() - date1.getFullYear();
    if (field == JST_FIELD_YEAR) {
        return years;
    } else if (field == JST_FIELD_MONTH) {
        var months1 = date1.getMonth();
        var months2 = date2.getMonth();
        
        if (years < 0) {
            months1 += Math.abs(years) * 12;
        } else if (years > 0) {
            months2 += years * 12;
        }
        
        return (months2 - months1);
    }
    return null;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Truncates the date, setting all fields lower than the specified one to its minimum value
 * Parameters:
 *     date: The date
 *     field: The field. May be one of the constants JST_FIELD_*. Default to JST_FIELD_DAY
 * Returns: The new Date
 */
function truncDate(date, field) {
    if (!isInstance(date, Date)) {
        return null;
    }
    if (field == null) field = JST_FIELD_DAY;
    if (field < 0 || field > JST_FIELD_YEAR) {
        return null;
    }
    var ret = new Date(date.getTime());
    if (field > JST_FIELD_MILLISECOND) {
        ret.setMilliseconds(0);
    }
    if (field > JST_FIELD_SECOND) {
        ret.setSeconds(0);
    }
    if (field > JST_FIELD_MINUTE) {
        ret.setMinutes(0);
    }
    if (field > JST_FIELD_HOUR) {
        ret.setHours(0);
    }
    if (field > JST_FIELD_DAY) {
        ret.setDate(1);
    }
    if (field > JST_FIELD_MONTH) {
        ret.setMonth(0);
    }
    return ret;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the maximum day of a given month and year
 * Parameters:
 *     month: the month
 *     year: the year
 * Returns: The maximum day
 */
function getMaxDay(month, year) {
    month = new Number(month) + 1;
    year = new Number(year);
    switch (month) {
        case 1: case 3: case 5: case 7:
        case 8: case 10: case 12:
            return 31;
	    case 4: case 6: case 9: case 11:
			return 30;
		case 2:
			if ((year % 4) == 0) {
				return 29;
			} else {
				return 28;
			}
        default:
            return 0;
	}
}

///////////////////////////////////////////////////////////////////////////////
/*
 * Returns the full year, given a 2 digit year. 50 or less returns 2050
 * Parameters:
 *     year: the year
 * Returns: The 4 digit year
 */
function getFullYear(year) {
    year = Number(year);
    if (year < 1000) {
        if (year < 50 || year > 100) {
            year += 2000;
        } else {
            year += 1900;
        }
    }
    return year;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * A class that represents a key/value pair
 * Parameters:
 *     key: The key
 *     value: The value
 */
function Pair(key, value) {
    this.key = key == null ? "" : key;
    this.value = value;
}

///////////////////////////////////////////////////////////////////////////////
/*
 * DEPRECATED - Pair is a much meaningful name, use it instaed.
 *              Value will be removed in future versions.
 */
function Value(key, value) {
    this.base = Pair;
    this.base(key, value);
}

///////////////////////////////////////////////////////////////////////////////
/*
 * A class that represents a Map. It is a set of pairs. Each key exists 
 * only once. If the key is added again, it's value will be updated instaed
 * of adding a new pair.
 * Parameters:
 *     pairs: The initial pairs array (optional)
 */
function Map(pairs) {
    this.pairs = pairs || new Array();
    this.afterSet = null;
    this.afterRemove = null;
    
    /*
     * Adds the pair to the map
     */
    this.putValue = function(pair) {
        this.putPair(pair);
    }

    /*
     * Adds the pair to the map
     */
    this.putPair = function(pair) {
        if (isInstance(pair, Pair)) {
            for (var i = 0; i < this.pairs.length; i++) {
                if (this.pairs[i].key == pair.key) {
                    this.pairs[i].value = pair.value;
                }
            }
            this.pairs[this.pairs.length] = pair;
            if (this.afterSet != null) {
                this.afterSet(pair);
            }
        }
    }

    /*
     * Adds the key / value to the map
     */
    this.put = function(key, value) {
        this.putValue(new Pair(key, value));
    }

    /*
     * Adds all the pairs to the map
     */
    this.putAll = function(map) {
        if (!(map instanceof Map)) {
            return;
        }
        var entries = map.getEntries();
        for (var i = 0; i < entries.length; i++) {
            this.putPair(entries[i]);
        }
    }
    
    /*
     * Returns the entry count
     */
    this.size = function() {
        return this.pairs.length;
    }
    
    /*
     * Returns the mapped entry
     */
    this.get = function(key) {
        for (var i = 0; i < this.pairs.length; i++) {
            var pair = this.pairs[i];
            if (pair.key == key) {
                return pair.value;
            }
        }
    }
    
    /*
     * Returns the keys
     */
    this.getKeys = function() {
        var ret = new Array();
        for (var i = 0; i < this.pairs.length; i++) {
            ret[ret.length] = this.pairs[i].key;
        }
        return ret;
    }
    
    /*
     * Returns the values
     */
    this.getValues = function() {
        var ret = new Array();
        for (var i = 0; i < this.pairs.length; i++) {
            ret[ret.length] = this.pairs[i].value;
        }
        return ret;
    }

    /*
     * Returns the pairs
     */
    this.getEntries = function() {
        return this.getPairs();
    }
    
    /*
     * Returns the pairs
     */
    this.getPairs = function() {
        var ret = new Array();
        for (var i = 0; i < this.pairs.length; i++) {
            ret[ret.length] = this.pairs[i];
        }
        return ret;
    }
    
    /*
     * Remove the specified key, returning the pair
     */
    this.remove = function (key) {
        for (var i = 0; i < this.pairs.length; i++) {
            var pair = this.pairs[i];
            if (pair.key == key) {
                this.pairs.splice(i, 1);
                if (this.afterRemove != null) {
                    this.afterRemove(pair);
                }
                return pair;
            }
        }
        return null;
    }
    
    /*
     * Removes all values
     */
    this.clear = function (key) {
        var ret = this.pairs;
        for (var i = 0; i < ret.length; i++) {
            this.remove(ret[i].key);
        }
        return ret;
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * A Map that gets its pairs using a single string. The String has a pair
 * separator and a name/value separator. Ex: name1=value1&name2=value2&...
 * Parameters:
 *     string: The string in form: name1=value1&name2=value2&...
 *     nameSeparator: The String between the name/value pairs. Default: &
 *     valueSeparator: The String between name and value. Default: =
 */
function StringMap(string, nameSeparator, valueSeparator, isEncoded) {
    this.nameSeparator = nameSeparator || "&";
    this.valueSeparator = valueSeparator || "=";
    this.isEncoded = isEncoded == null ? true : booleanValue(isEncoded);
    
    var pairs = new Array();
    string = trim(string);
    if (!isEmpty(string)) {
        var namesValues = string.split(nameSeparator);
        for (i = 0; i < namesValues.length; i++) {
            var nameValue = namesValues[i].split(valueSeparator);
            var name = trim(nameValue[0]);
            var value = "";
            if (nameValue.length > 0) {
                value = trim(nameValue[1]);
                if (this.isEncoded) {
                    value = decodeURIComponent(value);
                }
            }
            var pos = -1;
            for (j = 0; j < pairs.length; j++) {
                if (pairs[j].key == name) {
                    pos = j;
                    break;
                }
            }
            //Check if the value already existed: build an array
            if (pos >= 0) {
                var array = pairs[pos].value;
                if (!isInstance(array, Array)) {
                    array = [array];
                }
                array[array.length] = value;
                pairs[pos].value = array;
            } else {
                pairs[pairs.length] = new Pair(name, value);
            }
        }
    }
    this.base = Map;
    this.base(pairs);
    
    /*
     * Rebuild the String
     */
    this.getString = function() {
        var ret = new Array();
        for (var i = 0; i < this.pairs.length; i++) {
            var pair = this.pairs[i];
            ret[ret.length] = pair.key + this.valueSeparator + this.value;
        }
        return ret.join(this.nameSeparator);
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * A StringMap used to get values from the location query string
 * Parameters:
 *     location: the location object. Default to self.location
 */
function QueryStringMap(location) {
    this.location = location || self.location;
    
    var string = String(this.location.search);
    if (!isEmpty(string)) {
        //Remove the ? at the start
        string = string.substr(1);
    }

    this.base = StringMap;
    this.base(string, "&", "=", true);
    
    //Ensures the string will not be modified
    this.putPair = function() {
        alert("Cannot put a value on a query string");
    }
    this.remove = function() {
        alert("Cannot remove a value from a query string");
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * A StringMap used to get values from the document cookie
 * Parameters:
 *     document: the document. Default to self.document
 */
function CookieMap(document) {
    this.document = document || self.document;

    this.base = StringMap;
    this.base(document.cookie, ";", "=", true);

    //Set the callback to update the cookie    
    this.afterSet = function (pair) {
        writeCookie(pair.key, pair.value, this.document);
    }
    this.afterRemove = function (pair) {
        deleteCookie(pair.key, this.document);
    }
}

///////////////////////////////////////////////////////////////////////////////
/*
 * A Map used to get/set an object's properties
 * Parameters:
 *     object: The object
 */
function ObjectMap(object) {
    this.object = object;

    var pairs = new Array();
    for (var property in this.object) {
        pairs[pairs.length] = new Pair(property, this.object[property]);
    }
    this.base = Map;
    this.base(pairs);

    //Set the callback to update the cookie    
    this.afterSet = function (pair) {
        this.object[pair.key] = pair.value;
    }
    this.afterRemove = function (pair) {
        delete object[pair.key];
    }
}