/**
 * @author Dale Larsen
 * @copyright 2010 Fabuso, LLC
 * For a tool to generate js regexp faster use:
 * http://www.jslab.dk/tools.regex.php
 */
var isLoggedIn = false;
function toggleLoginButtons(){
	if (isLoggedIn) {
		$('#logoutBtn, #myAcntBtn').show();
		$('#loginBtn, #createAcntBtn').hide();
	} else {//test comment
		$('#logoutBtn, #myAcntBtn').hide();
		$('#loginBtn, #createAcntBtn').show();
	}
	
}

var fab = {};
jQuery.browser.chrome = jQuery.browser.webkit && !!window.chrome;
jQuery.browser.safari = jQuery.browser.webkit && !window.chrome;
/**
 * @author dlarsen
 * this object converts strings to a number if possible
 */
function doLogout(ru){
	$.post('/logout.do', function(){
		if (!ru) {
			location.reload();
		} else {
			location.href = ru;
		}
	});
}

String.prototype.toNumber = function(){
	var newNum;
	if (!this) {
		return 0;
	} else {
		if (this == 'auto') {
			newNum = 0;
		} else {
			newNum = parseInt(this);
		}
		if (!isNaN(newNum)) {
			return newNum;
		} else {
			return this;
		}
	}
};

//return if a var is an object
function isObj(o){
	return typeof o == 'object';
}

//return the length of variables in an object
function object_length(o){
	var n = 0, x;
	if (typeof o == 'object') {
		if (o) {
			for (x in o) {
				if (o.hasOwnProperty(x)) n++;
			}
		}
	}
	return n;
}

Object.size = function(o){
	return object_length(o);
};
function objectPropCount(o){
	var n = 0;
	for (x in o) {
		n++;
	}
	return n;
}

/**
 * Get the the type of a var
 * @param {Object} testVar
 */
function getType(testVar){
	if (testVar) {
		if (testVar.constructor.toString().indexOf("Array") != -1) {//testVar instanceof Array
			return 'array';
		} else if (testVar instanceof jQuery) {
			return 'jqueryObj';
		} else {
			return typeof testVar;
		}
	} else {
		return false;
	}
}

var osInfo = {
	os: null,
	isXP: false,
	isWin7: false,
	detect: function(){
		var usrAgent = navigator.userAgent.split('; ');
		this.os = usrAgent[2];
		this.isWin7 = (usrAgent[2] == 'Windows NT 6.1');
		this.isXP = (usrAgent[2] == 'Windows NT 5.1');
	}
};
osInfo.detect();

//this catches cases where the number is already a number.
Number.prototype.toNumber = function(){
	return this;
};
fab.uiReload = function(){
	window.location.reload(true);
};
/**
 * for less code to create new nodes
 */
function $div(){
	return $('<div></div>');
}

function $span(){
	return $('<span></span>');
}

function $input(){
	return $('<input></input>');
}

/** 
 * @author dlarsen
 * simplifies the console.log call for firebug and IE developer tools
 */
function log(msg){
	try {
		console.log(msg);
	} catch (err) {
		msg = msg.truncateString(122);
		//alert('Please open the console. Output was:\n' + msg);
	}
}

function varSmarts(v){
	var tVar = typeof(v);
	this.value = v;
	this.type = tVar;
}

function smartLog(logs){
	function doSmartLog(oneLog){
		var varSm = new varSmarts(oneLog);
		log('Value: ' + varSm.value + '. Type: ' + varSm.type);
	}
	if (typeof(logs) == 'string' || typeof(logs) == 'boolean' || typeof(logs) == 'number') {
		doSmartLog(logs);
	} else if (logs instanceof Array) {
		for (var i = 0; i < logs.length; i++) {
			doSmartLog(logs[i]);
		}
	}
}

/**
 * @author dlarsen
 * @param {Object} oA
 * oA means "other array", but you can pass in a string, boolean or number.
 * However, if you pass in boolean false, it will return false whether it was matched or not, might add detection for this later and return the string value "false"
 * You can use this function to compare arrays or to check if a value exists in an array. The matched value is returned
 */
Array.prototype.compare = function(oA, returnUnmatched, returnIOArr){
	var arr = this;
	if (typeof(oA) == 'string' || typeof(oA) == 'boolean' || typeof(oA) == 'number') {
	
		var same = false;
		for (var i = 0; i < arr.length; i++) {
			if (arr[i] == oA) {
				same = arr[i];
			}
		}
		return same;
		
	} else if (oA instanceof Array) {
	
		var same = [], unmatched = [], ioarr = [];
		for (var n = 0; n < oA.length; n++) {
		
			for (var i = 0; i < arr.length; i++) {
			
				if (oA[n] == arr[i]) {
					same.push(oA[n]);
					ioarr.push(n);
				}
				
			}
			
		}
		
		if (returnUnmatched) {
		
			for (var i = 0; i < oA.length; i++) {
				var arrlen = arr.length;
				for (var j = 0; j < arrlen; j++) {
					if (oA[i] == arr[j]) {
						arr = arr.slice(0, j).concat(arr.slice(j + 1, arrlen));
					}
				}
			}
			return arr;
		}
		
		if (returnIOArr) {
			return ioarr;
		} else if (same) {
			return same;
		} else {
			return false;
		}
		
	}
};
/** 
 * @author dlarsen
 * Match a character at the beginning or end of a string. allows for special characters since I couldn't figure out how to do a match on a period at the beginning using \b
 */
String.prototype.atFrontOrEnd = function(c){
	var s = this;
	var sL = s.length - 1;
	if (s.indexOf(c) == 0) {
		return true;
	} else if (s.lastIndexOf(c) == sL) {
		return true;
	} else {
		return false;
	}
};
/**
 * Are we on a mobile device?
 */
function detectMobile(){
	var navUserAgent = navigator.userAgent;
	if (navUserAgent.match(/Android/i) || navUserAgent.match(/webOS/i) || navUserAgent.match(/iPhone/i) || navUserAgent.match(/iPod/i)) {
		return true;
	} else {
		return false;
	}
}

/** 
 * @author dlarsen
 * I'm writing this little script because I don't like how the isNaN function works. I usually like to know if it is a number not if its not.
 * I also added it to the string object
 */
function isNum(num){
	if (isNaN(num)) {
		return false;
	} else {
		return true;
	}
}

String.prototype.isNumber = function(){
	return isNum(this);
}

/** 
 * @author dlarsen
 * this extracts one or all ocurrances of a string given by a starting search and ending search, pass in a string not a number, the original string without the extraction is returned
 * TODO: looks at the script for function removeScriptTags. rewrite this to work dynamically like that.
 */
String.prototype.extract = function(first, last, all, alerter){
	var str = this;
	var originalStr = str;
	var num = 0;
	if (str) {
		if (str.indexOf(first)) {
			num = 1;
			if (all) {
				num = str.split(first).length;//could make this faster by making sure it is the correct number, not one too many
			}
			for (var i = 0; i < num; i++) {
				var a = str.indexOf(first);
				if (a && a > -1) {
					var tStr = str.slice(a);
					var b = tStr.indexOf(last);
					var bLength = last.length;
					tStr = tStr.slice(0, b + bLength);
					var regexer = new RegExp(tStr, 'gi');
					str = str.replace(regexer, '') + '';
				}
			}
		}
	}
	if (str && str != 'undefined') {
		return str;
	} else {
		return originalStr;
	}
	
};

/**
 * Example of how to use removeScriptTags
 var formCoesHtml = '<div>Dale <script type="text/javascript">alert(\'hello\');</script>likes to read books<script language="javascript">alert(\'hi\');</script></div>';
 formCoesHtml = formCoesHtml.removeScriptTags();
 log(formCoesHtml);
 */
function arraysToObj(arr1, arr2){
	var newObj = {};
	for (var i = 0; i < arr1.length; i++) {
		newObj[arr1[i]] = arr2[i];
	};
	return newObj;
}

/**
 * @author dlarsen
 * simply removes part of a string that is passed in as the arg
 * @param {String} xStr - string to remove from String
 * @param {String} regOpts - can be g,i, or gi
 */
String.prototype.removeStr = function(xStr, regOpts){
	var str = this;
	var regexer = new RegExp(xStr, regOpts);
	str = str.replace(regexer, '');
	return str;
};

/**
 * @author dlarsen
 * This function will capitalize a String, needs a lot of work but works for what I need
 * for future ideas see http://ejohn.org/blog/title-capitalization-in-javascript/
 */
String.prototype.capitalize = function(all){
	var str = this;
	if (all) {
		var a = str.split(' ');
		if (a.length > 0) {
			jQuery.each(a, function(i, arrVal){
				if (arrVal.match(/a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v[.]?|via|vs[.]?/gi)) {
					a[i] = arrVal.substr(0, 1).toUpperCase() + arrVal.substr(1);
				}
			});
			str = a.join(' ');
		}
	} else {
		//get the first letter
		str = str.substr(0, 1).toUpperCase() + str.substr(1);
	}
	return str;
}
/**
 * @author dlarsen
 * TODO: make this function aware of words/spaces so that the elipses are placed after a word  and not in the middle of one.
 * It will determine if the num is in the middle of a word and if so it will go back to the next space available and start the slice from there.
 */
String.prototype.truncateString = function(num){
	var str = this;
	if (!num) {
		num = 20;
	}
	if (str.length > num) {
		str = str.slice(0, num);
		str += '...';
	} else {
		str = this + '';
	}
	return str;
	
};

/**
 * This converts rgb to hex color
 */
String.prototype.makeHex = function(){
	return new RGBColor(this).toHex();
};
/**
 * This converts hex to rgb color
 */
String.prototype.makeRGB = function(){
	return new RGBColor(this).toRGB();
};

/**
 * @author dlarsen
 * This is an object/library I've written to better handle url parameters with ease.
 */
var paramObj = {
	getAllParams: function(str){
	
		if (str) {
			var firstParam = str.split('?')[1];
			var params = [];
			if (firstParam) params = firstParam.split('&');
			
			return params;
		} else {
			return false;
		}
		
	},
	toObj: function(str){
		if (str) {
			var paramSection = str.split('?')[1], paramObj = {};
			if (paramSection) {
			
				var params = paramSection.split('&');
				
				for (var i = 0; i < params.length; i++) {
					var thisParam_Arr = params[i].split('=');
					paramObj[thisParam_Arr[0]] = thisParam_Arr[1];
				}
				
			}
			
			return paramObj;
		} else {
			return false;
		}
	},
	getParam: function(str, paramName){
	
		var qparts = str.split('?');
		if (qparts.length == 0) {
			return false;
		}
		var query = qparts[1];
		if (query) {
			var vars = query.split('&');
			var retValue = '';
			for (var i = 0; i < vars.length; i++) {
				var parts = vars[i].split('=');
				if (parts[0] == paramName) {
					retValue = parts[1];
					break;
				}
			}
			retValue = unescape(retValue);
			retValue.replace(/\+/g, ' ');
			return retValue;
		} else {
			return false;
		}
	},
	setParam: function(str, paramName, pVal){
		var allPs = this.getAllParams(str);
		var pAlreadySet = false;
		for (var i = 0; i < allPs.length; i++) {
			if (allPs[i].split('=')[0] == paramName) {
				pAlreadySet = true;
				break;
			}
		}
		if (!pAlreadySet) {
			str += (!str.match(/\?/) ? '?' : '&') + paramName + '=' + pVal;
		} else {
			str = str.split('?')[0];
			for (var i = 0; i < allPs.length; i++) {
				var useSep = (i == 0) ? '?' : '&';
				if (allPs[i].split('=')[0] == paramName) {
					str += useSep + paramName + '=' + pVal;
				} else {
					str += useSep + allPs[i];
				}
			}
		}
		return str;
	},
	remove: function(str, paramName){
		if (str) {
		
			if (paramName) {
			
				/**
				 * TODO: write a remove param script here when I catually need it
				 */
			} else {
				return str;
			}
			
		} else {
			return false;
		}
	}
};

String.prototype.setParam = function(paramName, pVal){
	return paramObj.setParam(this, paramName, pVal);
};
String.prototype.getParam = function(paramName){
	return paramObj.getParam(this, paramName);
};
String.prototype.removeParam = function(paramName){//function has not been completed
	var str = this;
	if (str) {
		return paramObj.remove(str, paramName);
	} else {
		return false;
	}
	
};

function warning(cont, msg, timeout){
	cont = $(cont);
	if (cont.length > 0 && msg) {
		if (!cont.is(':visible')) cont.fadeIn(350);
		if (!cont.hasClass('.warningDiv')) cont.addClass('warningDiv');
		cont.html(msg);
		if (timeout) {
			if (timeout === true) timeout = 2000;
			setTimeout(function(){
				cont.fadeOut(350);
			}, timeout);
		}
	}
}

/**
 * @author dlarsen
 * this one makes an element unselectable and therefore never receives focus. Handy for keeping a selection on another node.
 */
jQuery.fn.unSelectable = function(applyChildren, reverse, filterChildren){

	return this.each(function(){
		var obj = $(this);
		if (!reverse) {
			obj.attr('unselectable', 'on').css({
				'-moz-user-select': 'none',
				'-webkit-user-select': 'none'
			});
		} else {
			obj.attr('unselectable', 'off').css({
				'-moz-user-select': 'all',
				'-webkit-user-select': 'all'
			});
		}
		if (applyChildren) {
			var objChildren = obj.find('*:not(' + filterChildren + ')')//.not(filterChildren);
			if (objChildren.length > 0) {
				objChildren.each(function(){
					$(this).unSelectable(false, reverse, filterChildren);
				});
			}
		}
	});
};

/**
 * @author dlarsen
 * This creates a new event for right click. if you leave the newFunction variable empty it will just disable the browsers right click contextmenu
 */
jQuery.fn.onContextMenu = function(newFunction){

	var obj = $(this);
	if (obj[0]) {
	
		obj[0].oncontextmenu = function(){
			return false;
		};
		if (newFunction) {
			obj.mouseup(function(e){
				if (e.which == 3) {
					newFunction.call(this);
					return false;
				}
			});
		}
	}
	return obj;
};

/**
 * @author dlarsen
 * This is a function I wrote to make keypress detection more intuitive and human readable
 * currently only handles shift, ctrl, and alt with chars a-z, delete, enter, esc, left, up, right, and down. passing the jQuery key event to this will return the key as its keyboard name.
 * e.g. just typing "a" will return "a". shift + "a" will return "shift_a", etc.
 * var keyAPI is defined on /js/global_user_interface_popup.js
 *
 * @param {Object} keyEvent
 * @param {Boolean} preventDef
 */
function getKeyPress(keyEvent, preventDef){
	var isShift = keyEvent.shiftKey, isCtrl = keyEvent.ctrlKey, isAlt = keyEvent.altKey, key = keyEvent.which;
	//log(keyEvent);
	if (preventDef) keyEvent.preventDefault();
	if (!key) key = keyEvent.keyCode;
	//log(key);
	for (var i = 0; i < keyAPI.length; i++) {
		if (key == keyAPI[i].keyCode) {
			if (isShift && isCtrl) {
				return 'ctrl_shift_' + keyAPI[i].key;
			} else if (isCtrl && isAlt) {
				return 'ctrl_alt_' + keyAPI[i].key;
			} else if (isShift) {
				return 'shift_' + keyAPI[i].key;
			} else if (isCtrl) {
				return 'ctrl_' + keyAPI[i].key;
			} else if (isAlt) {
				return 'alt_' + keyAPI[i].key;
			} else if (keyAPI[i].key) {
				return keyAPI[i].key;
			} else {
				return key;
			}
		} else if (i == keyAPI.length - 1) {
			if (isShift && isCtrl) {
				return 'ctrl_shift_' + key;
			} else if (isCtrl && isAlt) {
				return 'ctrl_alt_' + key;
			} else if (isShift) {
				return 'shift_' + key;
			} else if (isCtrl) {
				return 'ctrl_' + key;
			} else if (isAlt) {
				return 'alt_' + key;
			} else if (key) {
				return key;
			}
		}
	}
	
}

/**
 * @author dlarsen
 * just an easy wait to toggle the waiting cursor on/off. TODO: find all places I do this and replace it with this function
 * @param {Boolean} on
 * @param {Selector} extra
 * @param {Boolean} cover
 */
var waiting = {
	waitTimeout: '',
	waitingTime: 5000
};

/**
 *
 * @param {Boolean} on
 * @param {Selector} extra
 * @param {Boolean} cover
 * @param {Boolean} coverInvisible
 */
function waitCursor(on, extra, cover, coverInvisible){
	var loadingCover = $('#loadingOverlay');
	if (on) {
		$('body').css('cursor', 'wait !important');
		if (extra) {
			$(extra).css('cursor', 'wait !important');
		}
		if (cover) {
			if (coverInvisible) loadingCover.css('opacity', 0);
			loadingCover.show();
		}
		waiting.waitTimeout = setTimeout(function(){
			waitCursor(false, extra);
		}, waiting.waitingTime);
	} else {
		clearTimeout(waiting.waitTimeout);
		$('body').css('cursor', '');
		if (extra) {
			$(extra).css('cursor', '');
		}
		loadingCover.hide().css('opacity', '');
	}
}

var doWait = {
	isOn: false,
	$waitCover: false,
	waitTimeout: false,
	waitMaxTime: 20000,
	start: function(){
		var lib = this;
		if (lib.$waitCover.length || !lib.$waitCover.length) {
			lib.$waitCover = $('<div></div>').attr('id', 'waitCover').addClass('waitCover').prependTo('body');
		}
		if (!lib.isOn) {
			lib.$waitCover.show();
			lib.isOn = true;
			
			lib.waitTimeout = setTimeout(function(){
				lib.stop();
			}, lib.waitMaxTime);
		}
		
	},
	stop: function(){
		var lib = this;
		clearTimeout(lib.waitTimeout);
		if (lib.isOn) {
			lib.$waitCover.hide();
			lib.isOn = false;
		}
	}
}

/** 
 * @author dlarsen
 * @param {Object} a
 * @param {Object} b
 * simple function to sort an array from the lowest to highest
 * usage: anyArray.sort(sortAtoB);
 */
function sortAtoB(a, b){
	return a - b;
}

/**
 * @author dlarsen
 * @param {Object} str
 */
function alphabetizer(str){
	if (str) {
		var words = str.split(' ');
		for (var i = 0; i < words.length; i++) {
			words[i] = words[i].split('').sort().join('');
		}
		returnStr = words.join(' ');
		return returnStr;
	}
}

/**
 * @author dlarsen
 * The following get and set top and left positions
 */
function getCssPos(obj, orient, newPos){
	if (newPos || newPos == 0) {//setter
		if (!isNaN(newPos)) newPos += 'px';
		obj.css(orient, newPos);
		return obj;
	} else {//getter
		var getPos = obj.css(orient);
		if (getPos == 'auto' && orient == 'top') {
			getPos = obj.position().top;
		} else if (getPos == 'auto' && orient == 'left') {
			getPos = obj.position().left;
		} else if (getPos != 'auto') {
			getPos = parseInt(getPos);
		}
		return getPos;
	}
}

jQuery.fn.top = function(newPos){
	return getCssPos($(this), 'top', newPos);
};
jQuery.fn.left = function(newPos){
	return getCssPos($(this), 'left', newPos);
};
jQuery.fn.right = function(newPos){
	return getCssPos($(this), 'right', newPos);
};
jQuery.fn.bottom = function(newPos){
	return getCssPos($(this), 'bottom', newPos);
};

/**
 * Change the opacity and do optional animation and revert.
 * @param {Integer} newOp, new opacity
 * @param {Integer} speed, if you specify a speed it will animate
 * @param {Integer} revertDelay, if you specify this it will revert back to the original opacity after the specified delay time
 */
jQuery.fn.opacity = function(newOp, speed, revertDelay){
	var $this = $(this);
	if (newOp) {
		if (speed) {//do animation
			if (revertDelay) {//revert to original opacity after animation and delay
				var revertOp = $this.css('opacity');
				$this.fadeTo(speed, newOp, function(){
					setTimeout(function(){
						$this.fadeTo(speed, revertOp);
					}, revertDelay);
				});
			} else {//do just animation, no revert
				$this.fadeTo(speed, newOp);
			}
		} else {
			$this.css('opacity', newOp);
		}
		return $this;//return the object for chainability
	} else if (!newOp) {
		return $this.css('opacity');//return the opacity
	}
};

/**
 * @param {Object} num
 * detect if the given number is even
 */
function detectEven(num){
	if (num > 0) {
		if (num % 2 == 0) {
			return true;
		} else {
			return false;
		}
	}
}

/**
 * number prototype way of detecting an even number.
 */
Number.prototype.isEven = function(){
	if (isNaN(this)) {
		return false;
	} else {
		return detectEven(this);
	}
};

/**
 * @param {Number} n
 * generate a random number between 1 and n
 */
function rand(n){
	return (Math.floor(Math.random() * n + 1));
}

/**
 *
 */
var dDate = {
	moObj: {
		'01': 'January',
		'02': 'February',
		'03': 'March',
		'04': 'April',
		'05': 'May',
		'06': 'June',
		'07': 'July',
		'08': 'August',
		'09': 'September',
		'10': 'October',
		'11': 'November',
		'12': 'December'
	},
	dayObj: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
	allMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
	today: new Date(),
	weekDayDate: function(){
		var d = this.today;
		return this.dayObj[d.getDay()] + ', ' + this.allMonths[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
	},
	todayObjNoTime: function(){
		var today = this.today;
		return new Date(today.getFullYear(), today.getMonth(), today.getDate());
	},
	timezone: function(){
		return parseInt(-new Date().getTimezoneOffset() / 60);
	},
	isDay: function(){
		var lib = this;
		var tHours = lib.today.getHours();
		return (tHours > 5 && tHours <= 17);
	},
	isEvening: function(){
		var lib = this;
		var tHours = lib.today.getHours();
		return (tHours <= 5 && tHours > 17);
	},
	isBusinessHours: function(){
		var lib = this;
		var tHours = lib.today.getHours(), getWeekDay = lib.today.getDay();
		/*log('tHours: ' + tHours + '. getWeekDay: ' + getWeekDay);
		 log(tHours > 5 + ' && ' + tHours <= 17 + ' && ' + getWeekDay != 0 + ' && ' + getWeekDay != 6);*/
		return (tHours > 5 && tHours <= 17 && getWeekDay != 0 && getWeekDay != 6)
	},
	formatted: function(format, _date){
		if (!format) format = 'month dd, yyyy';
		var d = (_date) ? new Date(_date) : this.today;
		var mo = this.allMonths[d.getMonth()];
		var yr = d.getFullYear();
		var day = d.getDate();
		
		if (format == 'month dd, yyyy') {
			return mo + ' ' + day + '' + ', ' + yr;
		}
	},
	getMoVal: function(mo){
	
		if (mo.match(/jan/gi)) {
			return '01';
		} else if (mo.match(/feb/gi)) {
			return '02'
		} else if (mo.match(/mar/gi)) {
			return '03'
		} else if (mo.match(/apr/gi)) {
			return '04'
		} else if (mo.match(/may/gi)) {
			return '05'
		} else if (mo.match(/jun/gi)) {
			return '06'
		} else if (mo.match(/jul/gi)) {
			return '07'
		} else if (mo.match(/aug/gi)) {
			return '08'
		} else if (mo.match(/sep/gi)) {
			return '09'
		} else if (mo.match(/oct/gi)) {
			return '10'
		} else if (mo.match(/nov/gi)) {
			return '11'
		} else if (mo.match(/dec/gi)) {
			return '12'
		}
	},
	sortAsc: function(d1, d2){
		if (d1 > d2) return 1;
		if (d1 < d2) return -1;
		return 0;
	},
	sortDesc: function(d1, d2){
		if (d1 > d2) return -1;
		if (d1 < d2) return 1;
		return 0;
	}
};

var dFormat = {
	from: {
		dbTimeStamp: function(str, addDayName){
			/**
			 * the timestamps in the db and from the java are like this:
			 *  2011-09-07 11:02:02
			 */
			if (str) {
			
				var sections = str.split(' '), returnArr = [];
				//reformat the date
				var tDate = sections[0].split('-');
				var weekDayName = dDate.dayObj[new Date(tDate[0], (tDate[1] - 1), tDate[2]).getDay()];
				returnArr.push((addDayName ? weekDayName + ', ' : '') + dDate.moObj[tDate[1]] + ' ' + tDate[2] + ', ' + tDate[0]);
				
				//Make the time more human readable
				var tTime = sections[1].split(':');
				tTime[0] = parseInt(tTime[0], 10);//need to set the radix to 10, otherwise 08 and 09 get set to 0
				//tTime[0] = ~~tTime[0];//other more hackish way to solve the above radix problem.
				//tTime[0] = Number(tTime[0]);//yet another work-a-round
				var isPm = false;
				if (tTime[0] > 12) {//if the hour is greater than 12, subtract 12 and set it to pm
					isPm = true;
					tTime[0] = tTime[0] - 12;
				} else if (tTime[0] == 0) {//the midnight hour is 00 so change that to 12
					tTime[0] = 12;
					isPm = false;
				}
				
				returnArr.push(tTime[0] + ':' + tTime[1] + ' ' + (isPm ? 'pm' : 'am'));
				
				return returnArr;
			}
			
		}
	}
}

/**
 * @author Dale Larsen
 * @param {Object} returnLast
 * returnLast set to true will retutn the last indexOf any number, while passing in nothing or false will return the first indexOf.
 * usage: 'th9is ha3s a number'.indexOfNum(true) would return 8 because the lastIndexOf any number would find the 3 and return its position in the string which is 8.
 */
String.prototype.indexOfNum = function(returnLast){
	if (this) {
		var str = this;
		var t = [];
		var index = 0;
		for (var i = 0; i <= 9; i++) {
			if (returnLast && str.lastIndexOf(i) > -1) {
				t[index] = str.lastIndexOf(i);
				index++;
			} else if (!returnLast && str.indexOf(i) > -1) {
				t[index] = str.indexOf(i);
				index++;
			}
			
		}
		if (t) {
			t = t.sort(sortAtoB);
			if (returnLast) {
				return t.pop();
			} else {
				return t.shift();
			}
		} else {
			return false;
		}
		
	}
};

function stripNumbers(str){
	if (str) {
		str = str.split('');
		var numbers = [];
		for (var i = 0; i < str.length; i++) {
			if (parseInt(str[i])) numbers.push(parseInt(str[i]));
		}
		return numbers;
	}
}

/**
 * @author Dale Larsen
 * @param {Object} url
 * This will get the IP address from an amazon server name
 */
function parseSiteServer(url){
	//ec2-184-73-214-255.compute-1.amazonaws.com
	if (url) {
		url = url.split('.');
		url = url[0].split('-');
		url = url[1] + '.' + url[2] + '.' + url[3] + '.' + url[4];
		return url;
	}
}

Array.prototype.findDuplicates = function(){
	var arr = this;
	if (arr) {
	
		var sorted_arr = arr.sort();
		var duplicates = [];
		for (var i = 0; i < arr.length - 1; i++) {
			if (sorted_arr[i + 1] == sorted_arr[i]) duplicates.push(sorted_arr[i]);
		}
		
		return duplicates;
	}
};

/**
 * @author Dale Larsen
 * This is a script to time how long any JS takes. Just call timer.start('whatever name');
 * Then when you call timer.stop('whatever name'); the time is miliseconds will be logged and returned to the callee.
 * Firebug has support for this, console.time and timeEnd, but IE doesn't support it.
 * @param {String} timeName
 */
var timer = {
	times: [],
	start: function(timeName){
		this.times[timeName] = new Date();
	},
	stop: function(timeName){
		if (this.times[timeName]) {
			var timeSpent = (new Date()) - this.times[timeName];
			delete this.times[timeName];
			console.log(timeName + ': ' + timeSpent + 'ms');
			return timeSpent;
		}
	}
};

var dMath = {
	round: function(num, dec){
		if (num >= 0) {
			if (!dec && dec != 0) dec = 2;
			return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
		} else {
			return 0;
		}
	}
};


var ajaxLinks = {
	load: function(targ, url, callback){
		var $targ = $(targ);
		if ($targ.is('iframe')) {
			$targ.attr('src', url);
			if (callback) callback.call(this);
		} else {
			$.get(url, function(data){
				$targ.html(data);
				if (callback) callback.call(this);
			});
		}
	}
};

jQuery.fn.ajaxLink = function(cont, callback){
	return this.each(function(){
		var $this = $(this);
		var url = this.href;
		$this.click(function(e){
			e.preventDefault();
			ajaxLinks.load(cont, url, callback);
		});
	});
};

var debug = {
	i: 0,
	currName: '',
	reset: function(){
		this.i = 0;
		this.currName = '';
	},
	logi: function(name, reset){
		if (name) this.currName = name;
		if (reset) this.reset();
		log((this.currName ? this.currName + ': ' : '') + this.i);
		this.i++;
	}
}

function makePopup(opts){

	var defaults = {
		doCreateNow: false,
		createOpts: false
	};
	
	var opts = jQuery.extend(defaults, opts);
	
	var newPopup = {
		created: false,
		$box: null,
		$content: null,
		create: function(tOpts){
			var lib = this;
			if (!lib.created) {
			
				var defs = {
					popupName: 'abtPopup',
					popupClass: 'abtPopupClass',
					shadows: false,
					zStack: 15000,
					popupBorder: '1px solid #999',
					fixedPos: false
				};
				
				var tOpts = jQuery.extend(defs, tOpts);
				
				if ($.browser.msie) tOpts.fixedPos = false;
				
				lib.$box = jQuery('<div></div>').attr('id', tOpts.popupName).addClass(tOpts.popupClass).css({
					'position': (tOpts.fixedPos ? 'fixed' : 'absolute'),
					'zIndex': tOpts.zStack,
					'backgroundColor': '#fff',
					'padding': '20px',
					'border': tOpts.popupBorder
				}).attr('fixedPos', tOpts.fixedPos).hide().appendTo('body');
				
				lib.$content = jQuery('<div id="' + tOpts.popupName + '_content"></div>').appendTo(lib.$box);
				
				var $dragHandle = jQuery('<div></div>').css({
					'position': 'absolute',
					'float': 'left',
					'width': '100%',
					'height': '15px',
					'top': '0px',
					'left': '0px',
					'cursor': 'move'
				}).appendTo(lib.$box);
				
				lib.$box.draggable({
					handle: $dragHandle
				});
				
				
				var $closeX = jQuery('<div></div>').css({
					'position': 'absolute',
					'top': '5px',
					'right': '8px',
					'font-weight': 'bold',
					'cursor': 'pointer',
					'color': '#666',
					'font-size': '21px'
				}).html('X').click(function(){
					lib.close(false, 600);
				}).appendTo(lib.$box);
				
				
				if (tOpts.shadows) {
					var nwSrc = '/images/popup_shadow_nw.png';
					var shadowNW = $('<div></div>').addClass('popupShadow').css({
						'position': 'absolute',
						'top': '-20px',
						'left': '-20px',
						'backgroundImage': 'url(\'' + nwSrc + '\')'
					}).width(20).height(20).appendTo(lib.$box);
					
					var nSrc = '/images/popup_shadow_n.png';
					var shadowN = $('<div></div>').addClass('popupShadow').css({
						'position': 'absolute',
						'top': '-20px',
						'left': '0px',
						'backgroundImage': 'url(\'' + nSrc + '\')'
					}).width('100%').height(20).appendTo(lib.$box);
					
					var neSrc = '/images/popup_shadow_ne.png';
					var shadowNE = $('<div></div>').addClass('popupShadow').css({
						'position': 'absolute',
						'top': '-20px',
						'right': '-20px',
						'backgroundImage': 'url(\'' + neSrc + '\')'
					}).width(20).height(20).appendTo(lib.$box);
					
					var eSrc = '/images/popup_shadow_e.png';
					var shadowE = $('<div></div>').attr('id', 'popupShadowE').addClass('popupShadow').css({
						'position': 'absolute',
						'top': '0px',
						'right': '-20px',
						'backgroundImage': 'url(\'' + eSrc + '\')'
					}).width(20).height('100%').appendTo(lib.$box);
					
					var seSrc = '/images/popup_shadow_se.png';
					var shadowSE = $('<div></div>').addClass('popupShadow').css({
						'position': 'absolute',
						'bottom': '-20px',
						'right': '-20px',
						'backgroundImage': 'url(\'' + seSrc + '\')'
					}).width(20).height(20).appendTo(lib.$box);
					
					var sSrc = '/images/popup_shadow_s.png';
					var shadowS = $('<div></div>').addClass('popupShadow').css({
						'position': 'absolute',
						'bottom': '-20px',
						'left': '0px',
						'backgroundImage': 'url(\'' + sSrc + '\')'
					}).width('100%').height(20).appendTo(lib.$box);
					
					var swSrc = '/images/popup_shadow_sw.png';
					var shadowSW = $('<div></div>').addClass('popupShadow').css({
						'position': 'absolute',
						'bottom': '-20px',
						'left': '-20px',
						'backgroundImage': 'url(\'' + swSrc + '\')'
					}).width(20).height(20).appendTo(lib.$box);
					
					var wSrc = '/images/popup_shadow_w.png';
					var shadowW = $('<div></div>').attr('id', 'popupShadowW').addClass('popupShadow').css({
						'position': 'absolute',
						'top': '0px',
						'left': '-20px',
						'backgroundImage': 'url(\'' + wSrc + '\')'
					}).width(20).height('100%').appendTo(lib.$box);
				}
				
				
				lib.created = true;
			}
		},
		open: function(content, w, h, pos, e, anim, speed, callback){
			var lib = this;
			lib.$content.html(content);
			lib.$box.width(w).height(h);
			if (!pos) pos = 'overClick';
			
			if (pos == 'overClick' && e) {
				lib.$box.css({
					'top': (e.pageY + 15) + 'px',
					'left': (e.pageX + 15) + 'px'
				});
			} else if (typeof pos == 'function') {
				pos.call(this, lib.$box);
			} else if (typeof pos == 'object') {
				lib.$box.css({
					'top': pos.top + 'px',
					'left': pos.left + 'px'
				});
			} else if (pos == 'center') {
				lib.$box.css({
					'top': '50%',
					'left': '50%',
					'margin-top': '-' + (lib.$box.height() / 2) + 'px',
					'margin-left': '-' + (lib.$box.width() / 2) + 'px'
				});
			} else if (pos == 'scrolltop') {
				var newTop = ($(window).scrollTop() + (($(window).height() - lib.$box.height()) / 2)) + 'px';
				lib.$box.css({
					'position': 'absolute',
					'top': ($(window).height() > lib.$box.height() ? newTop : ($(window).scrollTop() + 25) + 'px'),
					'left': '50%',
					'margin-left': '-' + (lib.$box.width() / 2) + 'px'
				});
			}
			if (!speed && speed != 0) speed = 500;
			if ($.browser.msie) {
				anim = false;
				speed = false;
			}
			if (anim) {
				lib.$box.show(anim, false, speed);
			} else {
				lib.$box.show(speed);
			}
			if (callback) callback.call(this);
		},
		resize: function(w, h, speed, callback){
			var lib = this;
			if (!h) h = lib.$box.height();
			if (!w) w = lib.$box.width();
			if (w != lib.$box.width() || h != lib.$box.height()) {
				lib.$box.animate({
					'top': '50%',
					'left': '50%',
					'margin-top': '-' + (h / 2) + 'px',
					'margin-left': '-' + (w / 2) + 'px',
					'width': w + 'px',
					'height': h + 'px'
				}, (!speed && speed != 0 ? 500 : speed), callback);
			}
		},
		close: function(anim, speed){
			var lib = this;
			lib.$content.empty();
			if ($.browser.msie) {
				anim = false;
				speed = false;
			}
			if (anim) {
				lib.$box.hide(anim, false, speed, function(){
					if (lib.$box.attr('fixedPos') == 'true' || lib.$box.attr('fixedPos') == 'fixed' || lib.$box.attr('fixedPos') == true) lib.$box.css('position', 'fixed');
				});
			} else {
				lib.$box.hide(speed, function(){
					if (lib.$box.attr('fixedPos') == 'true' || lib.$box.attr('fixedPos') == 'fixed' || lib.$box.attr('fixedPos') == true) lib.$box.css('position', 'fixed');
				});
			}
			
		}
	}
	if (opts.doCreateNow && opts.createOpts) {
		newPopup.create(opts.createOpts);
	} else if (opts.doCreateNow) {
		newPopup.create();
	}
	
	return newPopup;
};
var abtPopup = makePopup();
var fullPopup = makePopup();

jQuery.fn.abtHomeMenu2 = function(opts){
	var defs = {
		menuId: 'abtHomeMenu2',
		menuLeftOffset: -25,
		menuTopOffset: 8,
		items: [{//array of objects of menu link item names and location to anchor to
			'name': 'Link 1',
			'href': 'http://...'
		}],
		featItem: {
			href: '/book_authors.jsp',
			src: '/images/home2/authors_menu.jpg'
		}
	};
	
	var $this = $(this);
	
	if ($this.length) {
	
		var opts = $.extend(defs, opts);
		
		//if ($.browser.msie) opts.menuTopOffset += 14;
		
		//Build the menu
		var newMenu = '<div id="' + opts.menuId + '" class="abtHomeMenu2">';
		newMenu += '<div class="homeMenuLinkList">';
		
		for (var i = 0; i < opts.items.length; i++) {
			var thisItem = opts.items[i];
			newMenu += (thisItem['href'] ? '<a href="' + thisItem['href'] + '">' : '<span>');
			newMenu += thisItem['name'];
			newMenu += (thisItem['href'] ? '</a>' : '</span>');
		};
		
		newMenu += '</div><div id="homeMenuFeature">';
		newMenu += '<a href="' + opts.featItem.href + '"><img style="border: none;" src="' + opts.featItem.src + '" /></a>';
		newMenu += '</div></div>';
		
		//alert('newMenu: ' + newMenu);
		
		$this.css({
			'position': 'relative',
			'z-index': '16000',
			'cursor': 'default'
		}).append(newMenu);
		
		var $newMenu = $('#' + opts.menuId);
		var newMenuCssTop = ($this.outerHeight() + opts.menuTopOffset);
		$newMenu.css({
			'top': newMenuCssTop + 'px',
			'left': opts.menuLeftOffset + 'px'
		});
		
		//This is a bridge so that menu is connected to the item.
		var $linktoMenuBridge = $('<div></div>').css({
			position: 'absolute',
			//top: -(opts.menuTopOffset + ($.browser.msie ? -2 : 2)) + 'px',
			top: -(opts.menuTopOffset + 2) + 'px',
			left: -(opts.menuLeftOffset + 2) + 'px',
			background: '#f4f2f3',
			zIndex: 100
		}).width($this.outerWidth()).height((opts.menuTopOffset + 4) + 'px').appendTo($newMenu);
		//hack done
		
		$this.hover(function(e){
			$newMenu.show();
			$linktoMenuBridge.css('zIndex', 100);
		}, function(e){
			$newMenu.hide();
		});
	}
}

jQuery.fn.abtHomeMenu = function(opts){
	var defs = {
		menuId: 'abtHomeMenu',
		menuTitle: 'Menu Title',
		itemHoverColor: false,
		class_prefix: 'home',
		items: [{//array of objects of menu link item names and location to anchor to
			'name': 'Link 1',
			'href': 'http://...',
			'f': false//not completed, allow to call a function onclick
		}]
	};
	
	var $this = $(this);
	
	if ($this.length) {
	
		var opts = $.extend(defs, opts);
		
		//Build the menu
		var newMenu = '<div id="' + opts.menuId + '" class="homeMenuWr">';
		newMenu += '<div class="' + opts.class_prefix + 'MenuTop"></div>';
		newMenu += '<div class="' + opts.class_prefix + 'MenuMid">';
		newMenu += '<div class="homeMenuContent">';
		newMenu += '<div class="homeMenuTitle">' + opts.menuTitle + '</div>';
		newMenu += '<hr style="margin-bottom: 10px; background-color: #aaa; color: #aaa;" />';
		
		for (var i = 0; i < opts.items.length; i++) {
			var thisItem = opts.items[i];
			newMenu += (thisItem['href'] ? '<a href="' + thisItem['href'] + '">' : '');
			newMenu += '<div class="homeMenuItem">';// id="menuItem_' + thisItem['name'] + '"
			newMenu += thisItem['name'];
			newMenu += '</div>';
			newMenu += (thisItem['href'] ? '</a>' : '');
		};
		
		newMenu += '</div>';
		newMenu += '</div>';
		newMenu += '<div class="' + opts.class_prefix + 'MenuBtm"></div>';
		newMenu += '</div>';
		
		$this.css({
			'position': 'relative',
			'z-index': '16000',
			'cursor': 'default'
		}).append(newMenu);
		
		var thisW = $this.width(), thisH = $this.height();
		
		var $newMenu = $('#' + opts.menuId);
		
		$newMenu.css({
			'top': thisH + 13 + 'px',
			'left': (thisW / 2) - ($newMenu.width() / 2) - (20) + 'px'
		});
		
		// this is a hack so we can position the menu far beneath the link and make it so we can still hover onto the menu
		$('<div></div>').attr('class', 'IElayerFix').css({
			'top': -(thisH + 13) + 'px',
			'left': '125px'
		}).width(70).height((thisH + 20) + 'px').appendTo($newMenu);
		//hack done
		if (opts.itemHoverColor) {
			$newMenu.find('.homeMenuItem').hover(function(){
				var $thisItem = $(this);
				$thisItem.css('background-color', opts.itemHoverColor);
			}, function(){
				var $thisItem = $(this);
				$thisItem.css('background-color', '');
			});
		}
		$this.hover(function(e){
			$newMenu.show();
		}, function(e){
			$newMenu.hide();
		});
	}
}

$.fn.bookLinkMenu = function(opts){
	var defs = {
		menuId: 'abtHomeMenu2',
		menuLeftOffset: 'center',
		menuTopOffset: 8,
		menuWidth: 'auto',
		items: [{//array of objects of menu link item names and location to anchor to
			'name': 'Link 1',
			'href': 'http://...'
		}]
	};
	
	var $obj = $(this);
	
	if ($obj.length) {
	
		var opts = $.extend(defs, opts);
		
		var BookLinks = {
			'Chapter 1': {
				'Link 1': ['/link1', 'see'],
				'Link 2': ['/link2', 'see'],
				'Link 3': ['/link3', 'hear'],
				'Link 4': ['/link4', 'hear'],
				'Link 5': ['/link5', 'see'],
				'Link 6': ['/link6', 'hear'],
				'Link 7': ['/link7', 'idea'],
				'Link 8': ['/link8', 'idea'],
				'Link 9': ['/link9', 'idea'],
				'Link 10': ['/link10', 'see'],
				'Link 11': ['/link11', 'see']
			},
			'Chapter 2': {
				'Link 12': ['/link12', 'see'],
				'Link 13': ['/link13', 'hear'],
				'Link 14': ['/link14', 'see'],
				'Link 15': ['/link15', 'hear'],
				'Link 16': ['/link16', 'idea'],
				'Link 17': ['/link17', 'idea'],
				'Link 18': ['/link18', 'hear'],
				'Link 19': ['/link19', 'see'],
				'Link 20': ['/link20', 'idea']
			},
			'Chapter 3': {
				'Link 21': ['/link21', 'idea'],
				'Link 22': ['/link22', 'idea'],
				'Link 23': ['/link23', 'see'],
				'Link 24': ['/link24', 'see'],
				'Link 25': ['/link25', 'hear'],
				'Link 26': ['/link26', 'see']
			},
			'Chapter 4': {
				'Link 27': ['/link27', 'see'],
				'Link 28': ['/link28', 'see'],
				'Link 29': ['/link29', 'idea'],
				'Link 30': ['/link30', 'hear'],
				'Link 31': ['/link31', 'see'],
				'Link 32': ['/link32', 'idea'],
				'Link 33': ['/link33', 'see'],
				'Link 34': ['/link34', 'see']
			},
			'Chapter 5': {
				'Link 35': ['/link35', 'hear'],
				'Link 36': ['/link36', 'hear'],
				'Link 37': ['/link37', 'see'],
				'Link 38': ['/link38', 'hear'],
				'Link 39': ['/link39', 'see'],
				'Link 40': ['/link40', 'see'],
				'Link 41': ['/link41', 'see'],
				'Link 42': ['/link42', 'see'],
				'Link 43': ['/link43', 'see'],
				'Link 44': ['/link44', 'see'],
				'Link 45': ['/link45', 'see'],
				'Link 46': ['/link46', 'see']
			},
			'Chapter 6': {
				'Link 47': ['/link47', 'see'],
				'Link 48': ['/link48', 'idea'],
				'Link 49': ['/link49', 'hear'],
				'Link 50': ['/link50', 'idea'],
				'Link 51': ['/link51', 'idea'],
				'Link 52': ['/link52', 'idea'],
				'Link 53': ['/link53', 'idea'],
				'Link 54': ['/link54', 'hear'],
				'Link 55': ['/link55', 'hear'],
				'Link 56': ['/link56', 'see'],
				'Link 57': ['/link57', 'hear'],
				'Link 58': ['/link58', 'idea'],
				'Link 59': ['/link59', 'see']
			},
			'Chapter 7': {
				'Link 60': ['/link60', 'hear'],
				'Link 61': ['/link61', 'hear'],
				'Link 62': ['/link62', 'hear'],
				'Link 63': ['/link63', 'hear'],
				'Link 64': ['/link64', 'see']
			},
			'Chapter 8': {
				'Link 65': ['/link65', 'see'],
				'Link 66': ['/link66', 'idea'],
				'Link 67': ['/link67', 'see'],
				'Link 68': ['/link68', 'see'],
				'Link 69': ['/link69', 'idea'],
				'Link 70': ['/link70', 'see'],
				'Link 71': ['/link71', 'hear'],
				'Link 72': ['/link72', 'see'],
				'Link 73': ['/link73', 'see'],
				'Link 74': ['/link74', 'see'],
				'Link 75': ['/link75', 'idea']
			},
			'Chapter 9': {
				'Link 76': ['/link76', 'see'],
				'Link 77': ['/link77', 'hear'],
				'Link 78': ['/link78', 'hear'],
				'Link 79': ['/link79', 'hear'],
				'Link 80': ['/link80', 'see']
			},
			'Epilogue': {
				'Link 81': ['/link81', 'hear']
			}
		};
		
		var menuHtml = '<div id="mainBookMenu" class="abtHomeMenu2" style="width: ' + opts.menuWidth + (isNaN(opts.menuWidth) ? '' : 'px') + '">';
		menuHtml += '<div class="bm_Center">';
		menuHtml += '<div id="bm_column1" class="bm_column"></div>';
		menuHtml += '<div id="bm_column2" class="bm_column"></div>';
		menuHtml += '</div></div>';
		
		$obj.css({
			'position': 'relative',
			'z-index': '16000',
			'cursor': 'default'
		}).append(menuHtml);
		
		$mainBookMenu = $('#mainBookMenu');
		
		//if ($.browser.msie) opts.menuTopOffset += 14;
		
		var newMenuCssTop = ($obj.outerHeight() + opts.menuTopOffset);
		if (opts.menuLeftOffset == 'center') opts.menuLeftOffset = -(($mainBookMenu.outerWidth() / 2) - ($obj.outerWidth() / 2));
		
		$mainBookMenu.css({
			'top': newMenuCssTop + 'px',
			'left': opts.menuLeftOffset + 'px'
		});
		
		//This is a bridge so that menu is connected to the item.
		var $linktoMenuBridge = $('<div></div>').css({
			position: 'absolute',
			//top: -(opts.menuTopOffset + ($.browser.msie ? -2 : 2)) + 'px',
			top: -(opts.menuTopOffset + 2) + 'px',
			left: -(opts.menuLeftOffset + 2) + 'px',
			background: '#f4f2f3',
			zIndex: 100
		}).width($obj.outerWidth()).height((opts.menuTopOffset + 4) + 'px').appendTo($mainBookMenu);
		//hack done
		
		var obj1;
		
		var $bm_column1 = $obj.find('#bm_column1'), $bm_column2 = $obj.find('#bm_column2');
		
		
		for (obj1 in BookLinks) {
		
			var BookLinks1 = BookLinks[obj1];
			$bm_column1.append('<div id="' + spaceto_(obj1) + '_Link" class="bm_menuItem bm_menuColumnLink bm_menuColumnLink1"><span class="bm_column1_Text" >' + obj1 + '</span></div>');//<img src="/images/' + obj1.toLowerCase() + '.png" class="bm_column1_Icon" />
			$bm_column2.append('<div id="' + spaceto_(obj1) + '_Column" class="bm_Column2_column"></div>');
			var $bm_Column2_column = $('#' + spaceto_(obj1) + '_Column');
			var obj2;
			for (obj2 in BookLinks1) {
			
				var BookLinks2 = BookLinks1[obj2];//TODO: add image icon for hear, see, idea
				$bm_Column2_column.append('<a class="ajaxLink" href="' + BookLinks2[0] + '"><div id="' + spaceto_(obj1 + '_' + obj2) + '_Link" class="bm_menuItem bm_menuColumnLink bm_menuColumnLink2"><img src="/images/' + BookLinks2[1] + '.png" class="bm_column1_Icon" />' + obj2 + '</div></a>');
				
			}
			
		}
		$('div.bm_menuColumnLink').each(function(){
			var $this = $(this);
			$this.click(function(){
			
				if ($this.is('.bm_menuColumnLink1')) {
					$('.activeBlMenuItem1').removeClass('activeBlMenuItem1');
					$('div.bm_Column2_column, div.bm_Column3_column').hide();
					$('#bigArrow2').hide();
					$('#bigArrow3').show();
					$this.addClass('activeBlMenuItem1');
				}
				if ($this.is('.bm_menuColumnLink2')) {
					$('.activeBlMenuItem2').removeClass('activeBlMenuItem2');
					$('div.bm_Column3_column').hide();
					$('#bigArrow3').hide();
					$this.addClass('activeBlMenuItem2');
				}
				$linkColumn = $('#' + $this.attr('id').replace(/_Link/, '_Column'));
				$linkColumn.show();
			});
		});
		
		//$('#Chapter_1_Link').click();
		var $Chapter_1_Link = $('#Chapter_1_Link');
		$obj.hover(function(){
			$mainBookMenu.show();
			$Chapter_1_Link.focus();
		}, function(){
			$mainBookMenu.hide();
		});
	}
};

jQuery.fn.offsetFrom = function(o_parent){
	return $(this).offset().top - $(this).closest(o_parent).offset().top;
}

/**
 *
 * @param {Object} elm - can be a selector string or an object
 * @param {Object} speed - number in ms
 * @param {Object} elmToScroll - can be a selector or an object of an element which will scroll
 * @param {Object} callback
 */
function scrollToHere(elm, speed, elmToScroll, callback){
	if (elm) {
		var $elm = $(elm);
		if (!speed) speed = 700;
		var elm_OffsetTop = 0;
		if (!elmToScroll) {
			elmToScroll = 'html,body';
			elm_OffsetTop = $(elm).offset().top;
		} else {
			elm_OffsetTop = $elm.offsetFrom(elmToScroll);
		}
		
		$(elmToScroll).animate({
			scrollTop: elm_OffsetTop
		}, speed, function(){
			if (callback) callback.call(this);
		});
	}
}

jQuery.fn.dScrollTo = function(speed, elmToScroll, callback, i){
	var obj = $(this);
	scrollToHere(obj.eq((i && i > 0 && obj.length ? i : 0)), speed, elmToScroll, callback);
};

/**
 * Replace spaces in a string with underscores
 * @param {Object} str
 */
function spaceto_(str){
	if (str) {
		return str.replace(/ /g, '_');
	}
}

/**
 * ABT Audio functions
 */
jQuery.fn.makeAbtPlayer = function(vidStyle, callback){
	var abtPlayerHtml = '<div class="dVideoWr dVideoWr2">';
	
	if (vidStyle == 2) {
		abtPlayerHtml += '<div id="dProgressLine"></div>';
		abtPlayerHtml += '<div class="playControlWr">';
		abtPlayerHtml += '<a href="#" class="dPlayToggle dplayTogglePlay" tabindex="1"></a>';
		abtPlayerHtml += '<a href="#" class="dPlayToggle dplayTogglePause" style="display: none;" tabindex="1"></a>';
		abtPlayerHtml += '</div>';
		abtPlayerHtml += '<div class="dSeeker ui-corner-all">';
		//abtPlayerHtml += '<div class="dSeekerTxt">PROGRESS</div>';
		abtPlayerHtml += '<div class="dSeekerProgress ui-corner-all"></div>';
		abtPlayerHtml += '</div>';
		abtPlayerHtml += '<div class="dTimer">';
		abtPlayerHtml += '<span id="dTimerCur" class="dTimerCur">00:00</span>/<span id="dTimerDur" class="dTimerDur">00:00</span>';
		abtPlayerHtml += '</div>';
		abtPlayerHtml += '<div class="dAllVolumeWr">';
		abtPlayerHtml += '<div class="dVolumeTxt">VOLUME</div>';
		abtPlayerHtml += '<div id="dVolumeSlider"></div>';
		/*abtPlayerHtml += '<div class="dVolumeBar">';
		 abtPlayerHtml += '<div class="dVolumeBarValue"></div>';*/
		abtPlayerHtml += '</div>';
		abtPlayerHtml += '</div>';
		
	} else {
		abtPlayerHtml += '<div class="vid5_l"></div>';
		abtPlayerHtml += '<div class="vid5_m">';
		abtPlayerHtml += '<div class="dControlsRow">';
		abtPlayerHtml += '<a href="#" class="dPlayToggle dplayTogglePlay" tabindex="1"></a>';
		abtPlayerHtml += '<a href="#" class="dPlayToggle dplayTogglePause" style="display: none;" tabindex="1"></a>';
		abtPlayerHtml += '<div class="IElayerFix" style="top: 0px; left: 51px; width: 215px; height: 100%;"></div>';
		abtPlayerHtml += '<div class="dSeeker ui-corner-all">';
		abtPlayerHtml += '<div class="dSeekerProgress ui-corner-all"></div>';
		abtPlayerHtml += '</div>';
		abtPlayerHtml += '<div class="dTimer">';
		abtPlayerHtml += '<span id="dTimerCur" class="dTimerCur">00:00</span>/<span id="dTimerDur" class="dTimerDur">00:00</span>';
		abtPlayerHtml += '</div>';
		abtPlayerHtml += '<a href="#" class="dVolume dVolumeOn" tabindex="1"></a>';
		abtPlayerHtml += '<a href="#" class="dVolume dVolumeOff" style="display: none;" tabindex="1"></a>';
		abtPlayerHtml += '<div class="dVolumeBar ui-corner-all">';
		abtPlayerHtml += '<div class="dVolumeBarValue ui-corner-all"></div>';
		abtPlayerHtml += '</div></div></div>';
		abtPlayerHtml += '<div class="vid5_r"></div>';
		
	}
	abtPlayerHtml += '</div>';
	
	$(this).html(abtPlayerHtml);
	if (callback) callback.call(this);
	return this;
};
function getMediaNames(mediaName, replaceFolders, noCache){//TODO: need to obfuscate the file names so that in the headers its shown all messed up. This will make it harder to find the filename and download it.
	if (mediaName) {
		var thisType = mediaName.split('.');
		var isIE = $.browser.msie;
		thisType = thisType[thisType.length - 1];
		//prevent audio caching
		var noCache_a = (noCache) ? '?noCache=' + Math.random() : '';
		var typeRegex = new RegExp('\\.' + thisType, 'i');
		var fileEditions = {
			mp3: mediaName.replace(typeRegex, '.mp3') + noCache_a,
			oga: (isIE) ? '' : mediaName.replace(typeRegex, '.ogg') + noCache_a,
			m4a: (isIE) ? '' : mediaName.replace(typeRegex, '.m4a') + noCache_a
		}
		if (replaceFolders) {
			typeRegex = new RegExp('\\/' + thisType + '\\/', 'gi');
			fileEditions = {
				mp3: fileEditions.mp3.replace(typeRegex, '/320/'),
				oga: (isIE) ? '' : fileEditions.oga.replace(typeRegex, '/ogg/'),
				m4a: (isIE) ? '' : fileEditions.m4a.replace(typeRegex, '/m4a/')
			}
		}
		
		return fileEditions;
		
	} else {
		return false;
	}
}

function setAudioMedia($player, $title, href, title, justMp3, noCache){
	$player.jPlayer('clearMedia');
	if (justMp3) {
		$player.jPlayer('setMedia', {
			mp3: href
		});
	} else {
		var fileEditions = getMediaNames(href, !justMp3, noCache);
		$player.jPlayer('setMedia', fileEditions);
		
	}
	$title.html(title);
}

jQuery.fn.audioLink = function($player, $title, autoPlay){
	return this.each(function(){
		var $this = $(this);
		$this.click(function(e){
			e.preventDefault();
			var $theseClasses = $('.' + $this.attr('class'));
			$theseClasses.removeClass('activeAudioLink');
			$this.addClass('activeAudioLink');
			setAudioMedia($player, $title, $this.attr('href'), $this.attr('title'));
			if (autoPlay) $player.jPlayer('play');
		});
	});
};

function isLeapYear(theYear){
	return (theYear % 4 === 0) ? (theYear % 100 === 0) ? (theYear % 400 === 0) : true : false;
}

function getExpDateValue(moVal, yrVal){
	var d = '';
	switch (parseInt(moVal)) {
		case 1:
		case 3:
		case 5:
		case 7:
		case 8:
		case 10:
		case 12:
			d = '-31';
			break;
		case 2:
			d = isLeapYear(yrVal) ? '-29' : '-28';
			break;
		default:
			d = '-30';
			break;
	}
	var tzo = '' + (new Date().getTimezoneOffset() / 60) * (-1);
	if (tzo.length == 2) tzo = tzo.charAt(0) + '0' + tzo.charAt(1);
	return yrVal + '-' + moVal + d + 'T23:59:59.000' + tzo + ':00';
}

function get$Objects(objs){
	var retObj = {}, objsSplit = objs.split(', ');
	for (var i = 0; i < objsSplit.length; i++) {
		if (objsSplit[i]) retObj[objsSplit[i].replace(/\#/, '')] = $(objsSplit[i]);
	}
	return retObj;
}

function yearsSelectMenu($target, st_yr, end_yr, reverse){
	var appendHtml = '';
	if (reverse) {
		for (var i = st_yr; i >= end_yr; i--) {
			appendHtml += '<option value="' + i + '">' + i + '</option>';
		}
	} else {
		for (var i = st_yr; i <= end_yr; i++) {
			appendHtml += '<option value="' + i + '">' + i + '</option>';
		}
	}
	
	$target.append(appendHtml);
}

jQuery.fn.yearsSelectMenu = function(st_yr, end_yr, reverse){
	return this.each(function(){
		yearsSelectMenu($(this), st_yr, end_yr, reverse);
	});
};
jQuery.fn.daysSelectMenu = function(){

	if (this) {
	
		var appendHtml = '';
		var is1to9 = /1|2|3|4|5|6|7|8|9/gi
		for (var i = 1; i <= 31; i++) {
			var dayVal = (is1to9.test(i) ? '0' : '') + i;
			appendHtml += '<option value="' + dayVal + '">' + dayVal + '</option>';
		}
		$(this).append(appendHtml);
		
		return this;
	}
};

jQuery.fn.selectVal = function(_val){
	return this.each(function(){
		var $this = $(this);
		switch (this.nodeName.toLowerCase()) {
			case 'select':
				$this.find('option').each(function(){
					var $_this = $(this);
					if (_val == $_this.val()) {
						$_this.attr('selected', true);
						return false;
					}
				});
				break;
			case 'input':
				if ($this.attr('type') == 'radio') {
				//TODO: finish this when needed
				}
				break;
		}
		
	});
};
/**
 * Careful, not totally stable yet  :)
 * @param {Object} $elm
 */
var clock = {
	timer: false,
	start: function($elm){
		clearInterval(clock.timer);
		clock.timer = setInterval(function(){
			clock.run($elm);
		}, 1000);
	},
	stop: function(){
		clearInterval(clock.timer);
	},
	run: function($elm){
		var today = new Date();
		$elm.html(today.getHours() + ':' + clock.fixFormat(today.getMinutes()) + ':' + clock.fixFormat(today.getSeconds()));
	},
	fixFormat: function(i){//add a zero in front of numbers < 10
		if (i < 10) i = '0' + i;
		return i;
	}
}

jQuery.fn.makeClock = function(){
	clock.start($(this));
	return this;
};

jQuery.fn.uiTabsLink = function($tabsObj){
	return this.each(function(){
		var $this = $(this);
		$this.css('cursor', 'pointer').click(function(e){
			e.preventDefault();
			$tabsObj.tabs('select', $this.attr('href'));
		});
		
	});
};

var dElmRotate = {
	defs: {
		$items: [],
		interval: 4000,
		speed: 500,
		startWith: 0
	},
	current: 0,
	go: function(opts){
		var l = this;
		opts = $.extend(l.defs, opts);
		l.current = opts.startWith;
		
		opts.$items.hide();
		opts.$items.eq(l.current).show();
		function doNext(){
			opts.$items.eq(l.current).fadeOut(opts.speed, function(){
				l.current = (l.current == opts.$items.length - 1) ? 0 : l.current + 1;
				opts.$items.eq(l.current).fadeIn(opts.speed);
				startTimer();
			});
		}
		function startTimer(){
			var timeset = setTimeout(function(){
				doNext();
			}, opts.interval);
		}
		startTimer();
	}
};

jQuery.fn.dElmRotate = function(opts, elmSelector){
	var $this = $(this);
	if (!opts) {
		opts = {
			$items: false
		};
	}
	if (!opts.$items) opts.$items = $this.find('> ' + (elmSelector ? elmSelector : '*'));
	dElmRotate.go(opts);
};

var dHash = {
	set: function(newHash){
		window.location.hash = newHash;
	},
	get: function(keepPound){
		if (window.location.hash) return (!keepPound) ? window.location.hash.replace(/\#/, '') : window.location.hash;
	},
	restrictHash: ['tabs-1', 'tabs-2', 'tabs-3', 'tabs-4', 'tabs-5', 'tabs-6', 'tabs-7', 'home', 'my_account', 'my_history', 'group', 'groups', 'company', 'affiliate', 'settings', 'logout', 'discuss', 'discuss_tab'],//this keeps other hashes we use from being set as aff code cookies
	allowHash: function(h){
		var r_allowed = true;
		for (var i = 0; i < this.restrictHash.length; i++) {
			if (h == this.restrictHash[i]) r_allowed = false;
		}
		return r_allowed;
	},
	setToCookie: function(c_name, exDays){
		var thisHash = dHash.get();
		if (thisHash && this.allowHash(thisHash)) {
         dCookies.set(c_name, thisHash, exDays);
      }
	},
	getCookieHash: function(c_name){//hatsosAffCode
		return dCookies.get(c_name);
	}
};

var dCookies = {
	get: function(c_name){
		var i, x, y, allCookies_arr = document.cookie.split(';'), returnVal = false;
		for (i = 0; i < allCookies_arr.length; i++) {
			x = allCookies_arr[i].substr(0, allCookies_arr[i].indexOf('='));
			y = allCookies_arr[i].substr(allCookies_arr[i].indexOf('=') + 1);
			x = x.replace(/^\s+|\s+$/g, '');
			if (x == c_name) returnVal = unescape(y);
		}
		return returnVal;
	},
	set: function(c_name, c_value, exDays){
		var exdate = new Date();
		exdate.setDate(exdate.getDate() + exDays);
		c_value = escape(c_value) + ((!exDays) ? '' : '; expires=' + exdate.toUTCString());
		document.cookie = c_name + '=' + c_value;
	},
	remove: function(c_name){
		document.cookie = c_name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT;';
	}
};

/** 
 * make IE support indexOf for arrays
 */
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function(obj, start){
		for (var i = (start || 0), j = this.length; i < j; i++) {
			if (this[i] === obj) {
				return i;
			}
		}
		return -1;
	}
}
Array.prototype.removeByVal = function(v){
	if (this) {
		var x = this.indexOf(v);
		if (x != -1) this.splice(x, 1);
		return this;
	}
}
Array.prototype.pushUnique = function(v){
	if (this) {
		var valExists = false;
		for (var i = 0; i < this.length; i++) {
			if (this[i] == v) valExists = true;
		}
		if (!valExists) this.push(v);
		return this;
	}
}

function abtAudioLoad(target){
	flowplayer(target, {
		src: '/extensions/flowplayer/flowplayer-3.2.7.swf',
		wmode: 'transparent'
	}, {
		plugins: {
			controls: {
				fullscreen: false,
				height: 30,
				autoHide: false
			}
		}
	});
}

jQuery.fn.changeAudioPlayer = function($target, changeHash){
	$target = jQuery($target);
	return this.each(function(){
	
		var $this = jQuery(this);
		$this.click(function(e){
		
			e.preventDefault();
			if ($this.attr('id') && changeHash) {
				var hashRegex = new RegExp(changeHash);
				location.hash = $this.attr('id').replace(hashRegex, '');
			}
			$target.attr('href', $this.attr('href'));
			
			//flowplayer($target.attr('id')).play($this.attr('href'));
			
			abtAudioLoad($target.attr('id'));
			
		});
	});
};

jQuery.jsonStringify = function(obj){
	var t = typeof(obj);
	if (t != 'object' || obj === null) {
		// simple data type
		if (t == 'string') obj = '"' + obj + '"';
		return String(obj);
	} else {
		// recurse array or object
		var n, v, json = [], arr = (obj && obj.constructor == Array);
		for (n in obj) {
			v = obj[n];
			t = typeof(v);
			if (t == 'string') v = '"' + v + '"';
			else if (t == 'object' && v !== null) v = jQuery.jsonStringify(v);
			json.push((arr ? '' : '"' + n + '":') + String(v));
		}
		return (arr ? '[' : '{') + String(json) + (arr ? ']' : '}');
	}
};

function createCart(opts){
	var defs = {
		$box: false,
		cookieName: 'hatsosPubStore'
	};
	
	opts = $.extend(defs, opts);
	
	var storeCart = {
		$box: opts.$box,
		cookieName: opts.cookieName,
		getCartItems: function(){
			var t = this;
			//Get current store cookie
			var cartCookie_str = dCookies.get(t.cookieName);
			if (!cartCookie_str) cartCookie = {};
			else cartCookie = $.parseJSON(cartCookie_str);
			
			return cartCookie;
		},
		updateCart: function(action, itemInfo_str, boxUpdate){
			var t = this;
			//get the product info from url string. Recognized params are name, id, price, qnt
			var itemInfo = paramObj.toObj(itemInfo_str);
			
			itemInfo['param_str'] = itemInfo_str;
			
			//Get current store cookie
			var cartCookie = dCookies.get(t.cookieName);
			if (!cartCookie) cartCookie = {};
			else cartCookie = $.parseJSON(cartCookie);//.parseJSON();
			if (action == 'add') cartCookie[itemInfo.id] = itemInfo;
			else if (action == 'remove') delete cartCookie[itemInfo.id];
			
			//log(cartCookie);
			
			var cartCookie_str = $.jsonStringify(cartCookie);//.toJSONString();
			//Add the item to the cart cookie or remove it if there are no items.
			if (!cartCookie_str || cartCookie_str == '{}') dCookies.remove(t.cookieName);
			else dCookies.set(t.cookieName, cartCookie_str, 365);
			
			if (boxUpdate) boxUpdate.call(this, cartCookie);
			else t.updateBox();
			
			return cartCookie;
			
		},
		updateBox: function(callback){
			var t = this;
			if (t.$box && t.$box.length) {
				t.$box.empty();
				var cartCookie_str = dCookies.get(t.cookieName);
				//log('cartCookie_str: ' + cartCookie_str);
				if (cartCookie_str) {
				
					var cartCookie = $.parseJSON(cartCookie_str);//.parseJSON();
					var x;
					var allItemsHtml = '';
					for (x in cartCookie) {
						var z;
						var thisItem = cartCookie[x];
						
						var itemHtml = '<div id="cartBoxItem_' + thisItem.id + '" class="cartBoxItem">';
						itemHtml += '<div>' + thisItem.name + ': $' + thisItem.price * parseInt(thisItem.qnt) + '</div>';
						itemHtml += '<div><a href="' + thisItem.param_str + '" class="cartItemRemove">remove</a><input class="itemQnt" type="text" maxlength="4" value="' + thisItem.qnt + '" /></div>';
						itemHtml += '</div>';
						allItemsHtml += itemHtml;
					}
					t.$box.append(allItemsHtml);
					$('a.cartItemRemove').click(function(e){
						e.preventDefault();
						t.updateCart('remove', $(this).attr('href'));
					});
					
					if (callback) callback.call(this, cartCookie);
					
				}
			}
		}
	};
	
	return storeCart;
}

var dConvert = {
	mbToGb: function(mbs){
		return mbs / 1024;
	},
	bgToMb: function(gbs){
		return gbs * 1024;
	}
};
/**
 * Got this from here: http://stackoverflow.com/questions/2901102/how-to-print-number-with-commas-as-thousands-separators-in-javascript
 * This code will make a number output with the commas e.g. 1000 becomes 1,000
 * @param {Object} x
 */
function numberWithCommas(num){
	return num.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ',');
}

/**
 * load the countries into a select menu from xml
 */
jQuery.fn.loadCountries = function(){
	return this.each(function(){
		var $this = $(this);
		if (this.nodeName == 'select' || this.nodeName == 'SELECT') {
			$.get('/xml/allCountries.xml', function(xml){
				var countries = $.xml2json(xml).country, countriesHtml = '<option value="false">Select Country</option>';
				var x;
				//log(countries);
				for (x in countries) {
					if (countries[x].name) countriesHtml += '<option value="' + countries[x].name + '">' + countries[x].name + '</option>';
				}
				$this.html(countriesHtml);
			});
		}
	});
};

/**
 * load the US states into a select menu from xml
 */
jQuery.fn.loadStates = function(){
	return this.each(function(){
	
		var $this = $(this);
		if (this.nodeName == 'SELECT' || this.nodeName == 'select') {
			$.get('/xml/allStates.xml', function(xml){
				var states = $.xml2json(xml).state, statesHtml = '<option value="false">Select State</option>';
				var x;
				for (x in states) {
					if (states[x].name) statesHtml += '<option value="' + states[x].name + '">' + states[x].name + '</option>';
				}
				$this.html(statesHtml);
			});
		}
	});
};


$.fn.dPanelSlider = function(opts){
	var defs = {
		thumbs: '.ps_thumb',
		leftArr: '.leftArrow', //selector for left arrow.
		rightArr: 'rightArrow', //selector for right arrow.
		objCss: {}, //optional CSS object. Can assign any values
		speed: 600,
		easing: false,
		step: 6//the number of thumbs to move on each slide
	};
	opts = $.extend(defs, opts);
	
	return this.each(function(){
	
		var obj = $(this), $thumbs = obj.find(opts.thumbs);
		var objCss = {
			overflow: 'hidden',
			padding: '5px'
		};
		obj.css($.extend(objCss, opts.objCss));
		var $slidePanel = $('<div id="' + obj.attr('id') + '_panel" class="dPanel"></div>');
		obj.append($slidePanel);
		$thumbs.appendTo($slidePanel);
		var numThumbs = $thumbs.length, panelW = 0;
		$thumbs.each(function(){
			panelW += $(this).outerWidth(true);
			//log('panelW + ' + $(this).outerWidth(true) + ' = ' + panelW);
		});
		$slidePanel.width(panelW);
		var panelWrW = obj.width();
		
		var $arrow = {
			left: $(opts.leftArr),
			right: $(opts.rightArr)
		};
		var numHidden = 0;
		
		$arrow.left.click(function(e){
			var t_MarginLeft = $slidePanel.css('marginLeft');
			t_MarginLeft = (t_MarginLeft == 'auto') ? 0 : parseInt(t_MarginLeft);
			//alert('numHidden: ' + numHidden);
			if (numHidden > 0) {// && t_MarginLeft >= 0) {
				var positiveNumHid = (numHidden < 0) ? -numHidden : numHidden;
				var navToIndex = ((positiveNumHid) - (opts.step + 1));
				//log('Go left from ' + positiveNumHid + ' to ' + navToIndex);
				
				$thumbs.eq(positiveNumHid).prevUntil().each(function(){
					var thisEq = $(this).index();
					if (thisEq < positiveNumHid && thisEq > navToIndex) t_MarginLeft += $(this).outerWidth(true);
				});
				
				$slidePanel.animate({
					marginLeft: t_MarginLeft + 'px'
				}, opts.speed, opts.easing, function(){
					numHidden -= opts.step;
					//alert(numHidden + ' -= ' + opts.step);
				});
			}
		});
		$arrow.right.click(function(e){
			var t_MarginLeft = $slidePanel.css('marginLeft');
			t_MarginLeft = (t_MarginLeft == 'auto') ? 0 : parseInt(t_MarginLeft);
			//alert((panelW + ' + ' + t_MarginLeft) + ' > ' + panelWrW);
			if (panelW > panelWrW && ((panelW + t_MarginLeft) > panelWrW)) {
				var positiveNumHid = (numHidden < 0) ? -numHidden : numHidden;
				var navToIndex = ((positiveNumHid) + (opts.step + 1));
				//log('Go right from ' + positiveNumHid + ' to ' + navToIndex);
				
				$thumbs.eq(positiveNumHid).nextUntil().each(function(){
					var thisEq = $(this).index();
					if (thisEq > positiveNumHid && thisEq < navToIndex) t_MarginLeft -= $(this).outerWidth(true);
				});
				
				$slidePanel.animate({
					marginLeft: t_MarginLeft + 'px'
				}, opts.speed, opts.easing, function(){
					numHidden += opts.step;
				});
			}
			
		});
	});
};

var taxes = {
	states: {
		'utah': 6.85,
		'ut': 6.85,
		'washington': 6.5,
		'wa': 6.5
	},
	getRate: function(price, state){
		var taxrate = 0;
		for (x in this.states) {
			if (state.toLowerCase() == x) taxrate = this.states[x];
		}
		return (taxrate) ? dMath.round((parseFloat(price) / 100) * taxrate, 2) : 0;
	}
};

var telesems = {
	jsonUrl: '/json/teleseminars.json',
	isNext: function(json){
		var x;
		var lib = this, nonExpireds = [];
		for (x in json.items) {
			var thisItem = json.items[x];
			if (!lib.isExpired(thisItem.date)) nonExpireds.push(x);
		}
		return (nonExpireds.length) ? nonExpireds[0] : false;
	},
	isExpired: function(_itemDate){
		return dDate.todayObjNoTime() > new Date(_itemDate);
	},
	isToday: function(_itemDate){
		return dDate.todayObjNoTime().toString() == _itemDate.toString();
	},
	get: function(callback){
		var lib = this;
		$.post(lib.jsonUrl, function(jsonData){
			if (callback) callback.call(this, jsonData);
		});
	}
};
var buttonLink = {
	doAction: function(e, obj){
		var obj = $(obj);
		if (obj.attr('href')) {
			e.preventDefault();
			if (obj.attr('target') == '_blank' || obj.attr('target') == 'blank') {
				window.open(obj.attr('href'));
			} else {
				location.href = obj.attr('href');
			}
		}
	},
	create: function(obj, bindLive){
		var obj = $(obj);
		if (obj.attr('href')) {
			if (bindLive) {
				obj.live('click', function(e){
					buttonLink.doAction(e, this);
				});
			} else {
				obj.click(function(e){
					buttonLink.doAction(e, this);
				});
			}
			
		}
	}
};

var dCron = {
	parse: function(str){
		var items = str.split(' ');
		//array index 0 = min (0-59), 1 = hour (0-23),  2 = day of month (1-31), 3 = month (1-12), 4 = day of week (0-6, sunday = 0) 
		var cronJsObj = {
			min: items[0],
			hour: items[1],
			dayOfMonth: items[2],
			month: items[3],
			dayOfWeek: items[4]
		};
		
		//TODO: finish the cron tab parser
		return cronJsObj;
		
	},
	create: function(cObj){
	
		return cObj.min + ' ' + cObj.hour + ' ' + cObj.dayOfMonth + ' ' + cObj.month + ' ' + cObj.dayOfWeek;
	}
}

jQuery.fn.buttonLink = function(bindLive){
	if (bindLive) {
		buttonLink.create(this, bindLive);
		return this;
	} else {
	
		return this.each(function(){
			buttonLink.create(this);
		});
		
	}
}

var dBrowser = {
	attachUnload: function(msg){
		window.onbeforeunload = function(){
			return msg;
		};
		window.beforeunload = function(){
			return msg;
		};
	},
	removeUnload: function(){
		window.onbeforeunload = false;
		window.beforeunload = false;
	}
};

var dBoolean = {
	tf: function(boo){
		if (boo == 'true' || boo == 'True' || boo == 'TRUE') {
			return true;
		} else if (boo == 'false' || boo == 'False' || boo == 'FALSE') {
			return false;
		}
	}
};
/**
 *
 localStorage.setItem('myKey', 'myValue');
 var myVar = localStorage.getItem('myKey');
 */
var dPlatform = {
	type: {
		device: false,
		os: false
	},
	detect: function(){
		var lib = this;
		var uagent = navigator.userAgent.toLowerCase();
		var isIphone = uagent.search('iphone') > -1, isIpod = uagent.search('ipod') > -1, isIpad = uagent.search('ipad') > -1;
		if (isIphone || isIpod || isIpad) {
			lib.type.os = 'ios';
			lib.type.device = (isIphone ? 'iPhone' : (isIpod ? 'iPod' : 'iPad'));
		} else {
			lib.type.os = false;
			lib.type.device = false;
		}
	}
};

var msToTime = {
	two: function(x){
		return ((x > 9) ? "" : "0") + x;
	},
	three: function(x){
		return ((x > 99) ? "" : "0") + ((x > 9) ? "" : "0") + x;
	},
	convert: function(ms){
		var lib = this;
		
		var sec = Math.floor(ms / 1000);
		ms = ms % 1000;
		t = lib.three(ms);
		
		var min = Math.floor(sec / 60);
		sec = sec % 60;
		t = lib.two(sec);// + ":" + t;
		var hr = Math.floor(min / 60);
		min = min % 60;
		t = lib.two(min) + ":" + t;
		
		/*var day = Math.floor(hr / 60);
		 hr = hr % 60;
		 t = lib.two(hr) + ":" + t;
		 t = day + ":" + t;*/
		return t;
	}
};

var dDiscounts = {
	reverse: function(finalAmount, percent){
		return finalAmount / ((100 - percent) / 100);
	},
	percentOf: function(amount, percent){
		return amount * (percent / 100);
	},
	discountPrice: function(amount, percent){
		return amount - (amount * (percent / 100));
	}
}

