/*
 * ***** BEGIN LICENSE BLOCK *****
 * Version: ZAPL 1.1
 * 
 * The contents of this file are subject to the Zimbra AJAX Public
 * License Version 1.1 ("License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.zimbra.com/license
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is: Zimbra AJAX Toolkit.
 * 
 * The Initial Developer of the Original Code is Zimbra, Inc.
 * Portions created by Zimbra are Copyright (C) 2005 Zimbra, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK *****
 */

/**
 * AjxUtil - static class with some utility methods. This is where to
 * put things when no other class wants them.
 *
 * 12/3/2004 At this point, it only needs AjxEnv to be loaded.
 */
function AjxUtil () {
};

AjxUtil.FLOAT_RE = /^[+\-]?((\d+(\.\d*)?)|((\d*\.)?\d+))([eE][+\-]?\d+)?$/;
AjxUtil.NOTFLOAT_RE = /[^\d\.]/;
AjxUtil.NOTINT_RE = /[^0-9]+/;
AjxUtil.LIFETIME_FIELD = /^([0-9])+([dhms])?$/;
AjxUtil.isSpecified 	= function(aThing) { return ((aThing !== void 0) && (aThing !== null)); };
AjxUtil.isUndefined 	= function(aThing) { return (aThing === void 0); };
AjxUtil.isNull 		= function(aThing) { return (aThing === null); };
AjxUtil.isBoolean 	= function(aThing) { return (typeof(aThing) == 'boolean'); };
AjxUtil.isString 	= function(aThing) { return (typeof(aThing) == 'string'); };
AjxUtil.isNumber 	= function(aThing) { return (typeof(aThing) == 'number'); };
AjxUtil.isObject 	= function(aThing) { return ((typeof(aThing) == 'object') && (aThing !== null)); };
AjxUtil.isArray 		= function(aThing) { return AjxUtil.isInstance(aThing, Array); };
AjxUtil.isFunction 	= function(aThing) { return (typeof(aThing) == 'function'); };
AjxUtil.isDate 		= function(aThing) { return AjxUtil.isInstance(aThing, Date); };

AjxUtil.isNumeric 	= function(aThing) { return (!isNaN(parseInt(aThing)) && AjxUtil.FLOAT_RE.test(aThing) && !AjxUtil.NOTFLOAT_RE.test(aThing)); };
AjxUtil.isInteger	= function(aThing) { return (AjxUtil.isNumeric(aThing) && !AjxUtil.NOTINT_RE.test(aThing)); };
AjxUtil.isNonNegativeInteger = function (aThing) {
	var retVal = (AjxUtil.isNumeric(aThing) && AjxUtil.isInteger(aThing) && (parseInt(aThing) >= 0) ); 
	return retVal;
};
AjxUtil.isLifeTime = function (aThing) { return AjxUtil.LIFETIME_FIELD.test(aThing); };

// REVISIT: Should do more precise checking. However, there are names in
//			common use that do not follow the RFC patterns (e.g. domain
//			names that start with digits).
AjxUtil.IP_ADDRESS_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?$/;
AjxUtil.DOMAIN_NAME_SHORT_RE = /^[A-Za-z0-9\-]{2,}$/;
AjxUtil.DOMAIN_NAME_FULL_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,}){1,}$/;
AjxUtil.HOST_NAME_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*$/;
AjxUtil.EMAIL_SHORT_RE = /^[^@\s]+$/;
AjxUtil.EMAIL_FULL_RE = /^[^@\s]+@[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*$/;

AjxUtil.isIpAddress = function(s) {
	return AjxUtil.IP_ADDR_RE.test(s);
}
AjxUtil.isDomain = function(s) {
	return AjxUtil.DOMAIN_RE.test(s);
}
AjxUtil.isDomainName = function(s, shortMatch) {
	return shortMatch ? AjxUtil.DOMAIN_NAME_SHORT_RE.test(s) : AjxUtil.DOMAIN_NAME_FULL_RE.test(s);
}
AjxUtil.isHostName = function(s) {
	return AjxUtil.HOST_NAME_RE.test(s);
}
AjxUtil.isEmailAddress = function(s, nameOnly) {
	return nameOnly ? AjxUtil.EMAIL_SHORT_RE.test(s) : AjxUtil.EMAIL_FULL_RE.test(s);
}

AjxUtil.SIZE_GIGABYTES = "GB";
AjxUtil.SIZE_MEGABYTES = "MB";
AjxUtil.SIZE_KILOBYTES = "KB";
AjxUtil.SIZE_BYTES = "B";

/**
 * Formats a size (in bytes) to the largest whole unit. For example,
 * AjxUtil.formatSize(302132199) returns "288 MB".
 *
 * @param size      The size (in bytes) to be formatted.
 * @param round     True to round to nearest integer. Default is true.
 * @param fractions Number of fractional digits to display, if not rounding.
 *                  Trailing zeros after the decimal point are trimmed.
 */
AjxUtil.formatSize = 
function(size, round, fractions) {
	if (round == null) round = true;
	if (fractions == null) fractions = 20; // max allowed for toFixed is 20

	var units = AjxUtil.SIZE_BYTES;
	if (size >= 1073741824) {
		size /= 1073741824;
		units = AjxUtil.SIZE_GIGABYTES;
	}
	else if (size >= 1048576) {
		size /= 1048576;
		units = AjxUtil.SIZE_MEGABYTES;
	}
	else if (size > 1023) {
		size /= 1024;
		units = AjxUtil.SIZE_KILOBYTES;
	}

	var formattedSize = round ? Math.round(size) : size.toFixed(fractions).replace(/\.?0+$/,"");
	var formattedUnits = ' '+units;
	
	return formattedSize + formattedUnits;
}

/**
 * Formats a size (in bytes) to a specific unit. Since the unit size is
 * known, the unit is not shown in the returned string. For example,
 * AjxUtil.formatSizeForUnit(302132199, AjxUtil.SIZE_MEGABYTES, false, 2) 
 * returns "288.13".
 *
 * @param size      The size (in bytes) to be formatted.
 * @param units     The unit of measure.
 * @param round     True to round to nearest integer. Default is true.
 * @param fractions Number of fractional digits to display, if not rounding.
 *                  Trailing zeros after the decimal point are trimmed.
 */
AjxUtil.formatSizeForUnits = function(size, units, round, fractions) {
	if (units == null) units = AjxUtil.SIZE_BYTES;
	if (round == null) round = true;
	if (fractions == null) fractions = 20; // max allowed for toFixed is 20

	switch (units) {
		case AjxUtil.SIZE_GIGABYTES: { size /= 1073741824; break; }
		case AjxUtil.SIZE_MEGABYTES: { size /= 1048576; break; }
		case AjxUtil.SIZE_KILOBYTES: { size /= 1024; break; }
	}
	
	var formattedSize = round ? Math.round(size) : size.toFixed(fractions).replace(/\.?0+$/,"");
	return formattedSize;
}

/**
 * Performs the opposite of AjxUtil.formatSize in that this function takes a 
 * formatted size.
 *
 * @param units Unit constant: "GB", "MB", "KB", "B". Must be specified 
 *              unless the formatted size ends with the size marker, in
 *				which case the size marker in the formattedSize param
 *				overrides this parameter.
 */
AjxUtil.parseSize = function(formattedSize, units) {
	// NOTE: Take advantage of fact that parseFloat ignores bad chars
	//       after numbers
	var size = parseFloat(formattedSize.replace(/^\s*/,""));

	var marker = /[GMK]?B$/i;
	var result = marker.exec(formattedSize);
	if (result) {
		//alert("units: "+units+", result[0]: '"+result[0]+"'");
		units = result[0].toUpperCase();
	}
	
	switch (units) {
		case AjxUtil.SIZE_GIGABYTES: size *= 1073741824; break;
		case AjxUtil.SIZE_MEGABYTES: size *= 1048576; break; 
		case AjxUtil.SIZE_KILOBYTES: size *= 1024; break;
	}
	
	//alert("AjxUtil#parseSize: formattedSize="+formattedSize+", size="+size);
	return size;
}

AjxUtil.isInstance = 
function(aThing, aClass) { 
	return !!(aThing && aThing.constructor && (aThing.constructor === aClass)); 
};

AjxUtil.assert = function(aCondition, aMessage) {
	if (!aCondition && AjxUtil.onassert) AjxUtil.onassert(aMessage);
};

AjxUtil.onassert = 
function(aMessage) {
	// Create an exception object and set the message
	var myException = new Object();
	myException.message = aMessage;
	
	// Compile a stack trace
	var myStack = new Array();
	if (AjxEnv.isIE5_5up) {
		// On IE, the caller chain is on the arguments stack
		var myTrace = arguments.caller;
		while (myTrace) {
		    myStack[myStack.length] = myTrace.callee;
	    	myTrace = myTrace.caller;
		}
	} else {
		try {
			var myTrace = arguments.callee.caller;
			while (myTrace) {
				myStack[myStack.length] = myTrace;
				if (myStack.length > 2) break;
				myTrace = myTrace.caller;
		    }
		} catch (e) {
		}
	}
	myException.stack = myStack;
	
	// Alert with the message and a description of the stack
	var stackString = '';
	var MAX_LEN = 170;
	for (var i = 1; i < myStack.length; i++) {
		if (i > 1) stackString += '\n';
		if (i < 11) {
			var fs = myStack[i].toString();
			if (fs.length > MAX_LEN) {
				fs = fs.substr(0,MAX_LEN) + '...';
				fs = fs.replace(/\n/g, '');
			}
			stackString += i + ': ' + fs;
		} else {
			stackString += '(' + (myStack.length - 11) + ' frames follow)';
			break;
		}
	}
	alert('assertion:\n\n' + aMessage + '\n\n---- Call Stack ---\n' + stackString);
	
	// Now throw the exception
	throw myException;
};

AjxUtil.NODE_REPEATS = new Object();
AjxUtil.NODE_REPEATS["folder"]	= true;
AjxUtil.NODE_REPEATS["search"]	= true;
AjxUtil.NODE_REPEATS["tag"]		= true;
AjxUtil.NODE_REPEATS["pref"]		= true;
AjxUtil.NODE_REPEATS["attr"]		= true;
AjxUtil.NODE_REPEATS["c"]		= true;
AjxUtil.NODE_REPEATS["m"]		= true;
AjxUtil.NODE_REPEATS["cn"]		= true;
AjxUtil.NODE_REPEATS["e"]		= true;
AjxUtil.NODE_REPEATS["a"]		= true;
AjxUtil.NODE_REPEATS["mbx"]		= true;
//AjxUtil.NODE_REPEATS["mp"]		= true; // only when parent is "mp"
// these really shouldn't repeat
AjxUtil.NODE_REPEATS["prefs"]	= true;
AjxUtil.NODE_REPEATS["attrs"]	= true;
AjxUtil.NODE_REPEATS["tags"]	= true;

AjxUtil.NODE_IS_ATTR = new Object();
AjxUtil.NODE_IS_ATTR["authToken"]	= true;
AjxUtil.NODE_IS_ATTR["lifetime"]		= true;
AjxUtil.NODE_IS_ATTR["sessionId"]	= true;
AjxUtil.NODE_IS_ATTR["name"]			= true;
AjxUtil.NODE_IS_ATTR["quotaUsed"]	= true;
AjxUtil.NODE_IS_ATTR["su"]			= true;
AjxUtil.NODE_IS_ATTR["fr"]			= true;
AjxUtil.NODE_IS_ATTR["mid"]			= true;
//AjxUtil.NODE_IS_ATTR["content"]	= true; // only when parent is "note"

AjxUtil.NODE_CONTENT = new Object();
AjxUtil.NODE_CONTENT["pref"]	= true;
AjxUtil.NODE_CONTENT["attr"]	= true;
AjxUtil.NODE_CONTENT["a"]	= true;

// IE doesn't define Node type constants
AjxUtil.ELEMENT_NODE	= 1;
AjxUtil.TEXT_NODE	= 3;

AjxUtil.xmlToJs =
function(node, omitName) {

	if (node.nodeType == AjxUtil.TEXT_NODE)
		return ['"', node.data, '"'].join("");

	var name = node.name ? node.name : node.localName;
	if (node.nodeType == AjxUtil.ELEMENT_NODE) {
		var text = omitName ? "{" : [name, ":{"].join("");
		var needComma = false;	
		if (node.attributes) {
			for (var i = 0; i < node.attributes.length; i++) {
				var attr = node.attributes[i];
				if (attr.name == "xmlns") continue;
				if (needComma) text += ",";
				var value = AjxUtil.isNumeric(attr.value) ? attr.value : AjxUtil.jsEncode(attr.value);
				text = [text, attr.name, ':', value].join("");
				needComma = true;
			}
		}
		if (node.hasChildNodes()) {
			var cnodes = new Object();
			var hasChild = false;
			for (var i = 0; i < node.childNodes.length; i++) {
				var child = node.childNodes[i];
				var cname = child.name ? child.name : child.localName;
				var isAttr = AjxUtil.NODE_IS_ATTR[cname] || 
							 (name == "content" && parent.name == "note");
				if (isAttr) {
					if (needComma) text += ",";
					text = [text, cname, ':', AjxUtil.jsEncode(child.textContent)].join("");
					needComma = true;
				} else {
					if (!cnodes[cname])
						cnodes[cname] = new Array();
					cnodes[cname].push(child);
					hasChild = true;
				}
			}
			if (hasChild && needComma) {text += ","; needComma = false;}
			for (var cname in cnodes) {
				if (needComma) {
					text += ",";
					needComma = false;
				}
				var repeats = AjxUtil.NODE_REPEATS[cname] ||
							  (cname == "mp" && name == "mp");
				if (repeats) text += cname + ":[";
				var clist = cnodes[cname];
				for (var i = 0; i < clist.length; i++) {
					if (needComma) text += ",";
					text += AjxUtil.xmlToJs(clist[i], repeats);
					needComma = true;
				}
				if (repeats) text += "]";
			}
		}
		text += "}";
	}

	return text;
}

AjxUtil.JS_CHAR_ENCODINGS = [
	"\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
	"\\b",     "\\t",     "\\n",     "\\u000B", "\\f",     "\\r",     "\\u000E", "\\u000F",
	"\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
	"\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D", "\\u001E", "\\u001F"
];

AjxUtil.jsEncode =
function(string) {

	if (!string) return "\"\"";

	var text = '"';
	for (var i = 0; i < string.length; i++) {
		var c = string.charAt(i);
		switch (c) {
			case '\\': case '"': case '/':
				text += '\\' + c;
				break;
			default:
				var code = string.charCodeAt(i);
				text += (code < 32) ? AjxUtil.JS_CHAR_ENCODINGS[code] : c;
		}
	}
	text += '"';
	return text;
}

