// Copyright (c) 2011 All Right Reserved, http://calvinmceachron.com/
//
// This source is free for commercial and non-commercial use.
// Please credit the author for use of any part of this source.
// Code is subject to change without notice.
// All other rights reserved.
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// @author	Calvin McEachron
// @email	me@calvinmceachron.com
// @summary	Contains a number of methods and objects for easier Javascripting



/*
 * -----------------------------------------------------------------------------
 * Javascript Type Helpers
 * -----------------------------------------------------------------------------
 */

function is_object 	(o)	{ return typeof(o) == 'object'; }
function is_undef 	(u)	{ return u == undefined || typeof(u) == 'undefined'; }
function is_string 	(s)	{ return typeof(s) == 'string'; }
function is_empty 	(e)	{ return e==null || e==="" || e===[] ; }
function is_num 	(n)	{ return !isNaN(parseFloat(n)) && isFinite(n); }

/*
 * -----------------------------------------------------------------------------
 * Javascript Array Helpers
 * -----------------------------------------------------------------------------
 */

/**
 * @param key	the element being searched for
 * @param arr	Array to search through
 */
function in_array (key, arr) {
	var len = arr.length;
	while (len--) { if ( arr[len] == key ) return true; }
	return false;
}

/**
 * shuffle the elements of an array
 * @param v		Array
 * @return		Array
 */
function shuffle (v) {
    for(var j, x, i = v.length; i; j = parseInt(Math.random() * i), x = v[--i], v[i] = v[j], v[j] = x);
    return v;
};

/*
 * -----------------------------------------------------------------------------
 * Helper Methods
 * -----------------------------------------------------------------------------
 * line-saving, time-saving, thought-saving functions
 */

/**
 * @return Object	height and width of the html document
 */
function bodysize () { return { h: $(document).height(), w: $(document).width() }; }

/**
 * returns json object from a post request that doesn't send data
 * @param src	String	url being requested
 * @param async	Boolean
 * @return		JSON
 */
function getSimpleJSON (src, async) {
	var item = null;
	$.ajax({
		async:		option(async, false),
		type: 		'post',
		url:		src,
		dataType:	'json',
		success:	function(data){item=data;}
	});
	return item;
}

/**
 * This function returns a fallback value if the intended/preferred value is
 * null or not set, based on Ocaml options =D
 * @param preferred
 * @param option
 * @return
 */
function option (preferred, option) {
	if (is_undef(preferred) || is_empty(preferred))
		return option;
	return preferred;
}

/**
 * Rounds an floating point value
 * @param n		Float	the number to be rounded
 * @param dec	Int		number of decimal places desired
 * @return
 */
function round (n,dec) {
	n = parseFloat(n);
	if(!isNaN(n)){ if(!dec) var dec= 0; var factor= Math.pow(10,dec);
		return Math.floor(n*factor+((n*factor*10)%10>=5?1:0))/factor;
	}else{ return n; }
}

/**
 * Limits the number of characters on an input element
 * @param input		Object/String 	the dom element accepting input
 * @param limit		Int				the maximum number of characters to allow
 * @param counter	Object/String	element acting as counter if one exists
 * @return
 */
function limitChars (input, limit, counter) {
	var msg = $(input).val();
	if (msg.length > limit) $(input).val(msg.substring(0,limit));
	if (! is_undef(counter)) $(counter).empty().append(limit-msg.length);
}

function DateToUnix (date) {
	return Math.round(date.getTime() / 1000);
}

/**
 * performs a conditional operation on an array of elements
 * @param arr	the array of elements to be tested
 * @param comparator	the comparison operator being used
 * @param join	the conditional operator joining the expressions
 * @param val	the value being tested against
 * @return	boolean
 * 
 * usage: if ( conditional([1,3,5,9], '>', '|', 7) ) // perform action
 */
function conditional (arr, comparator, join, val) {
	var i = arr.length;
	switch(join) {
	case "&": case "&&":
		while(i--) {
			switch(comparator) {
			case "==": if (!(arr[i] == val)) return false; break;
			case ">": if (!(arr[i] > val)) return false; break;
			case "<": if (!(arr[i] < val)) return false; break;
			case "!=": if (!(arr[i] != val)) return false; break;
			}
		}
		return true; break;
	case "|": case "||":
		while(i--) {
			switch(comparator) {
			case "==": if (arr[i] == val) return true; break;
			case ">": if (arr[i] > val) return true; break;
			case "<": if (arr[i] < val) return true; break;
			case "!=": if (arr[i] != val) return true; break;
			}
		}
		return false; break;
	}
}


/*
 * -----------------------------------------------------------------------------
 * Debugging Helpers
 * -----------------------------------------------------------------------------
 */

/**
 * @param obj	the object to be printed
 */
function printr (obj) { alert(objstr(obj)); }

/**
 * recursively iterates through an object, building a pretty printed string 
 * representation of that object and its properties
 * @param myObj	the object to be converted to string
 * @param tab	(Optional). the starting tab position
 */
function objstr (myObj,tab) {
	var repeat = function (x,s) { 
		$s = ""; for (var i=0;i<x;i++){ $s+=s; } return $s; 
	}
	var str = "";
	if (is_undef(tab)) tab = 0;
	if (is_object(myObj)) {
		for (key in myObj) {
			str += repeat(tab,"\t");
			if (is_object(myObj[key]))
				str += '['+key+'] \n' + objstr(myObj[key],tab+1) + "\n";
			else
				str += '['+key+'] =>' + objstr(myObj[key],tab+1) + "\n";
		} 
	}
	else str += myObj;
	return str;
}

/*
 * -----------------------------------------------------------------------------
 * Custom Javascript Popup Object
 * -----------------------------------------------------------------------------
 * sample use:
 * popup = new Popup();
 * popup.content = htmlString;
 * popup.show();	// displays popup
 * popup.hide();	// removes popup
 * 
 * provided extensions:
 * confirmation popup, alert popup
 */

var popup_config = {
	base_style: { 'border':'20px solid #DDD', 'background-color':'#efefef', 'padding':'10px', 'box-shadow': '0 0 20px #000', 'border-radius': '10px', '-webkit-box-shadow': '0 0 20px #000', '-moz-box-shadow': '0 0 20px #000', '-webkit-border-radius': '10px', '-moz-border-radius': '10px', 'width':'300px', 'overflow-y':'auto' },
	position: { 'position':'absolute', 'margin':'auto', 'top':'0px', 'bottom':'0px', 'left':'0px', 'right':'0px', 'display':'none' },
	ismodal: true,
	close: $('<img alt="close" src="http://crimsonlist.org/assets/css/images/close_popup.png" />')
}

// FIXME positioning of close button
function Popup (config) {
	var configuration = option(config, popup_config);
	this.page_height = bodysize().h;
	this.page_width = bodysize().w;
	this.zindex = Popup.level;
	this.popup = $('<div class="x-popup"></div>');		// Popup DOM element
	this.modal = $('<div></div>');
	//this.close = option(configuration.close, popup_config.close);
	this.position = option(configuration.position, popup_config.position);
	this.base_style = configuration.base_style;
	this.ismodal = option(configuration.ismodal, popup_config.ismodal);
	this.autohide = this.ismodal;	// if modal, then popup hides on click elsewhere
	
	// (Optional)	Object	css styles
	this.style = null;
	
	// (Required). content of the popup (html string or JQuery object)
	this.content = null;
}

Popup.prototype.show = function () {
	Popup.level += 5;
	if (! is_empty(this.content)) {
		this.content = is_string(this.content) ? $(this.content) : this.content;
		
		//this.close.css({ 'position':'absolute', 'width':'25px', 'height':'25px', 'left':'-25px', 'top':'-25px', 'cursor':'pointer', 'z-index':this.zindex+1 });
		//this.popup.append(this.close);
		
		this.popup.append(this.content);
		
		this.popup.css(this.base_style).css(this.position).css(this.style).css({'z-index':this.zindex});
		
		this.modal.css({ 'position':'absolute','top':0,'bottom':0,'left':0,'right':0,'margin':0,'height':this.page_height,'width':this.page_width, 'z-index':this.zindex-1,'background-color':'#EEE' });
		
		if (this.ismodal) {
			this.modal.css({'opacity':0.8});
		}
		else {	// prevent interaction elsewhere on screen
			this.modal.css({'opacity':0});
			//this.close.css({'display':'none'});
		}
		
		if (this.autohide) { 
			this.modal.click($.proxy(function() {  this.hide();  }, this));
			//this.close.click($.proxy(function() {  this.hide();  }, this));
		}
		
		$('body').append(this.modal).append(this.popup);
		this.popup.fadeIn(1000);
		
		var offset = this.content[0].offsetHeight;
		if (this.page_height > (offset + 50)) {
			this.popup.css({'height':(offset+5)+'px'});	// adjust height to fit contents
		}
		else {
			this.popup.css({'height':(0.9*this.page_height)+'px'});
		}
		
	}
}

Popup.prototype.hide = function () {
	this.popup.detach();
	this.modal.detach();
	Popup.level -= 5;
}

Popup.level = 5;

/**
 * confirmation popup 
 * @param message	confirmation question to be displayed in popup
 * @param handler	function to execute on positive confirmation
 * @param text		text for buttons
 */
function Confirm (message, handler, text, config) {
	
	var y = is_undef(text) ? 'Confirm' : option(text.confirm, "Confirm");
	var n = is_undef(text) ? 'Cancel' : option(text.cancel, "Cancel");
	
	var i = 0; while ($('#_confirm'+i).length) { i++; }
		
	var confirm = new Popup(config);
	confirm.style = { 'border-color':'#31A3A3' };
	confirm.content =
		'<div style="font-size:14px;">'+
			'<div style="margin-bottom:5px;">'+ message +'</div>'+
			'<button class="confirm-button" id="_confirm'+i+'">'+y+'</button>'+
			'<button class="confirm-button" id="_cancel'+i+'">'+n+'</button>'+
		'</div>';
	confirm.show();
	$yes = $("#_confirm"+i); $no = $("#_cancel"+i);
	$yes.focus().click(handler).click(function () { confirm.hide(); });
	$no.click(function () { confirm.hide(); });
}

/**
 * alert popup 
 * @param message	confirmation question to be displayed in popup
 * @param handler	(Optional) function to execute on positive confirmation
 * @param text		(Optional) Object	confirm button text
 */
function Alert (message, handler, text, config) {
	var height = (message.length / 50) * 14;	// font size 14
	if ($('#alert_popup').length) { return; }
	var y = is_undef(text) ? 'Ok' : option(text.confirm, "Ok");
	
	var popup = new Popup(config);

	popup.style = { 'border-color':'#FFE303' };
	popup.content = 
		'<div style="font-size:14px;">'+
			'<div style="margin-bottom:5px;">'+ message +'</div>'+
			'<button class="confirm-button" id="_alert_confirm" >'+y+'</button>'+
		'</div>';
	popup.show();
	$yes = $("#_alert_confirm");
	
	if (!is_undef(handler)) $yes.click(handler);
	$yes.focus().click(function(){ popup.hide(); });
}

/**
 * -----------------------------------------------------------------------------
 * Similar to the original Popup object, XYPopup creates a Popup that appears 
 * at a given location in the window 
 * -----------------------------------------------------------------------------
 * @param id	the id of the popup being created
 * @param x		the x location of the center of the popup
 * @param y		the y location of the center of the popup
 * @param height
 * @param width
 * @param parent	the element popup is being displayed in (jQuery selector)
 * @return
 */
function XYPopup (x, y, width, height, parent) {
	this.zindex 	= 	Popup.level;
	this.parent 	= 	parent;
	this.content 	= 	null;
	this.position 	= {
			'position':'absolute', 'top':(y-height/2)+'px',	'left':(x-width/2)+'px', 
			'margin':0, 'padding':0, 'z-index':this.zindex, 'display':'none'
				};
	this.base_style = {
			'border':'1px solid #FFF', 'background-color':'#EFEFEF', 'background-color':'rgba(250,250,250,.7)',
			'-webkit-box-shadow': '0 0 30px 10px #BFBFBF', 	'-webkit-border-radius': '10px',
			'box-shadow': '0 0 30px 10px #BFBFBF', '-moz-box-shadow': '0 0 30px 10px #bfbfbf',
			'-moz-border-radius': '10px', 'border-radius': '10px', 'width':width+'px','height':height+'px', 
			};
	this.autohide 	=	false;
	this.popup 	= 	$('<div></div>');
	this.close 	= 	$('<img alt="close" src="http://crimsonlist.org/assets/css/images/close_popup.png" />');
	
}

XYPopup.prototype.show = function () {
	Popup.level += 5;
	if (! is_empty(this.content)) {
		this.content = $(this.content);
		
		if (this.autohide) {
			this.popup.mouseleave($.proxy(function() { this.hide(); }, this));
		}
		else {
			this.popup.append(this.close);
			this.close.css({
				'float':'right', 'width':'15px', 'height':'15px', 
				'margin':'-5px -5px 0 0', 'cursor':'pointer'
				});
			this.close.click($.proxy(function() { this.hide(); }, this));
		}

		this.popup.append(this.content);
		
		this.popup.css( this.base_style ).css( this.position );
		if (! is_empty(this.style)) this.popup.css( this.style );
		
		$(this.parent).append(this.popup);
		
		this.popup.fadeIn('slow');
		this.content.css({	// center content as soon as it is displayed
			'display':'block','margin':'auto', 'width':this.content.css('width'), 'height':this.content.css('height'),
			'position':'absolute', 'left':0, 'right':0, 'top':0, 'bottom':0, 'text-align':'center'
		});
	}
}

XYPopup.prototype.hide = function () {
	this.popup.detach();
	Popup.level -= 5;
}

/**
 * -----------------------------------------------------------------------------
 * Custom Javascript Date Object
 * -----------------------------------------------------------------------------
 * creates a Date object from a Unix Timestamp and extends that object with 
 * methods to return string representations of day, date, and month. For easy
 * integration with PHP timestamp
 * @param timestamp	unix timestamp
 */

function Unix_Date (timestamp) {
	this.timestamp = timestamp;
	this.jtime = this.timestamp * 1000;
	this.date = new Date(this.jtime);	// use this for natural Date methods
}

Unix_Date.prototype.getDay = function () {
	var weekday=new Array(7);
	weekday[0]="Sunday";
	weekday[1]="Monday";
	weekday[2]="Tuesday";
	weekday[3]="Wednesday";
	weekday[4]="Thursday";
	weekday[5]="Friday";
	weekday[6]="Saturday";
	
	return weekday[this.date.getDay()];
}

Unix_Date.prototype.getMonth = function () {
	var month=new Array(12);
	month[0]="January";
	month[1]="February";
	month[2]="March";
	month[3]="April";
	month[4]="May";
	month[5]="June";
	month[6]="July";
	month[7]="August";
	month[8]="September";
	month[9]="October";
	month[10]="November";
	month[11]="December";
	
	return month[this.date.getMonth()];
}

Unix_Date.prototype.getDate = function () {
	var date = this.date.getDate(),	end = '';
	switch(date) {
		case 1: case 21: case 31: end = 'st'; break;
		case 2: case 22: end = 'nd'; break;
		case 3: case 23: end = 'rd'; break;
		default: end = 'th'; break;
	}
	
	return date + end;
}

Unix_Date.prototype.getYear = function () {
	return this.date.getFullYear();
}

/**
 * @param format	String. (e.g. m/d/y h:i or m-d-y)
 */
Unix_Date.prototype.formatString = function (format) {
	format = format.replace(/m/i, date.getMonth()+1);
	format = format.replace(/d/i, date.getDate());
	format = format.replace(/y/i, date.getFullYear());
	format = format.replace(/h/i, meridiem(date.getHours()));
	var min = date.getMinutes();
	format = (min < 10) ? format.replace(/i/i, '0'+date.getMinutes()) : format.replace(/i/i, date.getMinutes());
	return format;
}

