/** -----------------------------------------------------------------------------

	FILE:		String.js
	PURPOSE:	Extends the javascript string object with useful methods

	Copyright 2007 (c) FlowTech Solutions, Inc.		(www.FlowTech-Solutions.com)

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


	PUBLIC METHODS		DESCRIPTION
	--------------				---------------------------------------------------
	ltrim							Removes leading spaces from the string
	rtrim							Removes trailing spaces from the string
	trim							Removes both leading and trailing spaces
	removeWhiteSpace	Removes excess spaces from anywhere within the string including leading and trailing
	isEmail						Returns true/false for a matches a limited version of the RFC 2822 email addr-spec form
	reverse						Reverses the contents of a string (useful for parsing)
	toInt							Converts a string to an array of character code
	toMixCase					converts lowercase statement to mixed case, capitalizing each word
	pad							Pads the string with preceeding pad characters  pad("123.45",10) -> "0000123.45"


	"Number" EXTENDED METHODS AND PROPERTIES
	----------------------------------------
	toFixed				This rounds and truncate to the specified decimal places
							Example: 123.456789012345.toFixed(2) -> 12.46

	toExponential		Formats the number into exponential notation  (returns string)
							Example: 123.456789012345.toExponential(6) -> 123.4567e+2

	format*				Formats a string replacing formatting specifiers with values provided
						as arguments which are formatted according to the specifier. This is
						similar to sprintf from C. see method below for details.
						Usage: resultString = formatString.format(value1, v2, ...);
						Examples:	"%02d".format(8) -> "08"
									"%05.2f".format(1.23432) == "01.23"
									"$%13.2f".format(987654.1234) == "$987654.12"
						(a non consumable class FormatSpecifier supports the String.format method)
						(String.prototype.npad is not designed for consumption, supports String.format method)

						* Note this is a method of the String class not Number but is classified
							in the section due to its use with formatting numbers.

------------------------------------------------------------------------------------------------------------------------ */

// Returns the string with leading spaces removed
if (String.prototype.ltrim == null)
{
	String.prototype.ltrim = function()
		{
		return this.replace(/^\s+/,'');
		}
}

// Returns the string with leading spaces removed
if (String.prototype.rtrim == null)
{
	String.prototype.rtrim = function()
		{
		return this.replace(/\s+$/,'');
		}
}

// Returns a string with both leading and trailing spcaes removed
if (String.prototype.trim == null)
{
	String.prototype.trim = function()
	{
		return this.replace(/^\s+/,'').replace(/\s+$/,'');
	}
}

// Removes excess spaces from anywhere within the string including leading and trailing
if (String.prototype.removeWhiteSpace == null)
{
	String.prototype.removeWhiteSpace = function()
	{
		return this.trim().replace(/( ){2,}/g, ' ');
	}
}

// Removes excess spaces from anywhere within the string including leading and trailing
if (String.prototype.normalize == null)
{
	String.prototype.normalize = function()
	{
		return this.replace(/[^a-z0-9_]/gi, '');
	}
}

//	Returns true/false for a matches a limited version of the RFC 2822 email addr-spec form
if(String.prototype.isEmail == null)
{
	String.prototype.isEmail = function()
	{
		var str= this.toString();
		return /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/.test(str);
		//return /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/gi.test(str);
	}
}

// Reverses the contents of a string (useful for parsing)
if(String.prototype.reverse == null)
{
	String.prototype.reverse = function()
	{
	var s='';
	var i=this.length;
	while(i-->0)
		s+=this.substring(i,i+1);
	return s;
	}
}

// Converts a string to an array of character code
if (String.prototype.toInt == null)
{
	String.prototype.toInt  =function()
	{
		var a=[];
		for(var i=0;i<this.length;i++)
			a[i]=this.charCodeAt(i);
		return a;
	}
}

// converts lowercase statement to mixed case, capitalizing each word
if (String.prototype.toMixCase == null)
{
	String.prototype.toMixCase  =function()
	{
		var ths = this.toLowerCase();
		var o='';
		var l=true;
		for(var i=0;i<ths.length;i++)
		{
			var c=ths.substr(i, 1);
			o+=l?c.toUpperCase():c;
			l=/(^|\W)$/gi.test(c);
		}
		return o;
	}
}

//	Encode string for HTML
if (String.prototype.encodeToHtml == null)
{
	String.prototype.encodeToHtml  =function()
	{
		return this.split("\x3C").join("&lt;").split("\x26").join("&amp;").split("\x3E").join("&gt;").split("\x22").join("&quot;");
	}
}

//	Decode HTML to string
if (String.prototype.decodeFromHtml == null)
{
	String.prototype.decodeFromHtml  =function()
	{
		return this.split("&lt;").join("\x3C").split("&amp;").join("\x26").split("&gt;").join("\x3E").split("&quot;").join("\x22").split("&nbsp;").join(" ");
	}
}





// Pads the string with preceeding pad characters  pad("123.45",10) -> "0000123.45"
if (String.prototype.pad == null)
{
	String.prototype.pad = function(padstr,nsize)
	{
		if ( isNaN(nsize) || (nsize-this.length+1) <= 0 ) return this.toString();
		return (new Array(nsize-this.length+1).join(padstr)+this).toString();
	}
}


// ========================================================================
//					Number    EXTENDED METHODS
// ========================================================================
// For more Number extended functions reference: http://www.prototypejs.org/api/number


// This rounds and truncate to the specified decimal places  123.456789012345.toFixed(2) -> 12.46
if(Number.prototype.toFixed == null)
{
    Number.prototype.toFixed = function(d)
	{
        var n = this;
        d = d || 0;
        var f = Math.pow(10, d);
        n = Math.round (f * n) / f;
        n = (n >= 0) ? n+Math.pow(10, -(d+1)) : n-Math.pow(10, -(d+1));
        n += '';
        return d == 0 ? n.substring(0, n.indexOf('.')) : n.substring(0, n.indexOf('.') + d + 1);
    }
}

// Formats the number into exponential notation    123.456789012345.toExponential(6) -> 123.4567e+2
if(Number.prototype.toExponential == null)
{
    Number.prototype.toExponential = function(d)
	{
        var n = this;
        var e = 0;
        if (n != 0)
		{
            e = Math.floor(Math.log(Math.abs(n)) / Math.LN10);
        }
        n /= Math.pow(10, e);
        if (isFinite(d))
		{
            if (Math.abs(n) + 5*Math.pow(10, -(d+1)) >= 10.0)
			{
                n /= 10;
                e += 1;
            }
            n = n.toFixed(d);
        }
        n += "e";
        if (e >= 0)
		{
            n += "+";
        }
        n += e;
        return n;
    }
}


// Class to support String.prototype.format  below
var FormatSpecifier=function(s)
{
	var s = s.match(/%(\(\w+\)){0,1}([ 0-]){0,1}(\+){0,1}(\d+){0,1}(\.\d+){0,1}(.)/);
	this.key = (s[1]) ? s[1].slice(1,-1) : null;
	this.paddingFlag = s[2];
	if (this.paddingFlag=="")
	{
		this.paddingFlag =" "
	}
	this.signed=(s[3] == "+");
	this.minLength = parseInt(s[4]);
	if (isNaN(this.minLength))
	{
		this.minLength=0;
	}
	if (s[5])
	{
		this.percision = parseInt(s[5].slice(1,s[5].length));
	}else{
		this.percision=-1;
	}
	this.type = s[6];
}

/* String.format() Uses a format string and substitutes values into format string.
	Formats a string replacing formatting specifiers with values provided as arguments
	which are formatted according to the specifier.
	This is an implementation of  python's % operator for strings and is similar to sprintf from C.
	Usage:
		resultString = formatString.format(value1, v2, ...);

	Each formatString can contain any number of formatting specifiers which are
	replaced with the formated values.

	specifier([...]-items are optional):
		"%(key)[flag][sign][min][percision]typeOfValue"

		(key)  If specified the 1st argument is treated as an object/associative array and the formating values
				 are retrieved from that object using the key.

		flag:
			0	Use 0s for padding.
			-	Left justify result, padding it with spaces.
				Use spaces for padding.
		sign:
			+	Numeric values will contain a +|- infront of the number.
		min:
			l   The string will be padded with the padding character until it has a minimum length of l.
		percision:
		   .x   Where x is the percision for floating point numbers and the lenght for 0 padding for integers.
		typeOfValue:
			d	Signed integer decimal.
			i	Signed integer decimal.
			b	Unsigned binary.                       //This does not exist in python!
			o	Unsigned octal.
			u	Unsigned decimal.
			x	Unsigned hexidecimal (lowercase).
			X	Unsigned hexidecimal (uppercase).
			e	Floating point exponential format (lowercase).
			E	Floating point exponential format (uppercase).
			f	Floating point decimal format.
			F	Floating point decimal format.
			c	Single character (accepts byte or single character string).
			s	String (converts any object using object.toString()).

	Examples:
		"%02d".format(8) --> "08"
		"%05.2f".format(1.234) --> "01.23"
		"123 in binary is: %08b".format(123) --> "123 in binary is: 01111011"
		"% 13.2f".format(987654.1234) --> "  987654.12"
		"$%-13.2f".format(987654.1234) --> "987654.12"
	@param *  Each parameter is treated as a formating value.
*/
if (!String.prototype.format)
{
	String.prototype.format=function()
	{
		var sf = this.match(/(%(\(\w+\)){0,1}[ 0-]{0,1}(\+){0,1}(\d+){0,1}(\.\d+){0,1}[dibouxXeEfFgGcrs%])|([^%]+)/g);
		if(sf)
		{
			if(sf.join("") != this)
			{
				throw "Unsupported formating string.";
			}
		}
		else
		{
			throw "Unsupported formating string.";
		}
		var thisMod = this;
		var rslt ="";
		var s;
		var obj;
		var cnt=0;
		var frmt;
		var sign="";

		for(var i=0;i<sf.length;i++)
		{
			s=sf[i];
			if(s == "%%")
			{
				s = "%";
			}
			else if(s.slice(0,1) == "%")
			{
				frmt = new FormatSpecifier(s);//get the formating object
				if (frmt.key) {//an object was given as formating value
					if ((typeof arguments[0]) == "object" && arguments.length == 1)
					{
						obj = arguments[0][frmt.key];
					}
					else
					{
						throw "Object or associative array expected as formating value.";
					}
				}
				else
				{//get the current value
					if (cnt >= arguments.length)
					{
						throw "Not enough arguments for format string";
					}
					else
					{
						obj = arguments[cnt];
						cnt++;
					}
				}

				if(frmt.type == "s")
				{//String
					s=obj.toString().npad(frmt.npaddingFlag, frmt.minLength);

				}
				else if(frmt.type == "c")
				{//Character
					if(frmt.paddingFlag == "0")
					{
						frmt.paddingFlag=" ";//padding only spaces
					}
					if(typeof obj == "number")
					{//get the character code
						s = String.fromCharCode(obj).npad(frmt.paddingFlag , frmt.minLength) ;
					}
					else if(typeof obj == "string")
					{
						if(obj.length == 1){//make sure it's a single character
							s=obj.npad(frmt.paddingFlag, frmt.minLength);
					}
					else
					{
							throw "Character of length 1 required.";
					}
				}
				else
				{
						throw "Character or Byte required.";
				}
			}
			else if(typeof obj == "number")
			{
					//get sign of the number
					if(obj < 0)
					{
						obj = -obj;
						sign = "-"; //negative signs are always needed
					}
					else if(frmt.signed)
					{
						sign = "+"; // if sign is always wanted add it
					}
					else
					{
						sign = "";
					}
					//do percision padding and number conversions
					switch(frmt.type)
					{
						case "f": //floats
						case "F":
							if(frmt.percision > -1)
							{
								s = obj.toFixed(frmt.percision).toString();
							}
							else
							{
								s = obj.toString();
							}
							break;
						case "E"://exponential
						case "e":
							if(frmt.percision > -1)
							{
								s = obj.toExponential(frmt.percision);
							}
							else
							{
								s = obj.toExponential();
							}
							s = s.replace("e", frmt.type);
							break;
						case "b"://binary
							s = obj.toString(2);
							s = s.npad("0", frmt.percision);
							break;
						case "o"://octal
							s = obj.toString(8);
							s = s.npad("0", frmt.percision);
							break;
						case "x"://hexadecimal
							s = obj.toString(16).toLowerCase();
							s = s.npad("0", frmt.percision);
							break;
						case "X"://hexadecimal
							s = obj.toString(16).toUpperCase();
							s = s.npad("0", frmt.percision);
							break;
						default://integers
							s = parseInt(obj).toString();
							s = s.npad("0", frmt.percision);
							break;
					}
					if(frmt.paddingFlag == "0")
					{//do 0-padding
						//make sure that the length of the possible sign is not ignored
						s=s.npad("0", frmt.minLength - sign.length);
					}
					s=sign + s;//add sign
					s=s.npad(frmt.paddingFlag, frmt.minLength);//do padding and justifiing
				}
				else
				{
					throw "Number required.";
				}
			}
			rslt += s;
		}
		return rslt;
	}
}

/* Supports String.prototype.format above
	Padds a String with a character to have a minimum length.
	@param flag   "-":      to padd with " " and left justify the string.
						Other: the character to use for padding.
	@param len    The minimum length of the resulting string.
*/
if (!String.prototype.npad)
{
	String.prototype.npad = function(flag, len)
	{
		flag = (!flag ? '':flag);
		var s = "";
		if(flag == "-")
		{
			var c = " ";
		}
		else
		{
			var c = flag;
		}
		for(var i=0;i<len-this.length;i++)
		{
			s += c;
		}
		if(flag == "-")
		{
			s = this + s;
		}
		else
		{
			s += this;
		}
		return s;
	}
}




	// Find smallest numeric value
	function min()
	{
		var args = $A(arguments);
		if (!args.length) return null;
		var ary = args.flatten(), smallest=Number.MAX_VALUE, val;
		for (var a=ary.length-1; a>=0; a--)
		{
			val = parseFloat(ary[a]);
			if (!isNaN(val)) smallest = (val < smallest ? val : smallest);
		}
		return ( smallest == Number.MAX_VALUE ? null : smallest );
	}

	// Find largest numeric value
	function max()
	{
		var ary = $A(arguments).flatten(), largest=Number.MIN_VALUE, val;
		if (!ary.length) return null;

		for (var a=ary.length-1; a>=0; a--)
		{
			val = parseFloat(ary[a]);
			if (!isNaN(val)) largest = (val > largest ? val : largest);
		}
		return ( largest == Number.MIN_VALUE ? null : largest );
	}


	// Return 1st non-null argument, simlar to SQL COALESCE
	function coalesce()
	{
		for (var i = 0; i<arguments.length && null!=arguments[i]; i++);
		return (i<arguments.length && null!=arguments[i] ? arguments[i] : null);
	}

	// Array sort numeric function
	//		e.g. var Srt = Ary.sort(sortNumber);
	function sortNumber(a,b)
	{
		return b - a;
	}

