/**
 * Classy 
 *
 * Provides the Classy namespace and a set of utils to speed up development
 *
 * @author		Charles Demers (charles.demers6@gmail.com)
 * @provides 	[classy]
 *
 */

var classy = {
	
	typeOf: function(obj) {
		if(obj === undefined) { return 'undefined'; }
		if(obj === null) { return 'null'; }
		if(Object.prototype.toString.call(obj) == "[object Array]") { return 'array'; }
		if(Object.prototype.toString.call(obj) == "[object RegExp]") { return 'regexp'; }
		if(Object.prototype.toString.call(obj) == "[object Date]") { return 'date'; }
		if(typeof obj == "number" && !isFinite(obj)) { return 'nan'; }
		if(obj.callee) { return 'arguments'; }
		if(obj.item) { return 'collection'; }
		if(obj.nodeName && obj.nodeType) {
			if(obj.nodeType == 1) { return 'element'; }
			if(obj.nodeType == 3) { return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; }
		}
		return typeof obj;
	},
	unlink:function(obj) {
		var ret;
		switch (classy.typeOf(obj)) {
			case 'object':
				ret = classy.hash.clone(obj);
				break;
			case 'array':
				ret = classy.array.clone(obj);
				break;
			default: 
				ret = obj;
				break;
		}
		return ret;
	},
	guid:function() {
		var guid = 0;
		classy.guid = function() {
			return guid++;
		};
		return classy.guid();
	}
};


/**
 *
 * Array utils
 *
 */
classy.array = {
	clone: function(arr) {
		var ret = [];
		for(var i=0, l=arr.length; i<l; i++) {
			ret[i] = classy.unlink(arr[i]);
		}
		return ret;
	},
	associate: function(keys, values) {
		var ret = {};
		for(var i=0, l=keys.length; i<l; i++) {
			ret[keys[i]] = values[i];
		}
		return ret;
	},
	clean: function(arr) {
		var ret = [];
		for(var i=arr.length; i--; ) {
			if(arr[i] != null) {
				ret[i] = arr[i];
			}
		}
		return ret;
	},
	combine: function(arr, arr2) {
		var ret = arr.clone();
		for(var i=0, l=arr.length; i<l; i++) {
			if(classy.array.contains(ret, arr2[i]) === false) {
				ret.push(arr2[i]);
			}
		}
		return ret;
	},
	contains: function(arr, obj, from) {
		from = from || 0;
		for(var i=from, l=arr.length; i<l; i++) {
			if(arr[i] === obj) {
				return true;
			}
		}
		return false;
	},
	empty: function(arr) {
		for(var i=arr.length; i--; ) {
			delete arr[i];
		}
		arr.length = 0;
		return arr;
	},
	every: function(arr, fn /*, scope*/) {
		if(Array.prototype.every) {
			classy.array.every = function(arr, fn) {
				return Array.prototype.every.call(arr, fn, arguments[2]);
			};
		} else {
			classy.array.every = function(arr, fn) {
				scope = arguments[2];
				for(var i=0, l=arr.length; i<l; i++) {
					if(i in arr && !fn.call(scope, arr[i], i, arr)) {
						return false;
					}
				}
				return true;
			};
		}
		return classy.array.every(arr, fn, scope);
	},
	extend: function(arr, extend) {
		for(var i=0, l=extend.length; i<l; i++) {
			arr.push(extend[i]);
		}
		return arr;
	},
	filter: function(arr, fn, scope) {
		if(Array.prototype.filter) {
			classy.array.filter = function(arr, fn, scope) {
				return Array.prototype.filter.call(arr, fn, scope);
			};
		} else {
			classy.array.filter = function(arr, fn, scope) {
				scope = scope || arr;
				var ret = [];
				for(var i=0, l=arr.length; i<l; i++) {
					if(fn.call(scope, arr[i], i, arr)) {
						ret.push(arr[i]);
					}
				}
				return ret;
			};
		}
		return classy.array.filter(arr, fn, scope);
	},
	flatten: function(arr) {
		var ret = [];
		for(var i=0, l=arr.length; i<l; i++) {
			if(classy.typeOf(arr[i]) == "array") {
				var level = classy.array.flatten(arr[i]);
				for(var j=0, len=level.length; j<len; j++) {
					ret.push(level[j]);
				}
			} else {
				ret.push(arr[i]);
			}
		}
		return ret;
	},
	forEach: function(arr,fn,scope) {
		if(Array.prototype.forEach) {
			classy.array.forEach = function(arr, fn, scope) {
				Array.prototype.forEach.call(arr, fn, scope);
			};
		} else {
			classy.array.forEach = function(arr,fn,scope) {
				scope = scope || arr;
				for(var i=0, l=arr.length; i<l; i++) {
					fn.call(scope,arr[i], i);
				}
			};
		}
		classy.array.forEach(arr, fn, scope);
	},
	getLast: function(arr){
		return arr[arr.length-1] || null;
	},
	getRandom: function(arr){
		return arr[classy.number.random(0,arr.length-1)] || null;
	},
	include: function(arr, obj){
		if(classy.array.contains(arr, obj) === false){
			arr.push(obj);
		}
		return arr;
	},
	indexOf: function(arr, obj /*, from*/) {
		if(Array.prototype.indexOf) {
			classy.array.indexOf = function(arr, obj) {
				return Array.prototype.indexOf.call(arr, obj, arguments[2]);
			};
		} else {
			classy.array.indexOf = function(arr, obj) {
				if(arr.length === 0) {
					return -1;
				}
				from = arguments[2] || 0;
				for(var i=from, l=arr.length; i<l; i++) {
					if(i in arr && arr[i] === obj) {
						return i;
					}
				}
				return -1;
			};
		}
		return classy.array.indexOf(arr, obj, from);
	},
	indexesOf: function(arr, obj /*, from*/) {
		if(arr.length === 0) {
			return -1;
		}
		var ret = [];
		from = arguments[2] || 0;
		for(var i=from, l=arr.length; i<l; i++) {
			if(i in arr && arr[i] === obj) {
				ret.push(i);
			}
		}
		if(ret.length > 0) {
			return ret;
		}
		return null;
	},
	lastIndexOf: function(arr, obj /*, from*/) {
		if(Array.prototype.lastIndexOf) {
			classy.array.lastIndexOf = function(arr, obj) {
				return Array.prototype.lastIndexOf.call(arr, obj, arguments[2]);
			};
		} else {
			classy.array.lastIndexOf = function(arr, obj) {
				from = arguments[2] || arr.length;
				for(var i=from; i--; ) {
					if(i in arr && arr[i] === obj) {
						return i;
					}
				}
				return -1;
			};
		}
		return classy.array.lastIndexOf(arr, obj, from);
	},
	lastIndexesOf: function(arr, obj /*, from*/) {
		var ret = [];
		from = arguments[2] || arr.length;
		for(var i=from; i--; ){
			if(i in arr && arr[i] === obj) {
				ret.push(i);
			}
		}
		if(ret.length > 0) {
			return ret;
		}
		return null;
	},
	map: function(arr, fn, scope) {
		if(Array.prototype.map) {
			classy.array.map = function(arr, fn, scope) {
				return Array.prototype.map.call(arr, fn, scope);
			};
		} else {
			classy.array.map = function(arr, fn, scope) {
				scope = scope || arr;
				var ret = [];
				for(var i=0,l=arr.length; i<l; i++) {
					ret.push(fn.call(scope, arr[i], i, arr));
				}
				return ret;
			};
		}
		return classy.array.map(arr, fn, scope);
	},
	remove: function(arr, obj) {
		for(var i=0, l=arr.length; i<l; i++) {
			if(arr[i] === obj) {
				arr.splice(i, 1);
			}
		}
		return arr;
	},
	some: function(arr, fn, scope) {
		if(Array.prototype.some) {
			classy.array.some = function(arr, fn, scope) {
				return Array.prototype.some.call(arr, fn, scope);
			};
		} else {
			classy.array.some = function(arr, fn, scope) {
				scope = scope || arr;
				for(var i=0, l=arr.length; i<l; i++) {
					if(fn.call(scope,arr[i],i,arr)) {
						return true;
					}
				}
				return false;
			};
		}
		return classy.array.some(arr, fn, scope);
	},
	unique: function(arr) {
		var ret = [];
		for(var i=0,l=arr.length; i<l; i++) {
			if(classy.array.indexOf(ret, arr[i]) == -1) {
				ret.push(arr[i]);
			}
		}
		return ret;
	}
};


/*
 * Provides : Object utils
 */
classy.hash = {
	
	clone: function(obj) {
		var ret = {};
		for(var n in obj) {
			ret[n] = classy.unlink(obj[n]);
		}
		return ret;
	},
	extend: function(obj, extending) {
		for(var n in extending){
			obj[n] = extending[n];
		}
		return obj;
	},
	add: function(obj, key, value) {
		if(!obj.hasOwnProperty(key)) {
			obj[key] = value;
		}
		return this;
	},
	combine: function(obj,combining) {
		for(var n in combining) {
			if(!obj.hasOwnProperty(n)) {
				obj[n] = combining[n];
			}
		}
		return obj;
	},
	empty: function(obj) {
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				delete obj[n];
			}
		}
		return obj;
	},
	every: function(obj, fn, scope) {
		scope = scope || obj;
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				if(!fn.call(scope, obj[n], n, obj)) {
					return false;
				}
			}
		}
		return true;
	},
	forEach: function(obj, fn, scope) {
		scope = scope || obj;
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				fn.call(scope, obj[n], n, obj);
			}
		}
	},
	filter: function(obj, fn, scope) {
		scope = scope || obj;
		var ret = {};
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				if(fn.call(scope,obj[n],n,obj)) {
					ret[n] = obj[n];
				}
			}
		}
		return ret;
	},
	getKeys: function(obj) {
		var ret = [];
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				ret.push(n);
			}
		}
		return ret;
	},
	getValues: function(obj) {
		var ret = [];
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				ret.push(obj[n]);
			}
		}
		return ret;
	},
	has: function(obj, key) {
		return obj.hasOwnProperty(key);
	},
	hasValue: function(obj, value) {
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				if(obj[n] == value){
					return true;
				}
			}
		}
		return false;
	},
	keyOf: function(obj, value) {
		for(var n in obj){
			if(obj.hasOwnProperty(p)) {
				if(obj[n] == value){
					return n;
				}
			}
		}
		return false;
	},
	keysOf: function(obj, value) {
		var ret = [];
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				if(obj[n] == value){
					ret.push(n);
				}
			}
		}
		if(ret.length > 0) {
			return ret;
		}
		return false;
	},
	length: function(obj) {
		var ret = 0;
		for(var n in obj) { 
			if(obj.hasOwnProperty(n)) {
				ret++;
			}
		}
		return ret;
	},
	map: function(obj, fn, scope) {
		scope = scope || obj;
		var ret = {};
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				ret[n] = fn.call(scope, obj[n], n, obj);
			}
		}
		return ret;
	},
	remove: function(obj, key) {
		if(obj.hasOwnPropety(key)) {
			delete obj[key];
		}
		return obj;
	},
	removeValue: function(obj, removing) {
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				if(obj[n] == removing) {
					delete obj[n];
				}
			}
		}
		return obj;
	},
	some: function(obj, fn, scope) {
		scope = scope || obj;
		for(var n in obj) {
			if(obj.hasOwnProperty(n)) {
				if(fn.call(scope, obj[n], n, obj)) {
					return true;
				}
			}
		}
		return false;
	}
};


/*
 * Provides : Number utils
 */
classy.number = {
	random: function(min, max) {
		return Math.floor(Math.random() * (max - min + 1) + min);
	},
	limit: function(num, min, max) {
		if(num < min) {
			return min;
		} else if(num > max) {
			return max;
		}
		return num;
	}
};


/*
 * Provides : String utils
 */
classy.string = {
	camelCase: function(str) {
		var strList = str.split("-");
		if(strList.length == 1) {
			return strList[0];
		}
	
		var camelizedStr = strList[0];
		for(var i=1, l=strList.length; i<l; i++) {
			var string = strList[i];
			camelizedStr += string.charAt(0).toUpperCase() + string.substring(1);
		}
		return camelizedStr;
	},
	capitalize: function(str) {
		var strList = str.split(" ");
		var capitalizedStr = "";
		for(var i=0, l=strList.length; i<l; i++) {
			var string = strList[i];
			capitalizedStr += string.charAt(0).toUpperCase() + string.substring(1)+" ";
		}
		return capitalizedStr.substring(0,str.length);
	},
	hyphenate: function(str) {
		var hyphenatedStr = str.charAt(0);
		for(var i=1, l=str.length; i<l; i++) {
			var curChar = str.charAt(i);
			if(/[A-Z]/.test(curChar)) {
				hyphenatedStr += "-"+curChar.toLowerCase();
			} else {
				hyphenatedStr += curChar;
			}
		}
		return hyphenatedStr;
	},
	stripTags: function(str) {
		return str.replace(/<\/?[^>]+>/gi,'');
	},
	toFloat: function(str) {
		var number = parseFloat(str);
		return isFinite(number) ? number : null;
	},
	toInt: function(str) {
		var number = parseInt(str, 10);
		return isFinite(number) ? number : null;
	},
	supplant: function(str, obj, regexp) {
		regexp = regexp || new RegExp(/{([^{}]*)}/g);
		return str.replace(regexp, function (a, b) {
				var r = obj[b];
				return (typeof r === 'string' || typeof r === 'number') ? r : a;
		});
	},
	trim: function(str) {
		return str.replace(/^\s+|\s+$/g, "");
	}
};


/*
 * Provides : Function utils
 */
classy.func = {
	bind:function(func,scope, args){
		args = (classy.typeOf(args) == "array") ? args : [args];
		return function(){
			func.apply(scope, args);
		};
	}
};
