/*
	__foundation/_js/common.js
	purpose: common functions for a variety of sites	
	functions labeled with ($) depend on jQuery	
*/



_ = {}; // wrapper for our "common" functions, vars, etc. all functions should have their own "class" of Group, of general functions
//_FN = _; // alternate refrence to _ in case we ever have a conflict. might use extra ram, commented out until can test better.



// - lanugage
_.lang = {
	_ : {} // language subsets, or any non-literal language; ie arrays like months, days of the week, etc 
}



// - variables
_.vars = {
}


// -- today's date & time
var t_date = new Date();
_.vars.today = {
	"ts" : t_date, // timestamp
	"month_int": t_date.getMonth() + 1, // month (int)
	"day_int" : t_date.getDate() // day (int)
};


// -- user. would hold a variety of info, where they are on a site, etc.
_.vars.user = {};










// - functions


// -- general functions


// --- determines if @a is an array (by default arrays are interpreted as 'object')
_.isArray = function(a) {
 return a.constructor == Array;
}


// --- determines if @value is between two other values. option: strict [can not equal to]
_.isBetween = function(value, value_min, value_max, strict) {
	if(strict) {
		return (value > value_min && value < value_max);
	} else {
		return (value >= value_min && value <= value_max);
	}
}


// --- determines if @o is a jquery object
_.isJquery = function(o) {
	return(o instanceof jQuery);	
}


// --- determines if @n is a number
_.isNumber = function(n) {
 return !isNaN(parseFloat(n)) && isFinite(n);
}










// -- array (and object) functions
_.Arr = {

}










// -- browser functions
_.Bro = {

	// --- wrapper for console.log (firefox, ie9+)
	logToConsole: function(message, args) {
		try {
			console.log(message);
		} catch(err) {	
		}
	},
	
	
	// --- wrapper for console.debug (firefox, ie9+)
	debugToConsole: function(obj, args) {
		try {
			console.debug(obj);
		} catch(err) {	
		}
	},
	
	
	// --- dumps an array or object to the console.
	//	--- @limit (int) limit recursion
	dumpArray: function(obj, limit) {
		if(typeof(obj) != 'object') {
			_.Bro.logToConsole('dumpArray was run on "'+obj+'" which is not an object');
			return;	
		}
		
		function parseLevel(obj, limit, counter) {
			if(!counter) counter = 1;
			var at_limit = (counter > limit && limit > 0);
			var str = '';
			var tabs = _.Str.mul("\t", counter);
			
			for(var i in obj) {
				str += tabs+i+' => ';
				
				var type_of = typeof(obj[i]);
				
				
				if(type_of == 'object' && !at_limit) {
					str += "object {\n"+parseLevel(obj[i], limit, counter+1)+tabs+"}\n";
				} else if(type_of == 'function') { 
					str += type_of+"\n";			
				} else {
					str += obj[i]+"\n";
				}
			}
			
			return str;			
		}
		var str = "{\n"+parseLevel(obj, limit)+"\n}";
		_.Bro.logToConsole(str);
	},
	
	
	
	// = group: google analytics
	
	// --- facilitates page tracking for google analytics
	//	--- @method: click, hover, etc.
	gaTrackPage: function(url, method) {
		if(!url) return false;
		
		if(method) {
			url = _.Str.qsAdd(url, {'event':method});
		}
		
		try {
			_gaq.push(['_trackPageview', url]);
			//pageTracker._trackPageview(url);	
		} catch(err) {
			_.Bro.logToConsole(err);
		}	
	},
	
	
	// --- wrapper for event tracking for google analytics
	//	--- @category (str) The name you supply for the group of objects you want to track
	// --- @action (str) A string that is uniquely paired with each category, and commonly used to define the type of user interaction for the web object
	// --- @label (str) A string to provide additional dimensions to the event data
	// --- @value (int) An integer that you can use to provide numerical data about the user event
	gaTrackEvent: function(category, action, opt_label, opt_value) {
		if(!category || !action) return false;
		try {
			_gaq.push(['_trackEvent', category, action, opt_label, opt_value]);
			//_trackEvent(category, action, opt_label, opt_value);
		} catch(err) {
			_.Bro.logToConsole(err);
		}	
	},
	
	
	// = group: browser navigation, redirection, popups, etc
	
	// --- change URLs. optional target (frame / window) and properties (popup)
	redirect: function(url, target, properties) {
		
		if(!properties) properties = {};
		
		if(target) {
			if(!properties.name) properties.name = '_blank';
			window.open(url, properties.name);
		} else {
			window.location = url;
		}
	},
	
	
	// --- popup window, with focus
	smallWindow: function(url, id, width, height, properties) {
		
		properties_defaults = {
			toolbar: 0,
			scrollbars: 1,
			location: 0,
			statusbar: 0,
			menubar: 0,
			resizable: 1,
		};
		
		
		if(!properties) {
			properties = {};
		}
		
		if(!width) {
			width = 600;
		}
		
		if(!height) {
			height = 500;
		}
		
		
		properties_str = 'width='+width+',height='+height;
		for(var x in properties_defaults) {
			properties_str += ','+x+'='+((properties[x]) ? properties[x] : properties_defaults[x]);
		}
		
		
		eval("page_"+id+" = window.open(url, '" + id + "', properties_str);");
		
		try {
			if(window.focus) {
				eval("page_"+id).focus();
			}
		} catch(err) {
		}
		
		return false;
	},
	
	
	// ---- false link ($)
	jsLink: function(elem) {
		
		var link_info = _.Elem.linkToProperties(elem);
		
		if(!link_info) { 
			return false;
		}
		
		_.Bro.redirect(link_info.url, link_info.target);
		return true;		
	}
	
};










// -- html element functions ($)
_.Elem = {
	
	
	
	// --- returns proper key code
	getKeyCode: function(e) {
		if(typeof(e) != 'object') return false;
		return (e.keyCode) ? e.keyCode : e.which;
	},
	
	
	// --- returns top and left positions required to center an element
	//	@args (obj) :
	//		elem_container (obj): window(default) || containing element to center elem into
	//		position (str): absolute(default) || fixed
	//		adjust_to_fit (bool): if position is set to "fixed" but the item doesn't fit on the screen, we change it to absolute - allowing the user to scroll to see it.
	getCenterPosition: function(elem, args) {
		
		if(!args) args = {};
		if(!args.position) args.position = 'absolute';
		
		var values = {};
		
		var container_ref;
		var container_size = {};
		
		if(typeof(args.container) == 'object') {
			container_ref = 'element';	
			container_size.width + args.container.innerWidth();
			container_size.height = args.container.innerHeight();	
		} else {
			container_ref = 'window';	
			container_size.width = $(window).width();
			container_size.height = $(window).height();	
		}
		
		var elem_size = {
			width: elem.outerWidth(true),
			height: elem.outerHeight(true)
		};
		
		
		// ---- find center, relative to conainer
		values.top = ((container_size.height - elem_size.height) / 2);
		values.left = ((container_size.width - elem_size.width) / 2);
		
		
		// ---- if we're allowing position change, and the element is bigger than the container, we'll need to adjust
		if(args.adjust_to_fit && elem_size.width > container_size.width || elem_size.height > container_size.height) {
			args.position = 'absolute';			
		}
		
		values.position = args.position;
		
		// ---- account for scroll position
		if(container_ref == 'window' && args.position == 'absolute') {
			values.top += $(window).scrollTop();
			values.left += $(window).scrollLeft();
		}
		
		
		return values;				
	},
	
	
	
	// ---- returns common properties from any link
	linkToProperties: function(elem) {
		if(!elem.length) {
			return false;
		}
		
		return {
			url: elem.attr('href'),
			target: elem.attr('target')
		}
		
	},
	
	
	// ---- links elem to a child anchor. if no anchor is found, this behavior is ignored
	linkParent: function(elem, disable_child_links, off) {
				
		// ----- unset		
		if(off) {
			elem
				.unbind('click.linkParent')
				.css('cursor', 'default');
			return;	
		}
		
		// ----- set
		elem.each(function() {
			
			// ------ determine target anchor and it's relevant selector, based on the following options (in preferred order)
			var selectors = ['a.primary:first', 'a:first']
			
			var a;
			var selector;
			
			for (var i in selectors) {
				selector = selectors[i];
				a = $(this).find(selector);
				if(a.length > 0) break;
			}
			
			
			// ------ if we found no anchor, exit
			if(a.length == 0) return;
			
			
			// ------ otherwise, set the handlers
			$(this)
				.css('cursor', 'pointer')
				.bind('click.linkParent', function(e) {
					
					// ------- we clicked a link (or child thereof)
					if($(e.target).is('a, a > *')) {
						if(disable_child_links) {
							// -------- prevent default behavior
							e.prevent_default();
						} else {
							// ------- allow default behavior by simply exiting this function
							return;
						}
					}					
										
					var url = a.attr('href');
					var target = a.attr('target');
					var properties = {};
					_.Bro.redirect(url, target, properties);				
				});
		});
	},
	
	
	
	// ---- adds "active" class on hover, removes it on hover out
	activeOnHover: function(elem, off) {
		
		// ----- unset
		if(off) {
			elem
				.unbind('mouseenter.activeOnHover')
				.unbind('mouseleave.activeOnHover');
			return;
		}			
		
		// ----- set
		elem
			.bind("mouseenter.activeOnHover", function() {				
				$(this).addClass('active');
			})
			.bind("mouseleave.activeOnHover", function() {
				$(this).removeClass('active');
			});
	}
}










// -- math functions
_.Math = {
	
		
	// --- range (numerical only (for now)
	inRange: function(value, min, max, step) {
		
		value = parseInt(value, 10);
		min = parseInt(min, 10);
		max = parseInt(max, 10);
		step = parseInt(step, 10);
		
		if(
			(value < min) || // too low
			(max > 0 && value > max) || // too high
			(step > 0 && (value % step)) // imporper step
		) {
			return false;
		}
		
		
		
		return true;
	}
	
}










// -- string functions
_.Str = { // wrapper
	
	
	// = group: general
	
	// --- convert html entities into to html characters
	htmlEntityDecode: function(str) {
		try {
			var tarea=document.createElement('textarea');
			tarea.innerHTML = str; return tarea.value;
			tarea.parentNode.removeChild(tarea);
		} catch(e) {
			//for IE add <div id="htmlconverter" style="display:none;"></div> to the page
			document.getElementById("htmlconverter").innerHTML = '<textarea id="innerConverter">' + str + '</textarea>';
			var content = document.getElementById("innerConverter").value;
			document.getElementById("htmlconverter").innerHTML = "";
			return content;
		}
	},


	// --- appends @str to itself @num times. implementation: mul8
	mul: function(str, num) {
		var i = Math.ceil(Math.log(num) / Math.LN2),
		res = str;
		do {
			res += res;
		} while (0 < --i);
		return res.slice(0, str.length * num);
	},
	
	
	// -- search through string's characters one by one. If character is not in bag, append to return_string.
	stripCharsInBag: function(str, bag) {
		var return_string = "";	
		var i;
		for(i=0; i<str.length; i++) {
			// Check that current character isn't whitespace.
			var c = str.charAt(i);
			if (bag.indexOf(c) == -1) return_string += c;
		}
		return return_string;
	},
	
	
	// --- strips spaces from string
	stripSpaces: function(str) {
		var return_string = "";	
		var i;
		for(i=0; i<str.length; i++) {
			// Check that current character isn't whitespace.
			var c = str.charAt(i);
			if (c != " ") return_string += c;
		}
		return return_string;
	},
	
	
	// --- truncate text. warning: does not look for html tags (so far)
	truncate: function(str, length, options) {
		
		defaults = {
			end_with : '',
			entity_decode: true
		}
		
		if(!options) {
			options = defaults;
		} else {
			for(var i in defaults) {
				if(options[i] == undefined) {
					options[i] = defaults[i];
				}
			}
		}
		
		if(options.end_with) {
			length -= options.end_with.length;	
		}
		
		
		if(options.entity_decode) {
			str = _.Str.htmlEntityDecode(str);
		}
		
		return str.substring(0, length)+options.end_with;
	},
	
	
	// --- pads @num with 0's until it is as long as @count
	zeroPad: function(num, count) {
		var num = num+'';
		while(num.length < count) {
			num = "0"+num;
		}
		return num;
	},
	


	// = group: url, querystrings, etc
	
	// --- returns object of "get" variables
	getGet: function(url) {
		
		if(!url) url = document.location;
		
		var $_GET = {};
		url.search.replace(/\??(?:([^=]+)=([^&]*)&?)/g, function () {
			function decode(s) {
				return decodeURIComponent(s.split("+").join(" "));
			}
			$_GET[decode(arguments[1])] = decode(arguments[2]);
		});
		return $_GET;	
	},
	

	// --- converts a URL or filepath to relevant parts	
	/* TESTING
	var str = 'http://sumo.styeven.com:1200/../../75th/_media/memory-bank/stursberg/stursberg-hitlers-bunker.mp4?qsa=102#meep';
	str = 'http://glo/75/75th/memory-bank/peter-stursberg.shtml';
	#### need to test this one! str = 'http://www.getpersonas.com/en-US/persona/359572';
	var x = parseURL(str);
	alert(" protocol:"+x.protocol+"\n host:"+x.host+"\n port: "+x.port+"\n path: "+x.path+"\n file: "+x.file+"\n Extension:"+x.ext+"\n qs:"+x.qs+"\n hash:"+x.hash);
	*/
	
	parseURL: function(url) {
		
		if(!url) url = window.location.href;
		var expr = /^((https?|ftp)\:\/\/([\w-\d.]+)(:(\d{2,5}))?)?(([\w~,;\-\./?%&+#=]*)\/)?([\w\-\.]+)\.([^#?\s]+)(\?([^#?\s]+)?)?(#([\w\-]+)?)?/; /* 
		*/
		var matches = url.match(expr);
		
		if(!matches) {
			return {"url":"", protocol:"", host:"", port:"", path:"", filename:"", file:"", ext:"", qs:"", hash:""};
		}
		
		return {
			"url": matches[0],
			protocol: matches[2],
			host: matches[3],
			port: matches[5],
			path: matches[6],
			filename: matches[8]+'.'+matches[9],
			file: matches[8],
			ext: matches[9],
			//qs:matches[11],
			qs: _.Str.qsToObject(matches[11]),
			hash: matches[13]	
		}
	},	
	
	
	// --- add a querystring parmeter to a url
	//	@items (obj) key:value pairs
	qsAdd: function(url, items) {
		if(!url || typeof(items) != 'object' || items.length == 0) {
			// ---- nothing to do
			return url;
		}
		
		var has_qs = (url.indexOf('?') > -1);
		
		for(var param in items) {
			var value = items[param];
			var re = new RegExp("([?|&])"+param+"=.*?(&|$)","i");
			if(url.match(re)) {
				has_qs = true;
				url = url.replace(re,'$1'+param+"="+value+'$2');
			} else {
				var appender = (has_qs) ? '&' : '?';
				url = url+appender+param+"="+value;
			}
		}
		
		return url;
	},
	
	
	// --- remove a querystring parameter from a url
	//	@key (arr|str) parameter(s) to remove
	//	@remove_all (bool) ignore keys and remove entire querystring
	qsRemove: function(url, keys, remove_all) {
		if(!url || (!keys && !remove_all)) {
			// ---- nothing to do
			return url;
		}
		
		// ---- convert string key to array
		if(keys && typeof(keys) != 'object') {
			keys = [keys];	
		}
		
				
		var urlparts = url.split('?');
		if(urlparts.length >= 2) {
			if(remove_all) return urlparts[0];
			
			var pars = urlparts[1].split(/[&;]/g);
			for(var i = pars.length; i-->0;) {
				for(var j in keys) {					
					var prefix = encodeURIComponent(keys[j])+'=';
					if(pars[i].lastIndexOf(prefix, 0) !== -1) {
						pars.splice(i, 1);
						break;
					}
				}				
			}
			
			if(pars.length) {
				url = urlparts[0]+'?'+pars.join('&');
			} else {
				url = urlparts[0];
			}
		}
		
		return url;
	},
	
	// --- converts a querystring to object of key => value pairs
	qsToObject: function(data) {
		if(!data) {
			return {}
		};
		
		var query_string = {};
		data.replace(
			new RegExp("([^?=&]+)(=([^&]*))?", "g"),
				function($0, $1, $2, $3) {
				query_string[$1] = $3;
			}
		);
		return query_string;	
	},
	
	
	
	// --- converts a string to a slug (lowercase, accent replacement, no spaces, etc)
	slug: function(str) {
	 str = str.replace(/^\s+|\s+$/g, ''); // trim
	 str = str.toLowerCase();
	 
	 // remove accents, swap ñ for n, etc
	 var from = "&agrave;áäâ&egrave;&eacute;ëêìíïîòóöôùúüûñç·/_,:;";
	 var to = "aaaaeeeeiiiioooouuuunc------";
	 for(var i=0, l=from.length; i<l; i++) {
	 str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
	 }
	
	 str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
	 .replace(/\s+/g, '-') // collapse whitespace and replace by -
	 .replace(/-+/g, '-'); // collapse dashes
	
	 return str;
	},
	
	
	// = group: validation
	
	// --- wrapper for all validation
	vdt: function(str, type) {
		if(!str) return true
		
		if(!type) {
			_.Bro.logToConsole('vdt() is missing parameter "type"');
			return true;
		}
		
		if(typeof _.Str.Validate[type] != 'function') {
			_.Bro.logToConsole('Validation does not exist for "'+type+'"');
			return true;
		}
		
		
		return _.Str.Validate[type](str);
	},
	
	
	
	
	
	// --- individual validation functions
	Validate: {
		
		// ---- email address
		email: function(str) {
			var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,6})$/; /* 
			*/
			return (reg.test(str));
		},
		
		
		
		
		
		// ---- number, greater than 0
		int: function(str) {
			if(isNaN(str)) {
				return false;
			}
			return (str > 0);	
		},
		
		
		
		
		
		// ---- any number (negative, positive, 0)
		number: function(str) {
			return (!isNaN(str));
		},
		
		
		
		
		
		// ---- password - allows certain characters
		password: function(str) {
			var reg = /[^-!@#*$%^&?.+_A-Za-z0-9]+/;
			return (reg.test(str) ? false : true);	
		},
		
		
		
		
		
		// ---- partial url
		path: function(str) {
			var reg = /^(\/[\S]+)+$/; /* 
			*/
			return reg.test(str);
		},
		
		
		
		
		
		// ---- path or url
		pathURL: function(str) {
			if(v_.Str.Validate.url(str)){
				return true;	
			} else if(_.Str.Validate.path(str)) {
				return true;
			}	
			return false;
		},
		
		
		
		
		
		// ---- phone or fax number
		phone: function(str){
			var phoneNumberDelimiters = "()-. extnsio#"; // non-digit characters which are allowed in phone numbers (accepts any chars in the word extension)
			var validWorldPhoneChars = phoneNumberDelimiters + "+"; // characters which are allowed in international phone numbers -  a leading + is OK
			var minDigitsInIPhoneNumber = 10; // Minimum no of digits in an international phone no.
		
			var bracket=3;
			str = _.Str.stripSpaces(str);
			if(str.indexOf("+") > 1) return false; // only allow + as the first character
			if(str.indexOf("-") != -1)bracket=bracket+1; // offset the bracket by 1 if - exists
			var openingBracketPos = str.indexOf("("); // get position of opening bracket
			var closingBracketPos = str.indexOf(")"); // get position of closing bracket
			if(openingBracketPos != -1 && openingBracketPos > bracket)return false; // don't allow ( after the allocated bracet range
			if(openingBracketPos != -1 && closingBracketPos != openingBracketPos + 4) return false; // closing bracket must be 4 characters after opening bracket
			if(openingBracketPos == -1 && closingBracketPos !=- 1) return false; // can't have a closing bracket without an opening backet
			s=_.Str.stripCharsInBag(str,validWorldPhoneChars); // trim allowed chars
			return (_.isNumber(s, str) && s.length >= minDigitsInIPhoneNumber); // check only numbers exist, and ensure minimum numbers is met
		},
		
		
		
		
		
		// ---- cannonical url (incl https, ftp, username:password, port)
		url: function(str) {
			var reg = /^((https?|ftp)\:\/\/)?([a-zA-Z0-9+!*(),;?&=$_.-]+(\:[a-zA-Z0-9+!*(),;?&=$_.-]+)?@)?([a-zA-Z0-9-.]*)\.([a-zA-Z]{2,3})(\:[0-9]{2,5})?(\/([a-zA-Z0-9+$_-]\.?)+)*\/?(\?[a-zA-Z+&$_.-][a-zA-Z0-9;:@&%=+\/$_.,-]*)?(#[a-zA-Z_.-][a-zA-Z0-9+$_.-]*)?$/;  /* 
			*/
			return reg.test(str);
		}
	}
	
}









// - other nice-to-have's


// -- calculates size (length of) object
Object.size = function(obj) {
    var size = 0, key;
    for(key in obj) {
       if(obj.hasOwnProperty(key)) size++;
    }
    return size;
};





// -- enables chaining class methods
Function.prototype.chain = function() {
	var that = this;
	return function() {
		// New function runs the old function
		var retVal = that.apply(this, arguments);
		
		if(typeof retVal == "undefined") {
			// Returns "this" if old function returned nothing
			return this;
		} else {
			// returns old value
			return retVal;
		}
	}
};

var chain = function(obj) {
	for(var fn in obj) {
		if(typeof obj[fn] == "function") {
			obj[fn] = obj[fn].chain();
		}
	}
	return obj;
}

