﻿
///
// get the URL for a REST command.
// This function returns a URL that can be used with the jQuery getJSON() function to
// execute a REST call.
//
// This function also provides a way to simulate REST calls when used in a static page
// presentation. The JSON structure that would result from each API call can be stored in a file
// in the "api" directory. The name of the file is the API name followed by the argument list. The arguments
// are separated by the '_', the equals is replace with '-', and any ':' is replaced with a '.'. For example,
// when the cpFeatures API is called with the arguments "category=common", the file name would be "api/cpFeatures_category-common.json".
// The content of these api files must exactly match the JSON that would be returned by the actual REST call. The API document
// defines all of the field names and types returned by each call. The API document must be kept
// synchronized with the content of the simulation files so the UI will work correctly with the backend code.
//
// @param[in] cmd	The name of the API function to execute.
// @param[in] args	A map of arguments in the form {"key":"value", "key2": "value"}. The args should
//			be omitted if the function doesn't require arguments. The addField() function can be
//			used to simplify creating the map. This function will handle properly
//			quoting the values.
//
// @return
//	- A URL that will execute the desired API function.
function getRestUrl(cmd, args)
{
	if (args) {
		return ("cp/"+cmd+"?v=1&"+argstr(args));
	}
	return ("cp/"+cmd+"?v=1");
}

function argstr(args)
{
	var str = "";
	var sep = "";
	for (var key in args) {
		str += sep+key+'='+encodeURIComponent(args[key]);
		sep = "&";
	}
	return (str);
}

function encodeFields(flds)
{
	if (flds) {
		var ret = new Object();
		for (var key in flds) {
			ret[key] = encodeURIComponent(flds[key]);
		}
		return (ret);
	}
	return (flds);
}
		
///
// Launch the help window and display a particular context
//
function openHelpFile(helpTopic, helpUrl)
{
	var aw = screen.availWidth;
	var ah = screen.availHeight;
	w = 775;
	if (w > aw) {
		w = aw;
	}
	l = aw-w;
	var options = "menubar=no, status=no, toolbar=yes, resizable=1, width=" + w + ", height=" + ah + ", top=0, left=" + l;
	if (helpUrl) {
		if (helpTopic) {
			helpUrl = helpUrl+helpTopic;
		}
	} else {
		if (helpTopic) {
			helpUrl = "help/help.html#"+helpTopic;
		} else {
			helpUrl = "help/help.html";
		}
	}
	var win = window.open(helpUrl, "Help", options);
	win.moveTo(l, 0);
	win.resizeTo(w, ah);
	win.focus();
}

///
// Set the initial values in a form to match the current values.
// Once the contents of a form are submitted the reset action associated with the Cancel button should
// return the form to the updated values, not to the values that existed when the form was originally
// populated. This behavior can be implemented by setting the default values of all controls to match
// the current values. The loadFields() function for every page should either populate the form content
// directly from device data using $.getJSON(), or it should call this function to set the default values.
//
// @param[in] selectstr (Optional): If specified, this argument gives a selector for elements that contain the
//			data entry fields to reset. If not provided then default values for all data entry
//			fields are updated.
//
function resetFieldDefaults(selectstr)
{
	if (selectstr) {
		selectstr = selectstr+" ";
	} else {
		selectstr = "form";
	}
	$(selectstr).find(':input').each(function () {
		if ($(this).is(':text, :password, :file')) {
			$(this).attr("defaultValue", $(this).attr("value"));
		} else if ($(this).is(':checkbox, :radio')) {
			$(this).attr("defaultChecked", $(this).attr("checked"));
		} else if ($(this).is('select')) {
			$(this).find("option:selected").attr("defaultSelected", true);
			$(this).find("option:not(:selected)").attr("defaultSelected", false);
		}
	});
}
///
// Set an event handler to be called if any field content changes.
// Any previous handler configured by this function is deleted.
//
// The event is attached to all input fields and selection elements and is called if any of these items
// changes. This is normally used to enable the "Apply" and "Cancel" buttons.
//
// @param[in] changefn	The function to call when field content changes.
// @param[in] selectstr (Optional): If specified, this argument gives a selector for elements that contain the
//			data entry fields to monitor. If not provided then all data entry fields are monitored.
//
function setFieldChange(changefn, selectstr)
{
	if (selectstr) {
		selectstr = selectstr+" ";
	} else {
		selectstr = "";
	}
	// Remove any previously configured handler for the field change
	$(selectstr+":input").unbind(".fieldChange");
	// Bind the new change event to all input and select fields
	$(selectstr+":input").bind("change.fieldChange", changefn);
	// Since the change event is only recognized when the field loses focus, we also need to look at
	// text field key presses to immediately send the event. We have to ignore arrow keys, tab, and return.
	var handler = function(event) {
		if (event.keyCode != 9 && event.keyCode != 13 && (event.keyCode < 37 || event.keyCode > 40)) {
			changefn();
		}
	};
	$(selectstr+'input[type="text"]').bind("keyup.fieldChange", handler);
	$(selectstr+'input[type="password"]').bind("keyup.fieldChange", handler);
	$(selectstr+'input[type="textarea"]').bind("keyup.fieldChange", handler);
}
///
// Start a timer to continually update the status information in the banner
// update the banner each REFRESHINFO miliseconds.
//
var hasReturned = true; //a new API call can be done only if the previous one was returned
var REFRESHINFO = 60000; //the first request will be done immediatelly (0 seconds); next ones will be done in 60 seconds
var REFRESHSTOPPED = false;

function showStatus()
{
	if (hasReturned) {
		hasReturned = false;
		$.getJSON(getRestUrl("StorageInfo"), function(data) {
			if (!data || data.error) {
				$("#spaceusage").fadeOut();

				//get the current page.
				var currPage = jQuery.url.attr("path");

				//redirect to login if session has expired
				if(data && data.error.text && (data.error.text.toLowerCase() == "authentication failure") && (currPage != "/login.html") && (currPage != "/index.html") && (currPage != "/")) {
					window.location = "login.html";
					return (false);
				}

				//in some pages, don't need to show the error (authentication failure, unable to retrieve the requested information)
				if ((currPage == "/") || (currPage == "/index.html") || (currPage == "/login.html") || (currPage == "/foldercontent.html") || (currPage == "/restart.html") || (currPage == "/manage.html")) {
					return (false);
				}

				//show the error message
				showError(data, function(data) {
					//in this case, keep (hasReturned == false) to prevent another requests to the API
					return (false);
				}, null, null);
			} else {
				$("#spaceusage").fadeIn();
				$("#spaceusage div").width(getPct(data.size, data.used, 0.5));
				$("#spaceusage").attr("title", "%1$s space free out of %2$s total.".replace("%1$s", sizeStr(data.size-data.used)).replace("%2$s", sizeStr(data.size)));

				hasReturned = true; //only allow a new API call if there're no erros
			}
		});
	}

	showStatusInterval(); //try to call the API again
}
function showStatusInterval()
{
	var t = setTimeout('if(!REFRESHSTOPPED) showStatus(); else showStatusInterval();',REFRESHINFO);
}

function showStatusStop() {
	REFRESHSTOPPED = true;
}

function showStatusResume() {
	REFRESHSTOPPED = false;
}

///
// Choose an appropriate plural form based on a number.
// Normally the ngettext call is used to generate a translation for content with a numeric value inserted
// in it. But ngettext requires that the numeric value be determined when the page is generated, not when
// the page runs in the browser. The pluralstr function can be used in conjunction with the plurals() parser
// function to allow translation of plurals for numbers only known when the JavaScript runs in the browser.
//
// @param[in] def	The plural string list generated by the plurals() parser function.
// @param[in] n		The numeric value used to determine which plural form is used.
//
// @return	A plural form translation string appropriate for the numeric value.
function pluralstr(def, n)
{
	var plural = 0;

	eval(def.expr);
	if (plural == false) {
		plural = 0;
	}
 	if (plural == true) {
		plural = 1;
	}
	if (plural >= 0 && plural < def.strs.length) {
		return (def.strs[plural]);
	}
	return (def.strs[def.strs.length-1]);
}

// Add quoting appropriate to XML content
function xmlQuote(str)
{
	var retstr = "";
	
	if (str == null) {
		return "";
	}
	for (var i = 0; i < str.length; i++) {
		var c = str.charCodeAt(i);
		var cv = str.charAt(i);
		if ((c > 0 && c < 32) || cv == '"' || cv == "'") {
			retstr = retstr+'&#'+c+';';
		} else if (cv == '<') {
			retstr = retstr+'&lt;';
		} else if (cv == '>') {
			retstr = retstr+'&gt;';
		} else if (cv == '&') {
			retstr = retstr+'&amp;';
		} else {
			retstr = retstr+cv;
		}
	}
	return (retstr);
}
///
// Add quoting appropriate to JavaScript strings
function jsQuote(str)
{	
	if (str == null) {
		return "";
	}
	return (str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/'/g, "\\'"));
}
///
// Round a floating point string to two decimal places.
function floatStr(num)
{
	if (typeof num == "string") {
		num = parseFloat(num);
	}
	var ret = floatLocale(num.toFixed(2));
	return (ret);
}
///
// get a capacity string based on a large number
function sizeStr(num)
{
	if (num < 0) {
		return ("-"+sizeStr(-num));
	}
	if (num < 1024) {
		return ('%s B'.replace(/%s/, num));
	}
	if (num < 1000000) {
		return ('%s KB'.replace(/%s/, floatStr(num/1024)));
	}
	if (num < 1048576000) {
		return ('%s MB'.replace(/%s/, floatStr(num/1048576)));
	}
	if (num < 1073741824000) {
		return ('%s GB'.replace(/%s/, floatStr(num/1073741824)));
	}
	if (num < 1099511627776000) {
		return ('%s TB'.replace(/%s/, floatStr(num/1099511627776)));
	}
	return ('%s PB'.replace(/%s/, floatStr(num/1125899906842624)));
	
}
///
// get a numeric byte value from a size string with magnitude
function bytesFromSizeStr(s)
{ 
	// format your data for normalization
	if (s) {
		s = $.trim(s); //remove any extra white space

		var $index = 0;
		var $factor = 0;
		var $size = 0;

		if(($index = s.indexOf( $.trim('%s KB'.replace("%s", ""))) ) > 0) {
			$factor = 1024;
		} else if (($index = s.indexOf( $.trim('%s MB'.replace("%s", ""))) ) > 0) {
			$factor = 1048576;
		} else if (($index = s.indexOf( $.trim('%s GB'.replace("%s", ""))) ) > 0) {
			$factor = 1073741824;
		} else if (($index = s.indexOf( $.trim('%s TB'.replace("%s", ""))) ) > 0) {
			$factor = 1099511627776;
		} else if (($index = s.indexOf( $.trim('%s PB'.replace("%s", ""))) ) > 0) {
			$factor = 1125899906842624;
		} else if (($index = s.indexOf( $.trim('%s B'.replace("%s", ""))) ) > 0) {
			$factor = 1;
		} else {
			$factor = 1;
			$index = s.length;
		}
	            
		$size = parseFloat(s.substring(0, $index));
		return ($size*$factor);
	}
	return (0);
}
///
// In several places we allow data entry of values as GB numbers.
// This routine gets the GB string based on an integer number in bytes.
//
// There are situations where it is not desireble to localize bytes,
// when we want to use the result as a plugin data input (dashboard graphs)
// for instance.
//
// Set (preventLocale != null) to not localize the result
function GBFromBytes(num, places, preventLocale)
{
	if (!places && places != 0) {
		places = 1;
	}
	if (!num) {
		return (0);
	}
	if (typeof num == "string") {
		num = parseFloat(num);
		if (isNaN(num)) {
			return (0);
		}
	}
	num = (num/1073741824)
	
	if (preventLocale) {
		return (num.toFixed(places));		
	} else {
		return (floatLocale(num.toFixed(places)));
	}
}
///
// Return bytes based on a number in GB
function bytesFromGB(num)
{
	if (!num) {
		return (0);
	}
	if (typeof num == "string") {
		var sep = "_locale_Decimal";
		num = num.replace(sep, "."); // ...in case the locale uses a different separator for decimal.
		num = parseFloat(num);
		if (isNaN(num)) {
			return (0);
		}
	}
	return (parseInt(Math.round(num*1073741824)));
}

//
//Return the number of bytes based on a number (as a number or string type) in GB
//This function also validates if the given number is valid and pops up an error
//if necessary.
//
//@num		The number to be parsed
//@errorMsg	The localized message to be shown in case of any error
//@fieldName	The name/id of the field in the form. This allows this function to
//		set the focus to the appropriate field
//
//@return	The parsed value as number type
//		undefined in case of error
function validateBytesFromGB(num, errorMsg, fieldName)
{
	var errorFound = false;

	if (!num) {
		errorFound = true;
	}

	if (false == errorFound && typeof num == "string") {
		var sep = "_locale_Decimal";
		num = num.replace(sep, "."); // ...in case the locale uses a different separator for decimal.
		var numAux = parseFloat(num);
		var decPlaces = 0;
		if (-1 != num.indexOf(".")) {
			decPlaces = num.length - num.indexOf(".") - 1;
		}
		if (isNaN(num) || 0 > num || !(numAux.toFixed(decPlaces) === num)) {
			errorFound = true;
		}
	}

	if (true == errorFound) {
		if (errorMsg) {
			showErrorStrNoQuote(errorMsg, function() {
				try {
					if (fieldName && undefined != document.getElementsByName(fieldName)[0]) {
						document.getElementsByName(fieldName)[0].focus();
					}
				} catch (err) {
					// SCRIPT2110: Can't move focus to the control because it is invisible, 
					// not enabled, or of a type that does not accept the focus.
				}
				return (false); 
			});
		}
		return (undefined);
	}

	return (parseInt(Math.round(num * 1073741824)));
}

///
// Convert a floating point number to a locale specific floating point string
function floatLocale(num)
{
	var sep = "_locale_Decimal";
	if (sep.length > 1) {
		return (num.toString());
	}
	return (num.toString().replace(".", sep));
}
///
// Get a percentage string
//
// @param[in] total	The total size.
// @param[in] part	The size of the part to calculate a percentage for.
// @param[in] min	(optional) If specified, this minimum will be returned if a value is not zero.
function getPct(total, part, min)
{
	if (!total || !part) {
		return ("0%");
	}
	total = parseFloat(total);
	part = parseFloat(part);
	min = parseFloat(min);
	if (total == 0 || part == 0) {
		return ("0%");
	}
	if (part > total) {
		return ("100%");
	}
	var pct = (part*100)/total;
	if (min && pct < min) {
		return (min+"%");
	}
	return (pct+"%");
}



///
// Get the order of day, month, year
// @return
//	- An array of 3 integer index values. The first is the index of the year, the second is the
//	  index of the month, and the third is the index of the day of the month.
function getDateOrder()
{
	var idx = "%2$d/%1$d/%3$d".replace(/\%/g, "").replace(/\$d/g, "").split("/");
	for (var i = 0; i < idx.length; i++) {
		idx[i] = parseInt(idx[i], 10)-1;
	}
	return (idx);	
}

///
// Create a result object in the same structure that an API call would return.
//
function createResult(code, description, field)
{
	var ret = new Object();
	ret.code = code;
	ret.text = description;
	ret.field = field;
	return (ret);
}

///
// Create a Date object based on an XMLRPC format iso8601 string.
// This format always assumes the time zone is 'Z' if not included (to handle XMLRPC's lack of 
// support for time zones).
//
// @param str[in]	The date and time in iso8601 format.
//
function iso8601Date(str)
{
	var zone = 'Z';
	var hr = 0;
	var mn = 0;
	var sc = 0;
	var yr = 0;
	var mo = 0;
	var dy = 1;
	var dtm = str.split("T");
	if (dtm.length < 1) {
		return (new Date());
	}
	var date = dtm[0].replace(/-/g, "");
	if (dtm.length > 1) {
		var t = dtm[1].replace(/:/g, "");
		var i = t.indexOf('Z');
		if (i > 0) {
			zone = t.substr(i);
		} else {
			i = t.indexOf('+');
			if (i > 0) {
				zone = t.substr(i);
			} else {
				i = t.indexOf('-');
				if (i > 0) {
					zone = t.substr(i);
				} else {
					i = t.length;
				}
			}
		}
		hr = parseInt(t.substr(0, 2), 10);
		if (i > 3) {
			mn = parseInt(t.substr(2, 2), 10);
			if (i > 5) {
				sc = parseInt(t.substr(4, 2), 10);
			}
		}
	}
	yr = parseInt(date.substr(0, 4), 10);
	if (date.length > 5) {
		mo = parseInt(date.substr(4, 2), 10)-1;
		if (date.length > 7) {
			dy = parseInt(date.substr(6, 2), 10);
		}
	}
	var msec = Date.UTC(yr, mo, dy, hr, mn, sc, 0);
	if (zone.charAt(0) == '-' || zone.charAt(0) == '+') {
		var zdiff = parseInt(zone.substr(1, 2), 10)*60;
		if (zone.length > 3) {
			zdiff = zdiff + parseInt(zone.substr(3, 2), 10);
		}
		if (zone.charAt(0) == '-') {
			msec = msec + zdiff*60000;
		} else {
			msec = msec - zdiff*60000;
		}
	}
	return (new Date(msec));
}

///
// Get a date string in the XMLRPC iso8601 format (UTC without specifying the time zone or if zonediff is set as -1 
// then the local convertion takes place), based on a
// date string, a time string, and an optional am/pm boolean.
// If the boolean is omitted the time is assumed to be in 24 hour format.
//
// @return
//	- An object with a fld member that contains the string
//	- The returned object will contain an 'error' object if there are
//	  problems with the input. The error object describes the failure
//	  in the same way a REST API call would.
function getDate(datestr, timestr, ispm, zonediff)
{
	var d = new Object();
	var tvals = null;
	var dvals = null;
	var date = null;

	if (timestr) {
		tvals = timestr.split(":");
		tvals[0] = parseInt(tvals[0], 10);
		tvals[1] = parseInt(tvals[1], 10);
		if (tvals[0] < 0 || tvals[0] > 23 || tvals[1] < 0 || tvals[1] > 59) {
			d.error = createResult("badparam", "Invalid time specified.", "time");
			return (d);
		}
		if (ispm != undefined && ispm != null) {
			if (tvals[0] == 12) {
				if (!ispm) {
					tvals[0] = 0;
				}
			} else {
				if (ispm && tvals[0] < 12) {
					tvals[0] = tvals[0]+12;
				}
			}
		}
	}
	if (datestr) {
		dvals = datestr.split("/");
		
		if (dvals.length == 3) {
			didx = getDateOrder();
			var tmp = [parseInt(dvals[didx[2]], 10), parseInt(dvals[didx[1]], 10)-1, parseInt(dvals[didx[0]], 10)];
			dvals = tmp;
			
			if (dvals[0] < 100) {
				dvals[0] = dvals[0]+2000;
			}			
			if (dvals[0] < 2010 || dvals[0] > 2037 || dvals[1] < 0 || dvals[1] > 11 || dvals[2] < 1 || dvals[2] > 31) {
				d.error = createResult("badparam", "Invalid date specified.", "date");
				return (d);
			}
		}
	}
	if (tvals && dvals && tvals.length == 2 && dvals.length == 3 && !isNaN(dvals[0]) && !isNaN(dvals[1]) && !isNaN(dvals[2]) ) {
		date = new Date(dvals[0], dvals[1], dvals[2], tvals[0], tvals[1]);
		if (!isNaN(zonediff) && zonediff != -1) {
			var zd = (zonediff-(-date.getTimezoneOffset()))*60000;
			if (zd != 0) {
				date = new Date(date.getTime()-zd);
			}
		}
	} else {
		if (!tvals || tvals.length != 2) {
			d.error = createResult("badparam", "Invalid time specified.", "time");
		} else {
			d.error = createResult("badparam", "Invalid date specified.", "date");
		}
		return (d);
	}
	if (date) {
		if (!isNaN(zonediff) && zonediff == -1) {
            d.fld = date.getFullYear()+zfill(date.getMonth()+1)+zfill(date.getDate())+"T"+
                zfill(date.getHours())+':'+zfill(date.getMinutes())+':'+zfill(date.getSeconds());
        }else {
            d.fld = date.getUTCFullYear()+zfill(date.getUTCMonth()+1)+zfill(date.getUTCDate())+"T"+
                zfill(date.getUTCHours())+':'+zfill(date.getUTCMinutes())+':'+zfill(date.getUTCSeconds());
        }
		return (d);
	}
	d.error = createResult("badparam", "Invalid date specified.", "date");
	return (d);
}

function zfill(s)
{
	var i = parseInt(s, 10);
	return (zerofill(i, 2, 10));
}

function zerofill(i, count, radix)
{
	var ret = ""+i.toString(radix);
	for (i = count-ret.length; i > 0; i = i - 1) {
		ret =  "0"+ret;
	}
	return (ret);
}

///
// Set a 'field=value' content value.
// Updates a data map in the proper form for sending as post data to a REST API call.
//
function addField(flds, field, value)
{
	if (!field) {
		return (flds);
	}
	if (!flds) {
		flds = new Object();
	}
	flds[field] = value;
	return (flds);
}

///
// Set the focus to the field identified in an error response.
// @param data	The return value from a REST API call that contains an 'error' member.
// @param focusFields	An array of field definitions that relate an 'api' field name to
//			the 'form' field name.
//
function formSetFocus(data, focusFields)
{
	var f = formGetFocus(data, focusFields);
	if (f && f.form && document.getElementsByName(f.form)[0] != undefined) {
		try {
			document.getElementsByName(f.form)[0].focus();
		} catch (err){
			// SCRIPT2110: Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus.
		}
	}
}
///
// Get the record corresponding to the 'field' etected in an error returned by the REST API.
//
// @param data	The return value from a REST API call that contains an 'error' member.
// @param focusFields	An array of field definitions that relate an 'api' field name to
//			the 'form' field name.
// @return
//	- null if the data object had no error or the error did not properly identify the field.
//	- The 'api' and 'form' field names in an object.
function formGetFocus(data, focusFields)
{
	if (data.error && focusFields && focusFields.length > 0) {
		var i;
		for (i = focusFields.length - 1; i >= 0; i = i - 1) {
			if (data.error.field == focusFields[i].api) {
				break;
			}
		}
		return (focusFields[i]);
	}
	return (null);
}

///
// Retrieve JSON content while displaying a spinner and disabling a region.
// Normally JSON content is retrieved by simply calling $.getJSON(), but if the desire is to block user activity
// while the AJAX request is processed, this function can be used instead.
//
// @param formselect	A jQuery selection string that identifies the parent object that contains all of
//			form content being submitted. This object is used to clear the display of the content.
// @param url		The url to post the request to.
// @param processfn	The function to call on completion. If omitted (or null) the form will be enabled
//			and the loadFields() function will be called (if it is defined). If this function returns
//			true the "submitting" class will be left in place so the data entry fields will remain hidden.
//			This can be used when chaining multiple getAjaxSpinner calls.
function getAjaxSpinner(formselect, url, processfn)
{
	return (formPost(formselect, url, null, null, {after:processfn}));
}

///
// Post a REST request to process form content.
// @note: This function initiates the REST request asynchronously and returns immediately.
//
// A spinner is presented in the specified region. When the response is received the spinner is removed and the loadFields()
// function (if any) is called. If an error is returned the text is displayed in a popup, then the spinner is removed and
// the focus is set to the field that created the error.
//
// This default behavior can be changed by specifying additional options.
//
// @param formselect	This required argument specifies A jQuery selection string that identifies the region where the
//			spinner is to be placed. The content of the specified region is disabled during the API call.
// @param url		The url to post the request to.
// @param flds		The input content for the API request (passed in the body of the POST request). This argument
//			may be omitted (or set to null) if there are no data fields.
// @param focusflds	A map from API fields to form fields. This is an array of objects where each entry has
//			the following fields:
//				- api: The name of the field used by the API call.
//				- form: The name of the field in the data entry form.
//				- text: The localized text for the prompt in the form.
//			The map is used to automatically set focus to the
//			proper field when an error message is displayed, and to substitute the prompt text from
//			the form into the returned error text (when appropriate). The first entry in this map
//			will get focus if no field matches the error. This argument is optional, but it should
//			always be provided if the flds argument is specified.
// @param options	An object with additional options that allow changes to the default behavior. In most cases this argument
//			can be omitted by ensuring that the loadFields() function is properly defined.
//	- after(data):
//			A function that is called after the call completes and any error popup has been shown, but before the
//			selected region is enabled again. This function is passed the returned data, which will include
//			an error member if the operation failed.
//			If the function returns 'true' the form will not be enabled (this allows a
//			series of API calls to be performed on a single 'Apply'). If this function
//			is not specified the loadFields() function will be called on success and the form will be enabled.
//	- hideError:	If this boolean property is set to true the after function is called without first processing
//			any errors.
//	- longWait:	If this boolean property is set to true the normal 'Processing...' text is replaced with
//			'This may take a few minutes to complete.'
//
function formPost(formselect, url, flds, focusflds, options)
{
	if (!options) {
		options = new Object();
		if (typeof loadFields == "function") {
			options.after = function(data) {
				if (!data.error) {
					loadFields();
				}
			};
		}
	}
	addProcessingMsg(formselect, options.longWait);

	$(".blockUI.blockMsg.blockElement").css("opacity", 0);
	$(".blockUI.blockOverlay").css("opacity", 0.6);
	
	var retfn =
		function(data) {
			if (data.error && !options.hideError) {
				if (data.error.code == "confirm") {
					// Ask the user to confirm the operation before adding the force flag.
					showError(data, function(confirmed, data, focusflds, formselect, options) {
						if (confirmed) {
							flds = addField(flds, "force", "1");
							
							// necessary for setting quota
							options.after = function(data) {
								// after calling QuotaModify successfuly, need restart
								if(!data.error) {
									location.replace("restart.html");
								}
							}
							
							formPost(formselect, url, flds, focusflds, options);
							
						} else {
							var leaveMessage = false;
							if (options.after) {
								leaveMessage = options.after(data);
							}
							if (!leaveMessage) {
								removeProcessingMsg(formselect);
								if (focusflds) {
									formSetFocus(data, focusflds);
								}
							}
						}
					}, focusflds, "Are you sure you want to continue?", null, formselect, options);
				} else if (data.error.code == "rebooting") {
					location.replace("restart.html");
				} else if(data.error.text && (data.error.text.toLowerCase() == "authentication failure")) {
					location.replace("login.html");
				} else if (data.error.code == "confirmchk") {
					// Ask the user to confirm the operation before adding the force flag.
					showError(data, function(confirmed, data, focusflds, formselect, options) {
						if (confirmed) {
							flds = addField(flds, "force", "1");
							formPost(formselect, url, flds, focusflds, options);
						} else {
							var leaveMessage = false;
							if (options.after) {
								leaveMessage = options.after(data);
							}
							if (!leaveMessage) {
								removeProcessingMsg(formselect);
								if (focusflds) {
									formSetFocus(data, focusflds);
								}
							}
						}
					}, focusflds, null, "confirmchk", formselect, options);
				} else {
					showError(data, function(data, focusflds, formselect, options) {
						var leaveMessage = false;
						if (options.after) {
							leaveMessage = options.after(data);
						}
						if (!leaveMessage) {
							removeProcessingMsg(formselect);
							if (focusflds) {
								formSetFocus(data, focusflds);
							}
						}
					}, focusflds, null, null, formselect, options);
				}
			} else {
				var leaveMessage = false;
				if (options.after) {
					leaveMessage = options.after(data);
				}
				if (!leaveMessage) {
					removeProcessingMsg(formselect);
					if (focusflds) {
						formSetFocus(data, focusflds);
					}
				}
			}
		};
	if (flds) {
		$.post(url, encodeFields(flds), retfn, "json"); 
	} else {
		$.getJSON(url, retfn);
	}
}

//add a "Processing" message to a region.
//el: element in the page that will be replaced by the "Processing" message
function addProcessingMsg(id, longWait) {
	var backgroundColor, imgSrc, spanClass, $p;

	if (!id) { // No spinner if no region is specified
		return;
	}

	//retrieve the background color
	$p = $(id);
	if (!$p) {
		return;
	}

	//check if the element is inside of an accordion - in this case, to correctly present 
	//the Processing spinner, make sure it is not disabled - this is required to fix IE7 styles
	if ($p.hasClass("accordionSectionContent")) {
		$p.removeClass("ui-accordion-disabled").removeClass("ui-state-disabled").addClass("accordion-was-disabled");
	}

	backgroundColor = $p.css("background-color").toLowerCase();
	while ( ((backgroundColor == "transparent") || (backgroundColor == "rgba(0, 0, 0, 0)")) &&  ($p != $("body")) ) {
		$p = $p.parent();
		backgroundColor = $p.css("background-color").toLowerCase();	
	}

	if ((backgroundColor == "rgb(255, 255, 255)") || ((backgroundColor) == "#ffffff")) {
		//white background
		imgSrc = "images/ajax-loader-black.gif";
		spanClass = "loadingInfo";
	} else {
		//dark background
		imgSrc = "images/ajax-loader-white.gif";
		spanClass = "loadingInfoWhite";
	}

	//show a message about saving using the blockUI plugin
	$.blockUI.defaults.overlayCSS.backgroundColor = backgroundColor;
	$.blockUI.defaults.overlayCSS.opacity = .6;
	$.blockUI.defaults.overlayCSS.cursor = "progress";

	var msg='Processing...';
	if (longWait) {
		msg = 'This may take a few minutes to complete.';
	}
	$(id).block({
		message: '<img src="' + imgSrc + '" class="loadImage"/><span class="' + spanClass + '">'+msg+'<\/span>',
		css: {
			border: "none",
			backgroundColor: "transparent",
			cursor: "progress"
		}
	});
}

//remove a message about changing the accordion content
//el: remove the "Processing" message from the 'el' element
function removeProcessingMsg(id) {
	if (!id) { // No spinner if no region is specified
		return;
	}

	$.blockUI.defaults.overlayCSS.cursor = "default";
	$(id).unblock();

	//check if the element is inside of an accordion - in this case, to correctly present 
	//the Processing spinner, make sure it is not disabled - this is required to fix IE7 styles
	var $p = $(id);
	if ($p.hasClass("accordion-was-disabled")) {
		$p.addClass("ui-accordion-disabled").addClass("ui-state-disabled").removeClass("accordion-was-disabled");
	}
}
///
// Determine if a particular flag is present
//
// @param[in] str	The list of flags currently set
// @param[in] flag	The flag to check
//
function isFlagSet(str, flag)
{
	srch = new RegExp("\\b"+flag+"\\b");
	if (str.search(srch) >= 0) {
		return (true);
	}
	return (false);
}
///
// Set a flag in a string
//
// @param[in] str	The list of flags currently set
// @param[in] flag	The flag to set
//
// @return
//	- The new flag string with the specified flag included.
//
function setFlag(str, flag)
{
	srch = new RegExp("\\b"+flag+"\\b");
	if (str.search(srch) >= 0) {
		// The flag is already set, so do nothing
		return (str);
	}
	if (str) {
		return (str+"|"+flag);
	}
	return (flag);
}
///
// Set a flag in a string
//
// @param[in] str	The list of flags currently set
// @param[in] flag	The flag to set
//
// @return
//	- The new flag string with the specified flag included.
//
function clearFlag(str, flag)
{
	srch = new RegExp("\\b"+flag+"\\b");
	var i = str.search(srch);
	if (i < 0) {
		// The flag is not set, so do nothing
		return (str);
	}
	if (i == 0) {
		str = str.substr(flag.length);
		if (str.charAt(0) == "|") {
			str = str.substr(1);
		}
		return (str);
	}
	return (str.substr(0, i-1)+str.substr(i+flag.length));
}

///
// Get a string date based on the locale
//
// @param[in] date	A JavaScript date object.
// @param[in] zonediff	(Optional) An alternate time zone difference to use (in minutes).
//
// @return
//	- The string 2010/02/24 formatted appropriately for the current locale.
function cpDate(date, zonediff)
{
	if (!isNaN(zonediff)) {
		// This isn't perfect since around the DST shift the time zone difference can be thrown off
		date = new Date(date.getTime()+((zonediff-(-date.getTimezoneOffset()))*60000));
	}

	var mon = ""+(date.getMonth()+1);
	var mday = ""+(date.getDate());

	if (mday.length < 2) {
		mday = "0"+mday;
	}
	if (mon.length < 2) {
		mon = "0"+mon;
	}
	return("%2$d/%1$d/%3$d".replace("%1\$d", mday).replace("%2\$d", mon).replace("%3$d", ""+date.getFullYear()));
}

function isAmPm(dstr)
{
	if (dstr == undefined) {
		dstr = "%1$d:%2$02d pm";
	}
	if (dstr.indexOf(" ") > 0) {
		return (true);
	}
	return (false);
}

// Get a string time based on the locale
//
// @param[in] date	A JavaScript date object.
// @param[in] zonediff	(Optional) An alternate time zone difference to use (in minutes).
//
// @return
//	- The string "11:24 pm" formatted appropriately for the current locale (this may be 24 hour time).
function cpTime(date, zonediff)
{
	if (!isNaN(zonediff)) {
		// This isn't perfect since around the DST shift the time zone difference can be thrown off
		date = new Date(date.getTime()+((zonediff-(-date.getTimezoneOffset()))*60000));
	}
	var hr = date.getHours();
	var mn = ""+date.getMinutes();
	var dstr = "%1$d:%2$02d am";

	if (mn.length < 2) {
		mn = "0"+mn;
	}
	if (dstr.indexOf(" ") > 0) {
		// If am/pm for this locale, adjust the hours
		if (hr > 11) {
			dstr = "%1$d:%2$02d pm";
			if (hr > 12) {
				hr = hr - 12;
			}
		} else {
			if (hr == 0) {
				hr = 12;
			}
		}
	}
	return (dstr.replace("%1\$d", hr).replace("%2\$02d", mn));
}

//default values to help bubbles
var SHOW_DELAY = 750; //show delay
var SHOW_DELAY_FAST = 100; //show delay fast

//this is a similar function, but the bubble is associated to a[title] elements
//el: the element
function helpBubblesTitle(el)
{

	var tooltip = "topLeft";
	var x = 10;

	$(el + "[title]").qtip({
		style: "help",
		position: {
			target: "mouse",
			corner: {
				target: tooltip,
				tooltip: tooltip
			},
			adjust: {
				screen: true,
				x: x,
				y: 15,
				mouse: false
			}
		},
		show: { delay: SHOW_DELAY, effect: "slide", solo: false },
		hide: {
			effect: "slide"
		}
	});
}

//this is to be used by categories/features icons
//include help bubble in the element el inside of the frame/content fr
//typically, element would be  "a[title]" and the frame content "main content frame.body"
function helpBubblesCategories(el)
{
	var tooltip = "topLeft";

	//lets change typical titles to qtip bubbles
	//should dynamically set the corner and the tip	
	$(el).qtip({
		style: {
			'text-align':'left',
			name: "categories"
		},
		position: {
			target: false,
			corner: {
				target: "center",
				tooltip: tooltip
			},
			adjust: {
				mouse: false,
				screen: false,
				x: 10,
				y: 15,
				scroll: true,
				resize: true
			},
			container: $("body")
		},
		show: { delay: 750, effect: "fade", solo: true},
		hide: { effect: "fade", fixed: true }
	});
}


//this function calls a help bubble on a h2 element
//hTopic: the help topic id to be displayed
function helpBubblesLM(hTopic)
{
	//associate the help bubble with the page title
	helpBubblesEl($("a#titleHelp"), hTopic);
}

//this is the most generic help bubble, with three input arguments
//el: the element to associate the help bubble
//hTopic: the topic ID of the help content to be displayed
//delay: The delay to use before displaying the topic. If not included, a default delay is used.
function helpBubblesEl(el, hTopic, delay)
{
	if (!delay) {
		delay = SHOW_DELAY_FAST;
	}
	try {
	$.getJSON(getRestUrl("HelpInfo", {"id":hTopic}), function(data) {
		if (!data.error) {
			var b = "";
			for (var i = 0; i < data.text.length; i++) {
				b = b+"<p>"+data.text[i]+"<p>";
			}
			
			var tooltip = "topLeft";
			var target = "bottomRight";
			var x = -10;

			//associate the help bubble with the page title
			$(el).qtip({
				content: {
					text: b,
					title: {
						text: data.title,
						button: ""
					}
				},
				style: {
					name: "help", 
					width: {
						min: 150, max: 375
					},
					title: {
						'text-align': 'left'
					},
					button: {
						'float': 'right'
					}
				},
				position: {
					target: $(el),
					corner: {
						target: target,
						tooltip: tooltip
					},
					adjust: {
						screen: true,
						x: x,
						y: 10,
						mouse: false,
						scroll: true,
						resize: true
					}
				},
				show: { delay: delay, effect: "slide", solo: true },
				hide: {
					effect: "slide",
					when: { event: "mouseout" }
				}
			});
		}
	});
	} catch(e) {}
}

//default options to the open new window function
var W_POPUP = 10; //opens a popup window (1024x768), without the navigation bar and the menu
var W_FULL = 20; //opens a new browser window with all browser's elements enabled and visible
var W_EXPLORER = 30; //opens a popup window (1024x768), without the navigation bar and the menu
var W_SETUP =40;
var W_TPA = 50;

//this function creates a new browser window to popup with most of the configuration options pre-defined
//it may not work if the browser (Chrome and Opera, specially) is set to open tabs instead of windows
//pURL: the page URL to open
//pTarget: name of the window
//wopts: type of the window
//noFocus: if true, don't focus the new window at the moment
function openNewWindow(pURL, pTarget, wopts, noFocus)
{
	var extras;

	switch(wopts) {
		case W_POPUP:
			extras = "location=no,width=1024,height=768,scrollbars=yes,resizable=yes";
			break;
		case W_FULL:
			extras = "location=yes,menubar=yes,toolbar=yes,scrollbars=yes,resizable=yes";
			break;
		case W_EXPLORER: 
			extras = "location=no,width=910,height=510,resizable=yes";
			break;
		case W_SETUP:
			extras = "location=no,left=250,top=170,width=670,height=480,scrollbars=yes";
			break;
		case W_TPA:
			extras = "location=no,menubar=yes,toolbar=yes,scrollbars=yes,resizable=yes";
			break;
		default:
			extras = "";
	}

	var nw = window.open(pURL, pTarget, extras);
	if (!noFocus && nw != null && nw != undefined) {
		nw.focus();
	}

	return (nw);
}

//simple dialog types
var YESNO = 10;
var APPLYCANCEL = 20;
var OKCANCEL = 30;
var JOINCANCEL = 40;
var SENDCANCEL = 50;
var UPLOADCANCEL = 60;

//create a generic jQuery-ui confirmation (Yes/No buttons) dialog with pre-defined, standard settings
//id: dialog ID - must be an unique id
//dTitle: dialog title
//msg: confirmation message string - it accepts html tags
//dw: dialog width (in pixels) - if empty "", use default size
//dh: dialog height (in pixels) - if empty "", use default size
//fApply: function to call when the user clicks "Yes/OK/Apply"
//fCancel: function to call when the user clicks "No/Cancel/Cancel"
//to open it, use $("#id").dialog("open");
//to close it, use $("#id").dialog("close");
//button type: Apply/Cancel | OK/Cancel | Yes/No
function createConfDialog(id, dTitle, msg, dw, dh, fApply, fCancel, type)
{

	createSimpleDialog(id, dTitle, msg, dw, dh);

	switch(type) {
		case YESNO:
			$("#" + id).dialog("option", "buttons", {
				"Yes": function() { 
					if (fApply()) {
						$(this).dialog("close"); 
					}
				},
				"No": function() {
					if (fCancel()) {
						$(this).dialog("close"); 
					}
				}
			});
			break;
		case APPLYCANCEL:
			$("#" + id).dialog("option", "buttons", {
				"Apply": function() { 
					if (fApply()) {
						$(this).dialog("close");
					}
				},
				"Cancel": function() {
					if (fCancel()) {
						$(this).dialog("close");
					}
				}
			});
			break;
		case OKCANCEL:
			$("#" + id).dialog("option", "buttons", {
				"OK": function() { 
					if (fApply()) {
						$(this).dialog("close");
					}
				},
				"Cancel": function() {
					if (fCancel()) {
						$(this).dialog("close");
					}
				}
			});
			break;
		case JOINCANCEL:
			$("#" + id).dialog("option", "buttons", {
				"Join": function() { 
					if (fApply()) {
						$(this).dialog("close");
					}
				},
				"Cancel": function() {
					if (fCancel()) {
						$(this).dialog("close");
					}
				}
			});
			break;
		case SENDCANCEL:
			$("#" + id).dialog("option", "buttons", {
				"Send": function() { 
					if (fApply()) {
						$(this).dialog("close");
					}
				},
				"Cancel": function() {
					if (fCancel()) {
						$(this).dialog("close");
					}
				}
			});
			break;
		case UPLOADCANCEL:
			$("#" + id).dialog("option", "buttons", {
				"Upload": function() { 
					if (fApply()) {
						$(this).dialog("close");
					}
				},
				"Cancel": function() {
					if (fCancel()) {
						$(this).dialog("close");
					}
				}
			});
			break;
	}
}

//this dialog doesn't have margins and confirmation buttons - but it has a close button
//it is typically used by slideshows
function createBorderlessDialog(id, dTitle, msg, dw, dh)
{
	createSimpleDialog(id, dTitle, msg, dw, dh);

	//adjust the styles
	//margin
	$("#" + id).css("margin-top", "0px");
	$("#" + id).css("margin-right", "0px");
	$("#" + id).css("margin-bottom", "0px");
	$("#" + id).css("margin-left", "0px");

	//padding
	$("#" + id).css("padding-top", "0px");
	$("#" + id).css("padding-right", "0px");
	$("#" + id).css("padding-bottom", "0px");
	$("#" + id).css("padding-left", "0px");

	//overflow	
	$("#" + id).css("overflow", "hidden");
	
	//borders
	$("#" + id).parent().css("border", "0px none #000000");

	//close button
	$("#" + id).prev().children("a.ui-dialog-titlebar-close").css("display", "block");
}

//this function fixes the styles required by a slideshow dialog
function fixSlideshowDlgStyles(id)
{
	//slideshow content
	$("#" + id).children("div.slideshow").children("div.slideshow-content").css("border-left", "1px solid #7f7f7f");
	$("#" + id).children("div.slideshow").children("div.slideshow-content").css("border-right", "1px solid #7f7f7f");
	$("#" + id).children("div.slideshow").children("div.slideshow-content").css("border-bottom", "1px solid #7f7f7f");

	//to make the background darker
	$("#" + id).parent().prev().css("background","#101010 none repeat scroll 0 0");
	$("#" + id).parent().prev().css("opacity","0.5");
}

function isQuote(c)
{
	if (c == '"' || c == "'" || c == "“" || c == "”" || c == "「" || c == "」" || c == "‎" || c == "‏") {
		return (true);
	}
	return (false);
}
function lastPart(s)
{
	var i = s.lastIndexOf(':');
	if (i < 0) {
		return (s);
	}
	return (s.substr(i+1));
}
function getStart(txt, i, field, m)
{
	if (m == field) {
		return (i);
	}
	for (j = field.lastIndexOf(':'), i--; i > 0 && j >= 0 && txt.charAt(i) == field.charAt(j); i--, j--) {
	}
	if (isQuote(txt.charAt(i))) {
		return (i+1);
	}
}

///
// Show an error message in an 'Info' dialog
// Unlike alert(), this function returns immediately and the dialog runs in a separate thread.
//
// @param data		The object containing the error object with the message
// @param okfn		The function to execute when the user clicks 'OK'. This takes no arguments and normally
//			does not return anything. If this function returns 'true' the dialog will not close. If
//			this argument is omitted the dialog will close without any additional processing.
// @param focusFields	An array of field definitions that relate an 'api' field name to
//			the 'form' field name.
// @param yesnomsg:	If specified, a Yes/No dialog is shown instead of an OK dialog. A boolean value is
//			passed to the okfn (set true if the user confirms).
// @param confirmchk: If specified, add a confirmation checkbox to the {OK| yes/no} dialog. okfn will be 
// called only if the checkbox is checked.
//
function showError(data, okfn, focusFields, yesnomsg, confirmchk, formselect, options)
{
	var txt = data.error.text;

	if (focusFields && data.error.field) {
		var f = formGetFocus(data, focusFields);
		if (f && f.text) {
			var i = 1;
			var m = lastPart(data.error.field);
			while (i > 0) {
				var j = txt.substr(i).search("\\b"+m+"\\b");
				if (j < 0) {
					break;
				}
				i = i + j;
				j = getStart(txt, i, data.error.field, m);
				if (j > 0) {
					txt = txt.substr(0, j)+f.text+txt.substr(i+m.length);
				}
				i = i+m.length;
			}
		}
	}
	if (confirmchk != undefined) {
		showConfirmChk(txt, yesnomsg, okfn, data, focusFields, formselect, options);
	} else if (yesnomsg) {
		showConfirm(txt, yesnomsg, okfn, data, focusFields, formselect, options);
	} else {
		showErrorStr(txt, okfn, data, focusFields, formselect, options);
	}
}

///
// Show a string in an error dialog.
// Unlike alert(), this function returns immediately and the dialog runs in a separate thread.
//
// errtext:	The text to display.
// okfn:	The function to execute when the user clicks 'OK'. This takes no arguments and normally
//		does not return anything. If this function returns 'true' the dialog will not close. If
//		this argument is omitted the dialog will close without any additional processing.
//
function showErrorStr(errtext, okfn, data, focusFields, formselect, options)
{
	var xmsg = xmlQuote(errtext);
	showErrorStrNoQuote(xmsg, okfn, data, focusFields, formselect, options);
}

///
//Show a string in an error dialog.
//Unlike alert(), this function returns immediately and the dialog runs in a separate thread.
//
//xmsg:	The text to display. No xmlQuote function is called to this string.
//okfn:	The function to execute when the user clicks 'OK'. This takes no arguments and normally
//		does not return anything. If this function returns 'true' the dialog will not close. If
//		this argument is omitted the dialog will close without any additional processing.
//
function showErrorStrNoQuote(xmsg, okfn, data, focusFields, formselect, options)
{
	if ($("#errokdlgmsg").length) {
		// Just change the text if the dialog already exists
		$("#errokdlgmsg").html(xmsg);
	} else {
		// Otherwise create the dialog
		createSimpleDialog("errokdlg", "Error", "<p id='errokdlgmsg'>"+xmsg+"</p>", 300, "auto");
		$("#errokdlg").dialog("option", "buttons", {
			"OK": function() {
				if (xmsg.indexOf("Authentication failure") != -1) {
					window.location = "login.html";
				} else {
					var data = $(this).data('data');
					var focusFields = $(this).data('focusFields');
					var formselect = $(this).data('formselect');
					var options = $(this).data('options');
					if (okfn && okfn(data, focusFields, formselect, options)) {
						return;
					}
					$(this).dialog("close");
				}
			}});
	}
	$("#errokdlg").data('data', data);
	$("#errokdlg").data('focusFields', focusFields);
	$("#errokdlg").data('formselect', formselect);
	$("#errokdlg").data('options', options);
	$("#errokdlg").dialog("open");
}
///
//Show a string in a confirmation dialog.
//Unlike alert(), this function returns immediately and the dialog runs in a separate thread.
//
//msg:		General text to display.
//yesnomsg:	A confirmation prompt to display.
//yesnofn:	The function to execute when the user clicks either 'Yes' or 'No'. This takes a boolean argument
//		(true if the user clicks 'Yes') and normally does not return anything. If this function returns
//		'true' the dialog will not close.
//
function showConfirm(msg, yesnomsg, yesnofn, data, focusFields, formselect, options)
{
	if (!yesnomsg) {
		yesnomsg = "Are you sure you want to continue?";
	}
	if ($("#errconfirmdlgmsg").length) {
		// Just change the text if the dialog already exists
		$("#errconfirmdlgmsg").html(xmlQuote(msg));
		$("#errconfirmdlgyesno").html(xmlQuote(yesnomsg));
	} else {
		// Otherwise create the dialog
		createSimpleDialog("errconfirmdlg", "Confirmation Required", "<p id='errconfirmdlgmsg'>"+xmlQuote(msg)+"</p><p id='errconfirmdlgyesno'>"+xmlQuote(yesnomsg)+"</p>", 300, "auto");
		$("#errconfirmdlg").dialog("option", "buttons", {
			"Yes": function() {
				var data = $(this).data('data');
				var focusFields = $(this).data('focusFields');
				var formselect = $(this).data('formselect');
				var options = $(this).data('options');
				
				// close de dialog before call yesnofn, which will call formPost
				$(this).dialog("close");
				
				if (yesnofn && yesnofn(true, data, focusFields, formselect, options)) {
					return;
				}
				
			},
			"No": function() {
				var data = $(this).data('data');
				var focusFields = $(this).data('focusFields');
				var formselect = $(this).data('formselect');
				var options = $(this).data('options');
				if (yesnofn && yesnofn(false, data, focusFields, formselect, options)) {
					return;
				}
				$(this).dialog("close");
			}});
	}

	$("#errconfirmdlg").data('data', data);
	$("#errconfirmdlg").data('focusFields', focusFields);
	$("#errconfirmdlg").data('formselect', formselect);
	$("#errconfirmdlg").data('options', options);
	$("#errconfirmdlg").dialog("open");
}

///
//Show a string in a confirmation dialog.
//Unlike alert(), this function returns immediately and the dialog runs in a separate thread.
//
//msg:		General text to display.
//yesnomsg:	A confirmation prompt to display.
//yesnofn:	The function to execute when the user clicks either 'Yes' or 'No'. This takes a boolean argument
//		(true if the user clicks 'Yes') and normally does not return anything. If this function returns
//		'true' the dialog will not close.
//
function showConfirmChk(msg, yesnomsg, yesnofn, data, focusFields, formselect, options)
{
	(yesnomsg) ? yesnomsg : "";
	
	var content = '<p id="errconfirmchkdlgmsg">' + xmlQuote(msg) + '</p><p id="errconfirmdlgyesno">' + xmlQuote(yesnomsg)+ '</p>' +
				'<input id="errconfirmchkdlg_checkbox" type="checkbox"><\/input>' +
				'<label for="errconfirmchkdlg_checkbox">Check this box to continue.<\/label>';
	
	// Otherwise create the dialog
	createSimpleDialog("errconfirmchkdlg", "Confirmation Required", content, 300, "auto");

	if (!yesnomsg) {
		$("#errconfirmchkdlg").dialog("option", "buttons", {
			"OK": function() {
				if (msg.indexOf("Authentication failure") != -1) {
					window.location = "login.html";
				} else {
					var checked = ($("input:checkbox#errconfirmchkdlg_checkbox").is(":checked")) ? true : false;
					var data = $(this).data('data');
					var focusFields = $(this).data('focusFields');
					var formselect = $(this).data('formselect');
					var options = $(this).data('options');
					if (yesnofn && yesnofn(checked, data, focusFields, formselect, options)) {
						$(this).dialog("destroy");	
						$("#errconfirmchkdlg").remove();
						return;
					}										
					$("#errconfirmchkdlg").remove();
				}
			}}
		);
	} else {
		$("#errconfirmchkdlg").dialog("option", "buttons", {
			"Yes": function() {
				var checked = ($("input:checkbox#errconfirmchkdlg_checkbox").is(":checked")) ? true : false;
				var data = $(this).data('data');
				var focusFields = $(this).data('focusFields');
				var formselect = $(this).data('formselect');
				var options = $(this).data('options');
				if (yesnofn && yesnofn(checked, data, focusFields, formselect, options)) {
					$(this).dialog("destroy");	
					$("#errconfirmchkdlg").remove();
					return;
				}				
				$("#errconfirmchkdlg").remove();				
			},
			"No": function() {
				var data = $(this).data('data');
				var focusFields = $(this).data('focusFields');
				var formselect = $(this).data('formselect');
				var options = $(this).data('options');
				if (yesnofn && yesnofn(false, data, focusFields, formselect, options)) {
					$(this).dialog("destroy");	
					$("#errconfirmchkdlg").remove();
					return;
				}				
				$("#errconfirmchkdlg").remove();
			}}
		);
	}

	$("#errconfirmchkdlg").data('data', data);
	$("#errconfirmchkdlg").data('focusFields', focusFields);
	$("#errconfirmchkdlg").data('formselect', formselect);
	$("#errconfirmchkdlg").data('options', options);
	$("#errconfirmchkdlg").dialog("open");
}

//this function is pretty similar to the createSimpleDialog, but with an OK button
function createInfoDialog(id, dTitle, msg, dw, dh)
{
	createSimpleDialog(id, dTitle, msg, dw, dh);

	$("#" + id).dialog("option", "buttons", {
		"OK": function() { 
			$(this).dialog("close");
		}
	});
}

//use this function to create a pretty simple dialog, without buttons
function createSimpleDialog(id, dTitle, msg, dw, dh)
{

	//the dialog id must be unique to create it
	if ($(id).length == 0) {
		$("body").append(
			'<div id="' + id + '" title="' + dTitle + '" style="display:none;">' +
				msg +
			'</div>'
		);

		$("#" + id).dialog({
			autoOpen: false,
			bgiframe: true,
			closeText: "Close",
			draggable: true,
			modal: true,
			resizable: false,
			height: dh,
			width: dw
		});
		$("#ui-dialog-title-"+id).addClass("ellipsis");
		var width = $("#ui-dialog-title-"+id).parent().parent().width();		
		$("#ui-dialog-title-"+id).width(width);				
		$("#ui-dialog-title-"+id).parent().addClass("ellipsis");
		
	}
}

//use this function to update the message/content of a dialog
function changeConfMsgDialog(id, msg)
{
	$("#" + id).html(msg);
}

//destroy and completey remove a dialog (if needed)
//id: the dialog id
function destroyDialog(id)
{
	$("#" + id).dialog("destroy").remove();
}

//repeat call: retrieve the same url in a specified time interval, until the specified flag is true
//furl: url to be retrieved
//args: arguments to be passed through the url
//successfn: callback function to be executed in case of success
//failfn: callback function to be executed in case of failure
//isStopped: to stop the calls
function repeatCall(furl, args, successfn, failfn, isStopped) {
	//set the interval between the requests
	//var interval = 60000; //1 minute
	var interval = 1000; //1 second

	if (!isStopped) {
		$.ajax({	
			url: getRestUrl(furl, args),
			success: function(data) {
				var stopNow = successfn(data); //check if we need to make another call
				var t = setTimeout(
					'repeatCall("' + furl + '", ' + args + ', ' + successfn + ', ' + failfn + ', ' + stopNow + ');',
					interval);
				return (true);
			}
		});
	}
}

function isHomePage()
{
    return (document.getElementById('this_is_home_page') != null);
}

function Ellipsize(str, max_len)
{
	var new_str = str;

	if (undefined != str && '' != str) {
		var str_len = str.length;
		if (str_len > max_len) {
			new_str = str.substring(0, max_len - 3) + '...';
		}
	}

	return (new_str);
}

function setBannerInfo(deviceName)
{
	if (!isHomePage()) {
		var devName = (deviceName ? deviceName : "Contab");
		var modelName = "Home Media Network Hard Drive";
		var len;
		len = 60 - modelName.length > 30 ? 60 - modelName.length : 30;
		var fmt_str = Ellipsize(devName, len) + " - " + Ellipsize(modelName, len > devName.length ? 60 - devName.length : 60 - len);
		var devEl = document.getElementById("devicename");

		if (devEl) {
			devEl.innerHTML = fmt_str;
			devEl.title = devName;
		}
	}
}

$(document).ready(function() {

	var tooltip = "topLeft";
	var x = 10;

	//create a new bubble style to be used in help bubbles
	$.fn.qtip.styles.help = {
		background: "#ffffff",
		color: "#000000",
		textAlign: "justify",
		padding: "10px",
		'font': 'normal 12px Arial, Helvetica, Sans-Serif',
		title: {
			"background": "#000000 url(images/bg-titles.png) repeat-x",
			"font-weight": "bold"
		},
		button: {
			"background": "transparent url(images/actiondrkbtns.png) no-repeat scroll 0 240px",
			color: "#dddddd",
			height: 20,
			width: 20
		},
		border: {
			width: 1,
			radius: 1,
			color: "#7a7a7a"
		},
		tip: {
			corner: tooltip,
			size: { x: 16, y: 16 }
		}
	};

	//create a new bubble style to be used in help bubbles for the features/categories icons
	$.fn.qtip.styles.categories = {
		background: "#ffffff",
		color: "#000000",
		textAlign: 'left',
		padding: 4,
		'font': 'normal 12px Arial, Helvetica, Sans-Serif',
		width: { max: 180},
		border: {
			width: 2,
			radius: 2,
			color: "#7a7a7a"
		},
		tip: {
			corner: tooltip,
			size: { x: 16, y: 16 }
		}
	};

	//create a new bubble style to be used in dashboard charts
	$.fn.qtip.styles.charttooltip = {
		background: "transparent",
		color: "#ffffff",
		textAlign: "center",
		padding: 4,
		border: {
			width: 2,
			radius: 2,
			color: "#7a7a7a"
		},
		width: {
			min: 120, max: 250
		},
		tip: "bottomMiddle"
	};

	setBannerInfo();
});

///
// Make an arbitrary string safe for use as an XML ID
// The original string can be retrieved using idUnquote().
// The '-' is used to add the unicode character value for any character that is not a valid
// character within an XML identifier.
//
// @param str	The string to use as the identifier
//
// @return The modified string with any necessary quoting.
//
function idQuote(str)
{
	var validchars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_.:";
	var ret = "I";

	for (var i = 0; i < str.length; i++) {
		var c = str.substr(i, 1);
		if (validchars.indexOf(c) >= 0) {
			ret += c;
		} else {
			ret += '-'+zerofill(c.charCodeAt(0), 4, 16);
		}
	}
	return (ret);
}

///
// Get the original string from a quoted id.
//
// @param str	The quoted ID string.
//
// @return The original string used to create the quoted ID.
function idUnquote(str)
{
	ret = "";

	if (str.substr(0, 1) != "I") {
		// This value wasn't quoted, so don't unquote it
		return (str);
	}
	for (var i = 1; i < str.length; i++) {
		var c = str.substr(i, 1);

		if (c == '-') {
			var val = str.substr(i+1, 4);
			i = i + 4;
			ret += String.fromCharCode(parseInt(val, 16));
		} else {
			ret += c;
		}
	}
	return (ret);
}

// jqSafe - Make an ID string safe to use in a jQuery select string.
//	A few of the characters that are valid in an ID string are not valid in a jQuery
//	selection string unless you escape them.
//	This function should be used whenever an arbitrary ID is used in a jQuery selection string.
//
//	To make an arbitrary string a valid XML ID, use the idQuote() function.
function jqSafe(str) {
	var ret = str.replace(/(:|\.)/g, '\\$1');
	return ret;
}


