/*
 * Script: Core.js
 * @author Charles Demers
 * @version 0.1
 * 
 * Based on Mootools (http://www.mootools.net) Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/)
 * 
 * Provides: [utils, Native, Hash]
 *
 */

var typeOf = function(obj){
	if(obj === undefined){ return 'undefined'; }
	if(obj === null){ return 'null'; }
	if(obj.$type){ return obj.$type.toLowerCase(); }
	if(typeof obj == "number" && !isFinite(obj)){ return false; }
	if(obj.callee){ return 'arguments'; }
	if(obj.item){ return 'collection'; }
	if(obj.nodeName){
		switch(obj.nodeType){
			case 1: return 'element';
			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
		}
	}
	return (typeof obj);
};

Object.clone = function(){
	var ret = {};
	for(var p in object){ ret[p] = unlink(obj[p]); }
	return ret;
};
Array.clone = function(){
	var ret = [];
	for (var i=0, l=obj.length; i<l; i++){ ret[i] = unlink(obj[i]); }
	return ret;
};

var unlink = function(obj){
	switch (typeOf(obj)){
		case 'object':
			return Object.clone(obj);
		break;
		case 'hash':
			return new Hash(obj);
		break;
		case 'array':
			return Array.clone(obj);
		break;
		default: return obj;
	}
};

Array.from = function(obj){
	return (typeOf(obj) == 'array') ? obj : (typeOf(obj) !== 'function') ? Array.prototype.slice.call(obj) : [obj];
};


function clearTimer(timer){
	clearInterval(timer);
	clearTimeout(timer);
}


function Native(opts){
	
	var object = opts.initialize;
	
	object.prototype.$type = opts.name;
	
	object.implement = function(methods){
		for(var m in methods){
			if(this.prototype.hasOwnProperty(m) === false || (this.prototype.hasOwnProperty(m) && this.prototype[m]._protected !== true)){
				this.prototype[m] = methods[m];
			}
		}
	};
	
	object.alias = function(method,alias){
		this.prototype[alias] = this.prototype[method];
	};
	
	return object;
}

(function(){
	var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Hash':Hash, 'Number': Number, 'RegExp': RegExp, 'String': String};
	for (var n in natives){
		new Native({name: n, initialize: natives[n]});
	}
	
	var protectedMethods = {
		'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
		'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
	};
	for (var m in protectedMethods){
		for (var i=0,l=n.length; i<l; i++){
			if(natives[m].prototype[protectedMethods[m][i]]){
				natives[m].prototype[protectedMethods[m][i]]._protected = true;
			}
		}
	}
})();


/*
 * Provides : Array utils
 */
Array.implement({
	associate:function(keys){
		var ret = {};
		for(var i=0,l=keys.length; i<l; i++){
			ret[keys[i]] = this[i];
		}
		return ret;
	},
	clean:function(){
		var ret = [];
		for(var i=0,l=this.length; i<l; i++){
			if(this[i] !== null){
				ret[i] = this[i];
			}
		}
		return ret;
	},
	clone:function(){
		return unlink(this);
	},
	combine:function(arr){
		var ret = this.clone();
		for(var i=0, l=arr.length; i<l; i++){
			if(ret.contains(arr[i]) === false){
				ret.push(arr[i]);
			}
		}
		return ret;
	},
	contains:function(obj,from){
		from = (from) ? from : 0;
		for(var i=from, l=this.length; i<l; i++){
			if(this[i] === obj){ return true; }
		}
		return false;
	},
	empty:function(){
		for(var i=this.length; i>=0; i--){ delete this[i]; }
		this.length = 0;
		return this;
	},
	every:function(fn,scope){
		scope = (scope) ? scope : this;
		for(var i=0, l=this.length; i<l; i++){
			if(!fn.call(scope,this[i],i,this)){ return false; }
		}
		return true;
	},
	extend:function(arr){
		for(var i=0, l=arr.length; i<l; i++){ this.push(arr[i]); }
		return this;
	},
	filter:function(fn,scope){
		scope = (scope) ? scope : this;
		var ret = [];
		for(var i=0,l=this.length; i<l; i++){
			if(fn.call(scope,this[i],i,this)){ ret.push(this[i]); }
		}
		return ret;
	},
	flatten:function(){
		var ret = [];
		for(var i=0,l=this.length; i<l; i++){
			if(this[i] instanceof Array){
				var level = this[i].flatten();
				for(var j=0,len=level.length; j<len; j++){
					ret.push(level[j]);
				}
			} else {
				ret.push(this[i]);
			}
		}
		return ret;
	},
	forEach:function(fn,scope){
		scope = (scope) ? scope : this;
		for(var i=0, l=this.length; i<l; i++){ fn.call(scope,this[i],i); }
	},
	getLast:function(){
		return this[this.length-1] || null;
	},
	getRandom:function(){
		return this[CoreMath.random(0,this.length-1)] || null;
	},
	include:function(obj){
		if(this.contains(obj) === false){
			this.push(obj);
		}
		return this;
	},
	indexOf:function(obj,from){
		from = (from) ? from : 0;
		for(var i=from, l=this.length; i<l; i++){
			if(this[i] === obj){ return i; }
		}
		return -1;
	},
	indexesOf:function(obj,from){
		var ret = [];
		from = (from) ? from : 0;
		for(var i=from, l=this.length; i<l; i++){
			if(this[i] === obj){ ret.push(i); }
		}
		if(ret.length > 0){ return ret; }
		return null;
	},
	lastIndexOf:function(obj,from){
		from = (from) ? from : this.length;
		for(var i=from; i>=0; i--){
			if(this[i] === obj){ return i; }
		}
		return -1;
	},
	lastIndexesOf:function(obj,from){
		var ret = [];
		from = (from) ? from : this.length;
		for(var i=from; i>=0; i--){
			if(this[i] === obj){ ret.push(i); }
		}
		if(ret.length > 0){ return ret; }
		return null;
	},
	map:function(fn,scope){
		scope = (scope) ? scope : this;
		var ret = [];
		for(var i=0,l=this.length; i<l; i++){
			ret.push(fn.call(scope,this[i],i,this));
		}
		return ret;
	},
	remove:function(obj){
		for(var i=0,l=this.length; i<l; i++){
			if(this[i] === obj){
				this.splice(i,1);
			}
		}
		return this;
	},
	some:function(fn,scope){
		scope = (scope) ? scope : this;
		for(var i=0, l=this.length; i<l; i++){
			if(fn.call(scope,this[i],i,this)){ return true; }
		}
		return false;
	},
	unique:function(){
		var ret = [];
		for(var i=0,l=this.length; i<l; i++){
			if(ret.indexOf(this[i]) == -1){
				ret.push(this[i]);
			}
		}
		return ret;
	}
});
Array.alias('forEach','each');


/*
 * Provides : Hash
 */
function Hash(obj){
	if (typeOf(obj) == 'hash'){
		 object = unlink(obj.getClean());
	}
	for(var p in obj){ this[p] = obj[p]; }
	return this;
}
Hash.implement({
	add:function(key,value){
		if(this.hasOwnProperty(key) === false){ this[key] = value; }
		return this;
	},
	clone:function(){
		var ret = {};
		for(var p in this){ 
			if(this.hasOwnProperty(p)){
				ret[p] = this[p];
			}
		}
		return new Hash(ret);
	},
	combine:function(obj){
		for(var p in obj){
			if(this.hasOwnProperty(p) === false){
				this[p] = obj[p];
			}
		}
		return this;
	},
	empty:function(){
		for(var p in this){
			if(this.hasOwnProperty(p)){
				delete this[p];
			}
		}
		return this;
	},
	every:function(fn,scope){
		scope = (scope) ? scope : this;
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(!fn.call(scope,this[p],p,this)){ return false; }
			}
		}
		return true;
	},
	extend:function(obj){
		for(var p in obj){
			this[p] = obj[p];
		}
		return this;
	},
	forEach:function(fn,scope){
		scope = (scope) ? scope : this;
		for(var p in this){
			if(this.hasOwnProperty(p)){
				fn.call(scope,this[p],p,this);
			}
		}
	},
	filter:function(fn,scope){
		scope = (scope) ? scope : this;
		var ret = new Hash();
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(fn.call(scope,this[p],p,this)){
					ret.set(p,this[p]);
				}
			}
		}
		return ret;
	},
	get:function(key){
		if(key.indexOf(".") == -1){
			if(this.hasOwnProperty(key)){
				return this[key];
			} else {
				return null;
			}
		}
		var keys = key.split(".");
		if(this.hasOwnProperty(keys[0])){
			var ret = this[keys[0]];
		} else {
			return null;
		}
		for(var i=1,l=keys.length; i<l; i++){
			if(ret.hasOwnProperty(keys[i])){
				ret = ret[keys[i]];
			} else {
				return null;
			}
		}
		return ret;
	},
	getClean:function(){
		var ret = {};
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(typeOf(this[p]) == "hash"){
					ret[p] = this[p].getClean();
				} else {
					ret[p] = this[p];
				}
			}
		}
		return ret;
	},
	getKeys:function(){
		var ret = [];
		for(var p in this){
			if(this.hasOwnProperty(p)){
				ret.push(p); 
			}
		}
		return ret;
	},
	getValues:function(){
		var ret = [];
		for(var p in this){
			if(this.hasOwnProperty(p)){
				ret.push(this[p]);
			}
		}
		return ret;
	},
	hasKey:function(key){
		if(this.hasOwnProperty(key)){ return true; }
		return false;
	},
	hasValue:function(value){
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(this[p] == value){ return true; }
			}
		}
		return false;
	},
	keyOf:function(value){
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(this[p] == value){ return p; }
			}
		}
		return false;
	},
	keysOf:function(value){
		var ret = [];
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(this[p] == value){ ret.push(p); }
			}
		}
		if(ret.length > 0){ return ret; }
		return false;
	},
	length:function(){
		var ret = 0;
		for(var p in this){ 
			if(this.hasOwnProperty(p)){ ret++; }
		}
		return ret;
	},
	map:function(fn,scope){
		scope = (scope) ? scope : this;
		var ret = new Hash();
		for(var p in this){
			if(this.hasOwnProperty(p)){
				ret.set(p,fn.call(scope,this[p],p,this));
			}
		}
		return ret;
	},
	remove:function(key){
		if(this.hasOwnProperty(key)){
			delete this[key];
		}
		return this;
	},
	removeObject:function(obj){
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(this[p] == obj){ delete this[p]; }
			}
		}
		return this;
	},
	set:function(key,value){
		this[key] = value;
	},
	some:function(fn, scope){
		scope = (scope) ? scope : this;
		for(var p in this){
			if(this.hasOwnProperty(p)){
				if(fn.call(scope,this[p],p,this)){ return true; }
			}
		}
		return false;
	}
});
Hash.alias('forEach','each');


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


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


/*
 * Provides : Function utils
 */
Function.implement({
	bind:function(scope, args){
		var that = this;
		args = (typeOf(args) == "array") ? args : [args];
		return function(){
			that.apply(scope, args);
		};
	},
	delay:function(delay, scope, args){
		var that = this;
		var t = typeOf(scope);
		var s,a;
		if(t == "array"){
			s = this;
			a = scope;
		} else {
			s = (scope) ? scope : this;
			a = (args) ? args : [];
		}
		return (setTimeout(function(){
			that.apply(s,a);
		},delay));
	},
	periodical:function(delay, scope, args){
		var that = this;
		var t = typeOf(scope);
		var s,a;
		if(t == "array"){
			s = this;
			a = scope;
		} else {
			s = (scope) ? scope : this;
			a = (args) ? args : [];
		}
		return (setInterval(function(){
			that.apply(s,a);
		},delay));
	},
	extend:function(other){
		source = (typeof other == "function") ? other.prototype : other;
		for(var p in source){
			this.prototype[p] = source.prototype[p];
		}
		return this;
	},
	implement:function(other){
		source = (typeof other == "function") ? other.prototype : other;
		for(var p in source){
			if(this.prototype.hasOwnProperty(p) === false){
				this.prototype[p] = source[p];
			}
		}
		return this;
	}
});
