/*!
 * Juice Prototype and Type Extensions @VERSION
 *
 * Copyright (c) 2009 AUTHORS.txt (http://shellscape.org/juice/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 */

/* Array Extensions */

	/// <property>Array.prototype.typeName</property>
	/// <summary>Used for identifying an object as an array.</summary>
	[].typeName || (Array.prototype.typeName = 'Array');
		
	/// <method>Array.prototype.clear</method>
	/// <summary>Removes all items from an Array object and resets it's length.</summary>
	[].clear || (Array.prototype.clear = function() {
		this.length = 0; 
		return this;
	});
	
	/// <method>Array.prototype.last</method>
	/// <summary>Returns the last item in the current Array object.</summary>
	[].last || (Array.prototype.last = function() {
		return this[this.length - 1];
	});
	
	/// <method>Array.prototype.clone</method>
	/// <summary>Returns an exact copy of the current Array object.</summary>
	[].clone || (Array.prototype.clone = function() {
		return [].concat(this);
	});
	
	/// <method>Array.prototype.remove</method>
	/// <summary>Removes the specified object from the current Array object.</summary>
	/// <param name="what" type="object">The object to look for, and remove if found.</param>
	[].remove || (Array.prototype.remove = function(what){
		for(i=0;i < this.length;i++){ 
			if (what == this[i]){
				this.splice(i, 1); 
				this.remove(what);
				return;
			}
		} 
	});

	/// <method>Array.prototype.remove</method>
	/// <summary>Removes the specified object from the current Array object.</summary>
	/// <param name="what" type="object">The object to look for, and remove if found.</param>
	[].removeRange || (Array.prototype.removeRange = function(from, to){
		var rest = this.slice((to || from) + 1 || this.length);
		this.length = from < 0 ? this.length + from : from;
		return this.push.apply(this, rest);
	});

	/// <method>Array.prototype.remove</method>
	/// <summary>Removes the specified object from the current Array object.</summary>
	/// <param name="what" type="object">The object to look for, and remove if found.</param>
	[].insert || (Array.prototype.insert = function(i, v){
		if(i>=0){
			var a = this.slice(), b = a.splice( i );
			a[i] = v;
			return a.concat( b );
		}
	});
	
	/// <method>Array.prototype.contains</method>
	/// <summary>Indicates if the current Array object contains the specified object.</summary>
	/// <param name="what" type="object">The object to look for.</param>	
	[].contains || (Array.prototype.contains = function(what){
		var self = this; 
		for (var i = 0, length = self.length; i < length; i++){
			if (self[i] == what){
				return true;
			}
		}
		return false; 
	});

	/// <method>Array.prototype.indexOf</method>
	/// <summary>Returns the index of the specified object within the current Array object.</summary>
	/// <param name="what" type="object">The object to look for.</param>	
	/// <param name="startIndex" type="integer" default="0">The position in the array to start looking from.</param>	
	[].indexOf || (Array.prototype.indexOf = function(what, startIndex){
		startIndex = startIndex || 0;
		
		var length = this.length;
		
		for(var i = startIndex; i < length; i++){
			if(this[i] == what){
				return i;
			}
		}
		return -1;
	});
	
/* Date Extensions */	

	var _d = new Date();

	/// <method>Date.prototype.diff</method>
	/// <ref url="http://msdn.microsoft.com/en-us/library/aa258269(SQL.80).aspx"/>
	/// <summary>Returns the number of date and time boundaries crossed between two specified dates.</summary>
	/// <param name="date" type="Date">The ending date for the calculation.</param>	
	/// <param name="part" type="string" default="ms">
	///   <desc>Specifies on which part of the date to calculate the difference.</desc>
	///   <values>
	///     <value desc="Day">d</value>
	///     <value desc="Hour">h</value>
	///     <value desc="Millisecond">ms</value>
	///     <value desc="Minute">n</value>
	///     <value desc="Second">s</value>
	///   </values>
	/// </param>
	_d.diff || (Date.prototype.diff = function(date, part){
		var ms = date.valueOf() - this.valueOf();
		if(!part || part == 'ms'){
			return ms;
		}

		// TODO: Finish calculating all parts. 

		switch (part){
			case 's': return ms / 1000; //seconds
			case 'n': return ms / 60000; //minutes
			case 'h': return ms / 3600000; //hours
			case 'd': return ms / 86400000; //days
		}
	});

	Date._formatRegex = /(yyyy|yy|y|MMMM|MMM|MM|M|dddd|ddd|dd|d|HH|H|hh|h|mm|m|ss|s|tt|x)/gi;
	Date._monthNames = new Array('January','February','March','April','May','June','July','August','September','October','November','December');
	Date._dayNames = new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday');

	/// <method>Date.prototype.format</method>
	/// <ref url="http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx"/>
	/// <summary>Returns a string representation of a date based on the specified format.</summary>
	/// <param name="format" type="string">
	///   <desc>The custom date and time format specifiers used to format the output string.</desc>
	///   <values>
	///     <value desc="The year as a four-digit number">yyyy</value>
	///     <value desc="The year, from 00 to 99.">yy</value>
	///     <value desc="The year, from 0 to 99.">y</value>
	///     <value desc="The full name of the month.">MMMM</value>
	///     <value desc="The abbreviated name of the month.">MMM</value>
	///     <value desc="The month, from 01 through 12.">MM</value>
	///     <value desc="The month, from 1 through 12.">M</value>
	///     <value desc="The full name of the day of the week.">dddd</value>
	///     <value desc="The abbreviated name of the day of the week.">ddd</value>
	///     <value desc="The day of the month, from 01 through 31.">dd</value>
	///     <value desc="The day of the month, from 1 through 31.">d</value>
	///     <value desc="The hour, using a 24-hour clock from 00 to 23.">HH</value>
	///     <value desc="The hour, using a 24-hour clock from 0 to 23.">H</value>
	///     <value desc="The hour, using a 12-hour clock from 01 to 12.">hh</value>
	///     <value desc="The hour, using a 12-hour clock from 1 to 12.">h</value>
	///     <value desc="The minute, from 00 through 59.">mm</value>
	///     <value desc="The minute, from 0 through 59.">m</value>
	///     <value desc="The second, from 00 through 59.">ss</value>
	///     <value desc="The second, from 0 through 59.">s</value>
	///     <value desc="The AM/PM designator.">tt</value>
	///     <value desc="The day of the month postfix; st, th, or rd.">x</value>
	/// </param>	
	_d.format || (Date.prototype.format = function(format){
		if (!this.valueOf()){
			return '';
		}

		var self = this,
			year = this.getFullYear(),
			month = this.getMonth(),
			day = this.getDate(),
			weekDay = this.getDay(),
			hours = this.getHours(),
			minutes = this.getMinutes(),
			seconds = this.getSeconds();
		
		// TODO: Implement O|o|R|r|u http://msdn.microsoft.com/en-us/library/az4se3k1.aspx
		
		return format.replace(Date._formatRegex, function(match){
			switch (match){
				case 'yyyy'	:	return year;
				case 'yy'  	:	return (year % 100).zf(2);
				case 'y'		:	return (year % 100);
				case 'MMMM'	: return Date._monthNames[month];
				case 'MMM'	:	return Date._monthNames[month].substr(0, 3);
				case 'MM'		:	return (month + 1).zf(2);
				case 'M'		:	return (month + 1);
				case 'dddd'	: return Date._dayNames[weekDay];
				case 'ddd'	:	return Date._dayNames[weekDay].substr(0, 3);
				case 'dd'		:	return day.zf(2);
				case 'd'		:	return day;
				case 'HH'		:	return hours.zf(2);
				case 'H'		:	return hours;
				case 'hh'		:	return ((h = hours % 12) ? h : 12).zf(2);
				case 'h'		:	return ((h = hours % 12) ? h : 12);
				case 'mm'		:	return minutes.zf(2);
				case 'm'		:	return minutes;
				case 'ss'		:	return seconds.zf(2);
				case 's'		:	return seconds;
				case 'tt'		:	return hours < 12 ? 'am' : 'pm';
				case 'x'		: switch (day){
					case 1: case 21: case 31:
						return "st";
					case 2: case 22:
						return "nd";
					case 3: case 23:
						return "rd";
					default:
						return "th";
				}                    
			}            
		});
	});

	/// <method>Date.prototype.parseFormat</method>
	/// <ref url="http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx"/>
	/// <summary>Returns a Date object, from a string representation of a date based on the specified format.</summary>
	/// <param name="format" type="string">
	///   <desc>The custom date and time format specifiers used to format the output string.</desc>
	///   <values>
	///     <value desc="The year as a four-digit number">yyyy</value>
	///     <value desc="The year, from 00 to 99.">yy</value>
	///     <value desc="The year, from 0 to 99.">y</value>
	///     <value desc="The full name of the month.">MMMM</value>
	///     <value desc="The abbreviated name of the month.">MMM</value>
	///     <value desc="The month, from 01 through 12.">MM</value>
	///     <value desc="The month, from 1 through 12.">M</value>
	///     <value desc="The full name of the day of the week.">dddd</value>
	///     <value desc="The abbreviated name of the day of the week.">ddd</value>
	///     <value desc="The day of the month, from 01 through 31.">dd</value>
	///     <value desc="The day of the month, from 1 through 31.">d</value>
	///     <value desc="The hour, using a 24-hour clock from 00 to 23.">HH</value>
	///     <value desc="The hour, using a 24-hour clock from 0 to 23.">H</value>
	///     <value desc="The hour, using a 12-hour clock from 01 to 12.">hh</value>
	///     <value desc="The hour, using a 12-hour clock from 1 to 12.">h</value>
	///     <value desc="The minute, from 00 through 59.">mm</value>
	///     <value desc="The minute, from 0 through 59.">m</value>
	///     <value desc="The second, from 00 through 59.">ss</value>
	///     <value desc="The second, from 0 through 59.">s</value>
	///     <value desc="The AM/PM designator.">tt</value>
	///     <value desc="The day of the month postfix; st, th, or rd.">x</value>
	/// </param>
	Date.parseFormat = function(value, format){

		var date = new Date();
		
		// TODO: Implement MMM|M|dddd|ddd|HH|H|hh|h|mm|m|ss|s
		
		format.replace(Date._formatRegex, function(match){
			switch (match){
				case 'yyyy': case 'yy': case 'y': {
					var i = format.indexOf(match),
						v = value.substring(i, i + m.length);
						
					if(v){
						data.d.setFullYear(parseInt(v));
					}
					return;
				}
				case 'MMM': case 'MM': {
					var i = format.indexOf(match),
						v = input.substring(i, i + match.length);
						
					if(v.startsWith('0')){
						v = v.substring(1, 2);
					}
					if(v){
						date.setMonth((parseInt(v) - 1));
					}
					return;
				}
				case 'dd': case 'd': {
					var i = data.format.indexOf(match),
						v = data.input.substring(i, i + match.length);
						
					if(v.startsWith('0')){
						v = v.substring(1, 2);
					}
					if(v){
						date.setDate(parseInt(v));
					}
					return;
				}            
			}    	
		});
		
		return (date == "Invalid Date" ? null : date);
	};

/* Math Extensions */
		
	/// <method>Math.uuid</method>
	/// <summary>Generates a random universal unique identifier.</summary>
	/// <param name="length" type="integer">The desired number of characters.</param>	 
	/// <param name="radix" type="integer" default="62">The number of allowable values for each character. The radix must be <= 62.</param>	 
	Math.uuid = function() {
		// Private array of chars to use
		var literals = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 

		return function (length, radix) {
			var chars = literals,
				uuid = [],
				rnd = Math.random,
				r;
			
			radix = radix || chars.length;

			if (length) {
				// Compact form
				for (var i = 0; i < len; i++){
					uuid[i] = chars[0 | rnd()*radix];
				}
			}
			else {
				// rfc4122, version 4 form, requires these characters
				uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
				uuid[14] = '4';

				// Fill in random data. At i==19 set the high bits of clock sequence as per rfc4122, sec. 4.1.5
				for (var i = 0; i < 36; i++) {
					if (!uuid[i]) {
						r = 0 | rnd() * 16;
						uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r & 0xf];
					}
				}
			}

			return uuid.join('');
		};
	};

/* Number Extensions */
	
	(0).zf || (Number.prototype.zf = function(l) {
		return this.toString().zf(l);
	});
	
/* String Extensions */

	/// <method>String.empty</method>
	/// <summary>Indicates whether the specified string object is undefined, null or of zero length.</summary>
	/// <param name="s" type="string">The string to check.</param>
	String.empty = function(s){
		return s === undefined || s == null || s.trim().length == 0;
	}

	''.zf || (String.prototype.zf = function(length) {
		var s = '';
		for(i = 0; i < (length - this.length); i++){
			s += '0';
		}
		return s + this;
	});

	/// <method>String.prototype.trim</method>
	/// <summary>Removes all leading and trailing whitespace characters from the current string object.</summary>
	''.empty || (String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g, '');
	});

	/// <method>String.prototype.remove</method>
	/// <summary>Removes the specified string from the current string object.</summary>
	/// <param name="s" type="string">The string to check.</param>
	/// <param name="ignoreCase" type="boolean">Indicates whether to ignore case. If null or undefined, the removal is case-sensitive.</param>
	''.remove || (String.prototype.remove = function(s, ignoreCase){
			var reg = new RegExp(s.escape(), ((ignoreCase && ignoreCase == true) ? 'gi' : 'g'));
			return this.replace(reg, '');
	});

	/// <method>String.prototype.startsWith</method>
	/// <summary>Indicates if the current string object starts with the specified string.</summary>
	/// <param name="s" type="string">The string to look for.</param>
	/// <param name="ignoreCase" type="boolean">Indicates whether to ignore case. If null or undefined, the removal is case-sensitive.</param>
	''.startsWith || (String.prototype.startsWith = function(s, ignoreCase){
			var reg = new RegExp('^' + s, (ignoreCase && ignoreCase == true ? 'i' : null));
			return reg.test(this); 
	});

	/// <method>String.prototype.endsWith</method>
	/// <summary>Indicates if the current string object starts with the specified string.</summary>
	/// <param name="s" type="string">The string to look for.</param>
	/// <param name="ignoreCase" type="boolean">Indicates whether to ignore case. If null or undefined, the removal is case-sensitive.</param>
	''.endsWith || (String.prototype.endsWith = function(s, ignoreCase){
			var reg = new RegExp(s + '$', (ignoreCase && ignoreCase == true ? 'i' : null));
			return reg.test(this); 
	});

	/// <method>String.prototype.contains</method>
	/// <summary>Indicates if the current string object contains the specified string.</summary>
	/// <param name="s" type="string">The string to look for.</param>
	''.contains || (String.prototype.contains = function(s){
			return this.indexOf(s) > -1;
	});
