/*
Native Extensions ported and modified by Scott Kyle from:
	MooTools - My Object Oriented JavaScript Tools.

License:
	MIT-style license.

Copyright:
	Copyright (c) 2006-2009 [Valerio Proietti](http://mad4milk.net/).

Code & Documentation:
	[From the MooTools Documentation](http://mootools.net/docs/core/Class/Class).
*/

(function(Number, String, Function, Array, Object, RegExp, Date){
	
	// nil

	var nil = function(item){
		return (item != null && item != nil) ? item : null;
	};
	
	// Accessor multipliers
 
	Function.prototype.setMany = function(){
		var one = this, many = function(item){
			var value = this;
			for (var key in item) value = one.call(this, key, item[key]);
			return value;
		};
	
		return function(item){
			return ((typeof item == 'string') ? one : many).apply(this, arguments);
		};
	};
	
	Function.prototype.getMany = function(){
		var one = this, many = function(item){
			var object = {};
			for (var i = 0; i < item.length; i++) object[item[i]] = one.call(this, item[i]);
			return object;
		};
	
		return function(item){
			return ((typeof item == 'string') ? one : many).apply(this, arguments);
		};
	};
	
	// Function extend, implement
	
	Function.prototype.extend = function(key, value){
		this[key] = value;
		return this;
	}.setMany();
	
	Function.prototype.implement = function(key, value){
		this.prototype[key] = value;
		return this;
	}.setMany();
	
	// From
 
	Function.from = function(item){
		return (typeof item == 'function') ? item : function(){
			return item;
		};
	};
	
	Array.from = function(item, slice){
		return (item == null) ? [] : (Native.isEnumerable(item)) ? Array.prototype.slice.call(item, slice || 0) : [item];
	};
	
	Number.from = Number;
	String.from = String;
	
	// hide, protect
	
	Function.implement({
	
		hide: function(){
			this._hidden_ = true;
			return this;
		},
		
		protect: function(){
			this._protected_ = true;
			return this;
		}
	
	});
	
	// Native
	
	var Native = function(name, object){
		
		if (object == null) return null;

		object.extend(this);
		object.constructor = Native;
		object.prototype.constructor = object;
		
		return object;
	};
		
	Native.isEnumerable = function(item){
		return (typeof item == 'object' && typeof item.length == 'number');
	};
	
	Native.implement({
		
		implement: function(name, method){
			
			if (method && method._hidden_) return this;
			
			var previous = this.prototype[name];
			if (previous == null || !previous._protected_) this.prototype[name] = method;
			
			// genericize
			if (typeof method == 'function' && this[name] == null) this.extend(name, function(item){
				return method.apply(item, Array.prototype.slice.call(arguments, 1));
			});
			
			return this;
			
		}.setMany(),
		
		extend: function(name, method){
			if (method && method._hidden_) return this;
			var previous = this[name];
			if (previous == null || !previous._protected_) this[name] = method;
			return this;
		}.setMany(),
		
		alias: function(name, proto){
			return this.implement(name, this.prototype[proto]);
		}.setMany()
		
	});
	
	
	// Default Natives
	
	var force = function(type, methods){
		var object = this[type];
		for (var i = 0; i < methods.length; i++){
			var name = methods[i];
			var proto = object.prototype[name];
			var generic = object[name];
			if (generic) generic.protect();
			
			if (proto){
				proto.protect();
				delete object.prototype[name];
				object.prototype[name] = proto;
			}
		}
		new Native(type, object).implement(object.prototype);
	};
	
	force('Array', [
		'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice',
		'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight'
	]);
	
	force('String', [
		'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
		'slice', 'split', 'substr', 'substring', 'toLowerCase', 'toUpperCase'
	]);
	
	force('Number', ['toExponential', 'toFixed', 'toLocaleString', 'toPrecision']);
	
	force('Function', ['apply', 'call']);
	
	force('RegExp', ['exec', 'test']);
	
	force('Date', ['now']);
	
	new Native('Date', Date).extend('now', function(){
		return +(new Date);
	});
	
	// forEach
 
	Object.extend({
	
		forEach: function(object, fn, bind){
			for (var key in object) fn.call(bind, object[key], key, object);
		},
		
		each: function(object, fn, bind){
			Object.forEach(object, fn, bind);
			return object;
		}
	
	});
	
	Array.implement({
	
		forEach: function(fn, bind){
			for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
		},
		
		each: function(fn, bind){
			this.forEach(fn, bind);
			return this;
		}
		
	});
	
	Array.each = function(self, fn, bind){
		Array.forEach(self, fn, bind);
		return self;
	};
	
	
	// String
	
	String.implement({

		test: function(regex, params){
			return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
		},
	
		contains: function(string, separator){
			return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
		},
	
		trim: function(){
			return this.replace(/^\s+|\s+$/g, '');
		},
	
		clean: function(){
			return this.replace(/\s+/g, ' ').trim();
		},
	
		camelCase: function(){
			return this.replace(/-\D/g, function(match){
				return match.charAt(1).toUpperCase();
			});
		},
	
		hyphenate: function(){
			return this.replace(/[A-Z]/g, function(match){
				return ('-' + match.charAt(0).toLowerCase());
			});
		},
	
		capitalize: function(){
			return this.replace(/\b[a-z]/g, function(match){
				return match.toUpperCase();
			});
		},
	
		escapeRegExp: function(){
			return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
		},
	
		substitute: function(object, regexp){
			return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
				if (match.charAt(0) == '\\') return match.slice(1);
				return (object[name] != null) ? object[name] : '';
			});
		}
	
	});
	
	
	// Number
	
	Number.extend({
	
		random: function(min, max){
			return Math.floor(Math.random() * (max - min + 1) + min);
		},
		
		toInt: function(number, base){
			return parseInt(number, base || 10);
		},
		
		toFloat: function(number){
			return parseFloat(number);
		}
	
	});
	
	Number.implement({
	
		limit: function(min, max){
			return Math.min(max, Math.max(min, this));
		},
	
		round: function(precision){
			precision = Math.pow(10, precision || 0);
			return Math.round(this * precision) / precision;
		},
	
		times: function(fn, bind){
			for (var i = 0; i < this; i++) fn.call(bind, i, null, this);
		}
	
	});
	
	['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan'].forEach(function(name){
		Number.extend(name, Math[name]).implement(name, function(){
			return Math[name].apply(null, [this].concat(Array.slice(arguments)));
		});
	});
	
	
	// Array
	
	Array.implement({

		filter: function(fn, bind){
			var results = [];
			for (var i = 0, l = this.length; i < l; i++){
				if (fn.call(bind, this[i], i, this)) results.push(this[i]);
			}
			return results;
		},
	
		indexOf: function(item, from){
			for (var l = this.length, i = (from < 0) ? Math.max(0, l + from) : from || 0; i < l; i++){
				if (this[i] === item) return i;
			}
			return -1;
		},
	
		map: function(fn, bind){
			var results = [];
			for (var i = 0, l = this.length; i < l; i++) results.push(fn.call(bind, this[i], i, this));
			return results;
		},
		
		every: function(fn, bind){
			for (var i = 0, l = this.length; i < l; i++){
				if (!fn.call(bind, this[i], i, this)) return false;
			}
			return true;
		},
	
		some: function(fn, bind){
			for (var i = 0, l = this.length; i < l; i++){
				if (fn.call(bind, this[i], i, this)) return true;
			}
			return false;
		},
		
		clean: function(){
			return this.filter(function(item){
				return item != null;
			});
		},
		
		pick: function(){
			for (var i = 0, l = this.length; i < l; i++){
				if (this[i] != null) return this[i];
			}
			return null;
		},
		
		call: function(name){
			var args = Array.slice(arguments, 1), results = [];
			for (var i = 0, j = this.length; i < j; i++){
				var item = this[i];
				results.push(item[name].apply(item, args));
			}
			return results;
		},
		
		append: function(array){
			this.push.apply(this, array);
			return this;
		},
	
		contains: function(item, from){
			return this.indexOf(item, from) != -1;
		},
	
		last: function(){
			return (this.length) ? this[this.length - 1] : null;
		},
	
		random: function(){
			return (this.length) ? this[Number.random(0, this.length - 1)] : null;
		},
	
		include: function(item){
			if (!this.contains(item)) this.push(item);
			return this;
		},
	
		combine: function(array){
			for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
			return this;
		},
	
		erase: function(item){
			for (var i = this.length; i--; i){
				if (this[i] === item) this.splice(i, 1);
			}
			return this;
		},
	
		empty: function(){
			this.length = 0;
			return this;
		},
	
		flatten: function(){
			var array = [];
			for (var i = 0, l = this.length; i < l; i++){
				var item = this[i];
				if (item != null) array = array.concat((Native.isEnumerable(item)) ? Array.flatten(item) : item);
			}
			return array;
		},
	
		item: function(at){
			if (at < 0) at = (at % this.length) + this.length;
			return (at < 0 || at >= this.length) ? null : this[at];
		},
		
		// EXTRAS
		
		shuffle: function() {
			for (var j, x, i = this.length; i; j = parseInt(Math.random() * i, 10), x = this[--i], this[i] = this[j], this[j] = x);
			return this;
		}
	
	});
	
	
	// Function
	
	Function.extend({

		argument: function(i){
			return function(){
				return arguments[i];
			};
		},
	
		clear: function(timer){
			clearInterval(timer);
			clearTimeout(timer);
			return null;
		},
	
		stab: function(){
			for (var i = 0, l = arguments.length; i < l; i++){
				try {
					return arguments[i]();
				} catch (e){}
			}
			return null;
		}
	
	});
	
	Function.implement({
	
		attempt: function(args, bind){
			try {
				return this.apply(bind, Array.from(args));
			} catch (e){
				return null;
			}
		},
	
		bind: function(bind, args){
			var self = this.unbind();
			if (args != null) args = Array.from(args);
			return function(){
				return self.apply(bind, args || arguments);
			}.extend('_bound_', self);
		},
		
		unbind: function(){
			return (this._bound_) ? this._bound_.unbind() : this;
		},
	
		delay: function(delay, bind, args){
			return setTimeout(this.bind(bind, args), delay);
		},
	
		pass: function(args, bind){
			return this.bind(bind, args);
		},
	
		periodical: function(periodical, bind, args){
			return setInterval(this.bind(bind, args), periodical);
		},
	
		run: function(args, bind){
			return this.apply(bind, Array.from(args));
		}
	
	});
	
	
	// Object
	
	Object.extend({
	
		from: function(keys, values){
			var object = {};
			for (var i = 0; i < keys.length; i++) object[keys[i]] = nil(values[i]);
			return object;
		},
		
		append: function(original, extended){
			for (var key in (extended || {})) original[key] = extended[key];
			return original;
		},
		
		map: function(object, fn, bind){
			var results = {};
			for (var key in object) results[key] = fn.call(bind, object[key], key, object);
			return results;
		},
		
		filter: function(object, fn, bind){
			var results = {};
			for (var key in object){
				if (fn.call(bind, object[key], key, object)) results[key] = object[key];
			}
			return results;
		},
		
		every: function(object, fn, bind){
			for (var key in object){
				if (!fn.call(bind, object[key], key)) return false;
			}
			return true;
		},
		
		some: function(object, fn, bind){
			for (var key in object){
				if (fn.call(bind, object[key], key)) return true;
			}
			return false;
		},
		
		keys: function(object){
			var keys = [];
			for (var key in object) keys.push(key);
			return keys;
		},
		
		values: function(object){
			var values = [];
			for (var key in object) values.push(object[key]);
			return values;
		}
		
	});
	
})(Number, String, Function, Array, Object, RegExp, Date);

