/* NOTE: This file will never contain application-specific code */

/* String Extensions
   This is the preferred way to do string manipulation, rather than global functions.
   They all check for existence first so that in case a specific browser has implemented the function in the JS engine,
   these versions will be ignored, as it's assumed that the browser version will be more efficient.
*/

if (!String.prototype.replaceAll) {
	/**
	 * Replace all instances of one string within another string.
	 * @param {String} find the string to find.
	 * @param {String} replace the string to replace with.
	 * @returns the modified string.
	 */
	String.prototype.replaceAll = function(find, replace) {
	    return this.split(find).join(replace);
	};
}

if (!String.prototype.trim) {
	/**
	 * Return string with leading and trailing spaces removed.
	 * @returns the trimmed string.
	 */
	String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g, '');
	};
}

if (!String.prototype.ltrim) {
	/**
	 * Return string with leading spaces removed.
	 * @returns the trimmed string.
	 */
	String.prototype.ltrim = function() {
		return this.replace(/^\s+/, '');
	};
}

if (!String.prototype.rtrim) {
	/**
	 * Return string with trailing spaces removed.
	 * @returns the trimmed string.
	 */
	String.prototype.rtrim = function() {
		return this.replace(/\s+$/, '');
	};
}

/**
 * Format a string by replacing an arbitrary number of tokens with passed values.
 * The tokens must be unique and must increment in the format {0}, {1}, etc.
 * @param {String} string the string to be formatted
 * @param {String} value1 value to replace token {0}
 * @param {String} value2 value to replace token {1}, etc..
 * @return {String} the formatted string
 * @static
 * @example
var s = String.format('The {0} red {1} jumped over', 'lazy', 'fox');
// s now contains the string: 'The lazy red fox jumped over'
 */
String.format = function(format){
    var args = Array.prototype.slice.call(arguments, 1);
    return format.replace(/\{(\d+)\}/g, function(m, i){
        return args[i];
    });
};
/* End String Extensions */


/* String manipulation backwards compatibility 
   These definitions will replace any existing function definitions that are to be removed elsewhere in the codebase.
   They are simple aliases to the above functions which will always be expected to have the optimum implementation.
   Ideally, calls to these functions should all be replaced with calls to the functions declared above.  Once all calls to
   these are removed from all application code, these can go away in order to limit code bloat and global namespace pollution.
*/

/**
 * Replace all instances of one string within another string.
 * @deprecated Use the generic string.replaceAll method
 * @param {String} s the string to search within.
 * @param {String} find the string to find.
 * @param {String} replace the string to replace with.
 * @returns the modified string.
 */
function replaceAll(s, find, replace) {
	return s.replaceAll(find, replace);
}

/**
 * Replace all instances of one string within another string.
 * @deprecated Use the generic string.replaceAll method
 * @function
 * @param {String} s the string to search within.
 * @param {String} find the string to find.
 * @param {String} replace the string to replace with.
 * @returns the modified string.
 */
js_ReplaceChar = replaceAll;

/**
 * Return string with leading and trailing spaces removed.
 * @deprecated Use the generic string.trim method
 * @param {String} s the string to be trimmed.
 * @returns the trimmed string.
 */
function trim(s) {
	return s.trim();
}

/**
 * Return string with leading spaces removed.
 * @deprecated Use the generic string.ltrim method 
 * @returns the trimmed string.
 */
function ltrim(s) {
	return s.ltrim();
}

/**
 * Return string with trailing spaces removed.
 * @deprecated Use the generic string.rtrim method
 * @returns the trimmed string.
 */
function rtrim(s) {
	return s.rtrim();
}

/**
 * Return trimmed, lowercase string.  This function should NOT be removed - it makes no sense
 * for a 'trim' function to also be changing case.
 * @deprecated Use the generic string.trim method and lowercase where appropriate
 * @returns the trimmed, lowercase string.
 */
function trimAll(s) {
	return s.trim().toLowercase;
}

/**
 * Return string with leading and trailing spaces removed.
 * @deprecated Use the generic string.trim method
 * @param {String} s the string to be trimmed.
 * @returns the trimmed string.
 */
function trimStr(s) {
	return s.trim();
}

/**
 * Return string with leading spaces removed.
 * @function
 * @deprecated Use the generic string.ltrim method
 * @returns the trimmed string.
 */
LTrim = ltrim;
/**
 * Return string with trailing spaces removed.
 * @function
 * @deprecated Use the generic string.rtrim method
 * @returns the trimmed string.
 */
RTrim = rtrim;
/**
 * Return string with leading spaces removed.
 * @function
 * @deprecated Use the generic string.ltrim method
 * @returns the trimmed string.
 */
trimLeft = ltrim;
/**
 * Return string with trailing spaces removed.
 * @function
 * @deprecated Use the generic string.rtrim method
 * @returns the trimmed string.
 */
trimRight = rtrim;

/**
 * Return a string with all spaces removed.
 * @deprecated Use the generic string.replaceAll method
 * @param {String} s the string to be cleaned.
 * @returns the new string
 */
function removeSpaces(s) {
	return s ? s.replaceAll(' ', '') : '';
}


/* Function Extensions */

/**
 * Create a delegate (function) that sets the scope to a requested object.
 * Can be called directly on any function.  Creates a function that is scoped
 * so that the <b>this</b> var inside the function points to <i>scope</i>.<br />
 * This is a more useful version than the MS Toolkit's Function.createDelegate, which doesn't allow
 * extra parms to be passed.
 * @param {Object} [scope=window] object for which the scope is set.
 * @param {Array} [args=args passed by caller] override arguments for the call.
 * @param {Boolean|Number} [append=false] true to append to caller args instead of overriding, or
 * a if a number, the args are inserted at the specified position.
 * @returns {Function} The new function
 * @example
doSomething.createDelegate(this, ['foo', 'bar']);
...
doSomething();
...
function doSomething() {
	// arguments =['foo', 'bar'] here
}
// OR
doSomething.createDelegate(this, ['foo', 'bar'], true);
...
doSomething(false, 'hello');
...
function doSomething() {
	// arguments = [false, 'hello', 'foo', 'bar'] here
}
*/
Function.prototype.createDelegate = function(scope, args, append) {
    var me = this;
    return function() {
        var callArgs = args || arguments;
        if (append === true) {
            callArgs = Array.prototype.slice.call(arguments, 0);
            callArgs = callArgs.concat(args);
        } else if (typeof append == 'number') {
            callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
            var applyArgs = [appendArgs, 0].concat(args); // create method call params
            Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
        }
        return me.apply(scope || window, callArgs);
    };
};
/* End Function Extensions */


/* Date Extensions - TBD */

/**
 * Global alias for document.getElementById
 * @function
 * @returns {Function} an alias fn to document.getElementById
 */
$get = document.getElementById;

/**
 * Namespace for all related functions
 * @namespace
 */
var FED = FED || {};

/**
 * Utility functions
 * @constructor
 * @singleton
 */
FED.Util = function() {
	var reNumber = /^\d+$/,
		reEmail = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i,
		reEmailUser = /^(root@|abuse@|spam@)/,
		reZipcode = /^\d{5}$|^\d{5}-?\d{4}$/;

	//private
	var uaTest = function(s) {
        return s.test(ua);
    };
	var ua = navigator.userAgent.toLowerCase(),
        isOpera = uaTest(/opera/),
        isChrome = uaTest(/chrome/),
        isWebKit = uaTest(/webkit/),
        isSafari = !isChrome && uaTest(/safari/),
        isSafari3 = isSafari && uaTest(/version\/3/),
        isSafari4 = isSafari && uaTest(/version\/4/),
        isIE = !isOpera && uaTest(/msie/),
        isIE7 = isIE && uaTest(/msie 7/),
        isIE8 = isIE && uaTest(/msie 8/),
        isIE6 = isIE && !isIE7 && !isIE8,
        isGecko = !isWebKit && uaTest(/gecko/),
        isGecko3 = isGecko && uaTest(/rv:1\.9/),	//FF3x
		isGecko2 = isGecko && !isGecko3,			//FF2x
        isWindows = uaTest(/windows|win32/),
        isMac = uaTest(/macintosh|mac os x/);
	/*
	 * Add CSS classes to the body tag to allow browser-specific CSS without sniffing
	 * @private
	 */
    var addBodyCls = function() {
        var bd = document.body || document.getElementsByTagName('body')[0];
        if (!bd) {
			return false;
		}
        var cls = [
                isIE ? 'ie ' + (isIE6 ? 'ie6' : (isIE7 ? 'ie7' : 'ie8'))
                : isGecko ? 'gecko ' + (isGecko2 ? 'gecko2' : 'gecko3')
                : isOpera ? 'opera'
                : isWebKit ? 'webkit' : ''];

        if (isSafari) {
            cls.push('safari ' + (isSafari3 ? 'safari3' : 'safari4'));
        }
		else if (isChrome) {
            cls.push('chrome');
        }

        if (isMac) {
            cls.push('mac');
        }

        bd.className += (!!bd.className ? ' ' : '') + cls.join(' ');
        return true;
    };

	//wire any init functionality
	$(function() {
		addBodyCls();
	});


	return {
		/**
		 * Returns true if the passed value is empty.
		 * The value is considered to be empty if it is
		 * <ul>
		 * <li>null</li>
		 * <li>undefined</li>
		 * <li>a string equal to 'undefined'</li>
		 * <li>a zero length string (unless the <i>allowBlank</i> parameter is <b>true</b>)</li>
		 * </ul>
		 * @param {Mixed} value The value to test
		 * @param {Boolean} [allowBlank=false] true to allow empty strings
		 * @returns {Boolean} true if the value is empty
		 */
		isEmpty: function(val, allowBlank) {
			return typeof val == 'undefined' || val == 'undefined' || val === null || (allowBlank===true ? false : val === '');
		},

		/**
		 * Returns true if the string contains only numbers.
		 * @param {String} val the string to check
		 * @returns {Boolean} true if the value is numeric
		 */
		isNumeric: function(val) {
		    return reNumber.test(val);
		},

		/**
		 * Returns true if the string is a valid 5 or 9 digit zipcode. Hyphen is optional in a 9 digit zip.
		 * @param {String} val the zipcode to check
		 * @returns {Boolean} true if the value is a valid zipcode
		 */
		isValidZipcode: function(val) {
		    return reZipcode.test(val);
		},

		/**
		 * Returns true if the string is a valid email address.
		 * @param {String} val the string to check
		 * @returns {Boolean} true if the value is a valid email
		 */
		isValidEmail: function(val) {
		    return reEmail.test(val);
		},

		/**
		 * Returns true if the string is a valid email username (left of '@' is NOT 'root', 'spam', 'abuse').
		 * @param {String} val the string to check
		 * @returns {Boolean} true if the value is a valid
		 */
		isValidEmailUser: function(val) {
		    return !reEmailUser.test(val);
		}
	};
}();

/**
 * Provides functions for gettting/setting/removing cookies.<br />NOTE: These are just wrappers around $.cookie
 * @class Provides functions for gettting/setting/removing cookies.
 * @constructor
 * @singleton
 */
FED.Cookie = function() {
	return {
		/**
		 * Returns a cookie value or null if not found.
		 * @param {String} name the cookie to retrieve
		 * @returns the cookie value or null if not found
		 */
		get: function(name) {
			return !!name ? $.cookie(name) : null;
		},

		/**
		 * Removes a cookie by name.<br />
		 * NOTE: To delete, you must use the same path/domain as specified when the cookie was created.
		 * @param {String} name the name of the cookie to remove
		 * @param {String} [path=''] the path of the cookie to remove
		 * @param {String} [domain=''] the domain of the cookie to remove
		 * @returns void
		 */
        clear: function(name, path, domain) {
			$.cookie(name, null, {path: path || '', domain: domain || ''});
	    },

		/**
		 * Sets a cookie value and optionally the path/domain.<br />
		 * NOTE: if you need to set additional properties (e.g. expires/secure), call $.cookie directly
		 * @param {String} name the cookie name
		 * @param {Mixed} value the cookie value
		 * @param {String} [path=''] the cookie path
		 * @param {String} [domain=''] the cookie domain
		 * @returns void
		 */
		set: function(name, value, path, domain) {
			$.cookie(name, value, {path:path || '', domain: domain || ''});
		}
	};
}();

/* back compat - these should go away as usage is refactored throughout the codebase */

/**
 * Sets a cookie value and optionally the path.
 * @deprecated Use $.cookie(name, value) or FED.Cookie.set
 * @param {String} name the cookie name
 * @param {Mixed} value the cookie value
 * @param {String} [path=''] the cookie path
 * @param {String} [domain=''] the cookie domain
 * @returns void
 */
function setCookie(name, value, path, domain) {
    FED.Cookie.set(name, value, path, domain);
}

/**
 * Get a cookie value.<br />
 * NOTE: unlike $.cookie, this returns '' if not found
 * @deprecated Use $.cookie(name) or FED.Cookie.get
 * @param {String} name the cookie name
 * @returns the value or empty string if not found
 */
function getCookie(name) {
	return FED.Cookie.get(name) || '';
}

/**
 * Removes a cookie by name.
 * NOTE: To delete, you must use the same path/domain as specified when the cookie was created.
 * @deprecated Use $.cookie(name, null) or FED.Cookie.clear
 * @param {String} name the name of the cookie to remove
 * @param {String} [path=''] the cookie path
 * @param {String} [domain=''] the cookie domain
 * @returns void
 */
function Delete_Cookie(name, path, domain) {
    FED.Cookie.clear(name, path, domain);
}

/**
 * Validate a 5 or 9 digit zipcode. Hyphen is optional in a 9 digit zip.
 * @deprecated Use FED.Util.isValidZipcode
 * @param {String} val the zipcode to check
 * @returns {Boolean} true if valid else false
 */
function zipcodeVal(val) {
    return FED.Util.isValidZipcode(val);
}
