/* ocms-extensions.js */
/*
	2011-Nov-23 dwl added fn.partial() as an super curry() and fn.defaults()
	2011-Nov-21 dwl isIE8() joins isIE7()
	2011-Oct-01 dwl always establish properties inPageEditor and inPagePreview, without having to
					call addAuthorClasses()
	2011-Jul-20 dwl slight upgrade to consoleMsg() for webkit; deserves more attention
	2011-Jul-19 dwl month name support added to Date object
	2011-Jun-21 dwl support for cms api's page_mode
	2011-May-06 dwl cleaning up dmStudio's version of this module that had added OCMS.lastFocus,
					 changed addBrowserClasses(), and added email address support
*/

var OCMS;
if (!OCMS) {
	/* OCMS becomes a global var */
	OCMS = {};
}

(function ($) {
	//helper function for adding methods
	Function.prototype.method = function (name, func) {
		if (!this.prototype[name]) {
			this.prototype[name]= func;
			return this;
		}
	}

	// properly rounds both +ve and -ve numbers
	Number.method('integer', function () {
		return Math[this < 0 ? 'ceil' : 'floor'](this);
	});

	/* already present in Orchestra JavaScript Library 1.0.0? */
	String.method('trim', function () {
		return this.replace(/^\s+|\s+$/g, '');
	});

	// template string fill-er in-ner
	// based on source in Crockford's JavaScript: The Good Parts
	// 'Hello {you}'.supplant({you: 'world!'});
	// 'Hello {1}'.supplant(['or use an array!', 'world!', 'unused']);
	String.method('supplant', function (o) {
		return this.replace(/{([^{}]*)}/g,
			function (m, k) {
				var r = o[k];
				return typeof r === 'string' ? r : m;
			}
		);
	});

	Date.method('getMonthName', function () {
		return ['January', 'February', 'March', 'April', 'May', 'June',
				'July', 'Augusst', 'September', 'October', 'November', 'December'][this.getMonth()];
	});


	Date.method('getMonthNameAbbr', function () {
		return this.getMonthName().slice(0, 3);
	});


	// based on http://ejohn.org/blog/partial-functions-in-javascript/
	// customFunc = myFunc.partial(undefined, 10, 'yes', undefined);
	// produces a function whose 2nd and 3rd parameters have *HARD-WIRED* default values
	// customFunc(a, b) ==> myFunc(1, 10, 'yes', b)
	Function.method('partial', function () {
		var fn = this,
			args = Array.prototype.slice.call(arguments);

		return function () {
			var i,
				arg = 0;

			for (i = 0; i < args.length && arg < arguments.length; i++ ) {
				// if argument at creation time was undefined, use the argument of the invocation
				if (args[i] === undefined) {
					args[i] = arguments[arg++];
				}
			}

			return fn.apply(this, args);
		};
	  });	// partial


	// provide default argument values, but let them be overridden by the caller
	Function.method('defaults', function () {
		var fn = this,
			defArgs = Array.prototype.slice.call(arguments);

		return function () {
			var def = 0,
				theseArgs = Array.prototype.slice.call(arguments),
				maxArg = Math.max(defArgs.length, theseArgs.length);

			for (def = 0; def < maxArg; def++) {
				// if invocation argument is empty, take from the defaults
				// always let invocation argument override defaults
				if (theseArgs[def] === undefined) {
					theseArgs[def] = defArgs[def];
				}
			}

			return fn.apply(this, theseArgs);
		};
	  });	// defaults


	// extend jQuery

	// - call these when fading element has alpha transparency
	// - reverts to show/hide for IE < 9

	$.fn.fadIn = function (dur) {
		return this.each(function () {
			if ($.browser.msie && $.browser.version < 9) {
				$(this).show();
			} else {
				$(this).fadeIn(dur || null);
			}
		});
	};

	$.fn.fadOut = function (dur) {
		return this.each(function () {
			if ($.browser.msie && $.browser.version < 9) {
				$(this).hide();
			} else {
				$(this).fadeOut(dur || null);
			}
		});
	};

	/**************************************************************************
		- simple in-place hint text for input[type='text'] control
		- lacks submit support
	*/
	$.fn.hintText = function (txt, className) {
		className = className || 'hintText';

		return this.each(function () {
			function toFocus () {
				if (this.value === txt) {
					this.value = '';
					$(this).removeClass(className);
				}
			}

			function toBlur () {
				if (this.value.length === 0) {
					this.value = txt;
					$(this).addClass(className);
				}
			}

			this.value = txt;
			$(this)
				.addClass(className)
				.focus(toFocus)
				.blur(toBlur);
		});
	};


	// FUTURE: if nMaxChars not given, look for it in a data- attribute
	$.fn.installCharCounter = function installCharCounter (ccSelector, nMaxChars) {
		return this.each(function () {
			ccSelector = ccSelector || '.charCount';
			var $charCount = $(ccSelector);

			if ($charCount.length) {
				nMaxChars = nMaxChars || 500;

				$(this).keyup(function () {
					var sStory = $(this).val(),
						nChars = nMaxChars - sStory.length;	// nChars: REMAINING

					$charCount.text(nChars);
					if (nChars < 0) {
						$(this).val(sStory.slice(0, nMaxChars)).scrollTop($(this)[0].scrollHeight);
						$charCount.text(0);
					}
				}).keyup();
			} else {
				OCMS.consoleMsg('couldn\'t find ' + ccSelector);
			}
		}
	)};


	(function () {
		var cmsInfo = $(document).data('cms'),
			location;

		if (cmsInfo && cmsInfo.page_mode) {
			OCMS.inPageEditor = cmsInfo.page_mode === 'edit';
			OCMS.inPagePreview = cmsInfo.page_mode === 'prev';
			// or === 'production'
			// or === ... ?
		} else {
			location = window.location.href.toLowerCase();
			OCMS.inPageEditor = location.indexOf('/apex/edit?') > 0;
			OCMS.inPagePreview = location.indexOf('/apex/preview?') > 0;
		}
	})();


	OCMS.addAuthorClasses = function () {
		if (OCMS.inPageEditor) {
			$('body').addClass('OCMS-Edit');
		} else {
			if (OCMS.inPagePreview) {
				$('body').addClass('OCMS-Preview');
			}
		}
	};	// addAuthorClasses


	// remove hints intended only for the OCMS page editor from the DOM
	// note: requires that OCMS.addAuthorClasses has previously been called
	OCMS.removeEditorHints = function () {
		if (!OCMS.inPageEditor) {
			$('div.ocmsHint').remove();
		}
	};

	// ************************************************************************
	// OCMS.addBrowserClasses
	//
	// - add a browser-identifying class name
	// - when doing IE, always adds IEn class as well
	// - optionally, add only if the current browser engine corresponds to the if string in
	//		parameter object
	// - most frequently we only care about the travesty that is IE7
	// - use IE conditional comments instead of this JavaScript-based solution when possible
	// - note that Safari and Chrome both identify as WEBKIT
	// - for more detailed browser/version sniffing if necessary, do it yourself, use $.support
	//		or feature detection techniques
	// - examples
	//		- OCMS.addBrowserClasses()
	//				body will be have a browser-specific class added for all recognized browsers
	//		- OCMS.addBrowserClasses({selector: 'div.pg'})
	//				browser-specific class added to div.pg instead of body
	//		- OCMS.addBrowserClasses({onlyIf: 'IE'})
	//				IE and IEn classes added to body within IE, nothing for other browsers
	OCMS.addBrowserClasses = function (args) {
		var	settings = {
				selector: 'body',
				onlyIf: null			// default: we do it for all browsers
			},
			classList = '';

		$.extend(settings, args);
		if (settings.onlyIf) {
			settings.onlyIf = settings.onlyIf.toUpperCase();
		}

		if ($.browser.msie && (!settings.onlyIf || settings.onlyIf.slice(0, 1) === 'IE')) {
			classList = 'IE IE' + parseInt($.browser.version, 10);
		} else if ($.browser.webkit && (!settings.onlyIf || settings.onlyIf === 'WEBKIT')) {
			classList = 'WEBKIT';
		} else if ($.browser.mozilla && (!settings.onlyIf || settings.onlyIf === 'MOZILLA')) {
			classList = 'MOZILLA';
		} else if ($.browser.opera && (!settings.onlyIf || settings.onlyIf === 'OPERA')) {
			classList = 'OPERA';
		}

		if (classList.length !== 0) {
			$(settings.selector).addClass(classList);
		}
	}; // OCMS.addBrowserClasses

	OCMS.isIE7 = ($.browser.msie && parseInt($.browser.version, 10) === 7) || false;
	OCMS.isIE8 = ($.browser.msie && parseInt($.browser.version, 10) === 8) || false;

	/**************************************************************************
		- required by enableOrDisable_ValidEmail, but given broader exposure since others
			may need it
	*/
	OCMS.isValidEmailAddress = function (el) {
		var reEMail = OCMS.isValidEmailAddress.reEmail_ALTERNATE || OCMS.isValidEmailAddress.reEmail_DEFAULT;

		// if el is null, this: the email text field in question (invoked as a method)
		el = el || this;

		return reEMail.test($(el).val().trim());
	};

	// candidate regexp's
	// ^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$		-- original: assumes too much about valid chars
	// ^([^.@]+)(\.[^.@]+)*@([^.@]+\.)+([^.@]+)$			-- doesn't sanity check a,b,c in a@b.c
	// ^[^<>\s\@]+(\@[^<>\s\@]+(\.[^<>\s\@]+)+)$			-- forbids spaces, <, > in a,b,c
	// ^[^<>\s\@]+(\@[^<>\s\@\.]+\.([^<>\s\@\.]+)+)$		-- forbids spaces, <, > in a,b,c
	OCMS.isValidEmailAddress.reEmail_DEFAULT = /^[^<>\s\@]+\@[^<>\s\@\.]+\.[^<>\s\@\.]+$/;
	OCMS.isValidEmailAddress.reEmail_ALTERNATE = null;

	// pass a regular expression to use a different pattern for email validity checking
	// pass null to reset to default
	OCMS.setEmailAddressRE = function (re) {
		OCMS.isValidEmailAddress.reEmail_ALTERNATE = re;
		OCMS.consoleMsg('new email RE: ' + re);
	};


	/**************************************************************************
		- simple start of a safe console wrapper
		- TO DO!
		- NOT yet proven appropriate for all browsers!
	*/
	OCMS.consoleMsg = function () {

		// IE8 says Object doesn't support 'apply'
		//console.info.apply(console, Array.prototype.slice.call(arguments));
		//return;

		// okay for Chrome. What about Safari?
		if ($.browser.mozilla || $.browser.webkit) {
			console.info.apply(console, Array.prototype.slice.call(arguments));
		} else {
			console.info(Array.prototype.slice.call(arguments));
		}
	};	// OCMS.consoleMsg


	/**************************************************************************
		- provide nop functions for when these development functions aren't available
	*/
	if (typeof console === 'undefined') {
		console = {
			log:		function () {},
			info:		function () {},
			warning:	function () {},
			assert:		function () {}
		};
	}

	/**************************************************************************
		- track what control previously had the input focus
		- intentionally ignore command buttons
	*/
	OCMS.lastFocus = null;
	OCMS.trackLastFocus = function () {
		$('input[type="text"], input[type="radio"], input[type="checkbox"], select, textarea')
			.live('focusin', function (evt) {
			if (evt.type === 'focusin') {
				OCMS.lastFocus = this;
				//OCMS.consoleMsg('focusin ' + this.tagName + this.value);
			}
		});
	};

	/**************************************************************************
		- written for DenMat.com when standard mouse enter/leave events triggering
			failed or proved unreliable
		- note that parameter is a jQuery collection of arbitrary length, and the elements
			do not have to be visible now (but are expected to not move, shrink, or grow!)
	*/
	if (OCMS.boundsChecker === undefined) {
		OCMS.boundsChecker = function ($args) {
			var bounds = [];

			function _init () {
				function tlbr (el) {
					var $this = $(el);

					// if object isn't currently visible, set nulls but include object
					// in order to build tlbr co-ordinates later when asked about a point
					if ($this.is(':visible')) {
						return {
							t: $this.offset().top,
							l: $this.offset().left,
							b: $this.offset().top + $this.height(),
							r: $this.offset().left + $this.width()
						}
					} else {
						return {
							t: null,
							l: null,
							b: null,
							r: null,
							$that: $this
						}
					}
				}

				for (var i = 0; i < $args.length; ++i) {
					var reck = tlbr($args.eq(i));
					bounds.push(reck);
				}

//					dump();
			}

			function isInside (pt) {
				// pt must have pageX and pageY properties, as found in an event object
				var r,
					bInside = false,
					rect;

				for (r = 0; !bInside && r < bounds.length; ++r) {
					rect = bounds[r];

					if (rect.t !== null) {
						bInside = 	pt.pageX >= rect.l &&
									pt.pageX <= rect.r &&
									pt.pageY >= rect.t &&
									pt.pageY <= rect.b;
					} else {
						if (rect.$that.is(':visible')) {
							rect.t = rect.$that.offset().top;
							rect.l = rect.$that.offset().left;
							rect.b = rect.$that.offset().top + rect.$that.height();
							rect.r = rect.$that.offset().left + rect.$that.width();

							bInside = 	pt.pageX >= rect.l &&
										pt.pageX <= rect.r &&
										pt.pageY >= rect.t &&
										pt.pageY <= rect.b;
							dump();
						}
					}
				}

/*
				if (bInside) {
					window.status += '(' + pt.pageX + ' o ' + pt.pageY + ') ';
				} else {
					window.status += '(' + pt.pageX + ' x ' + pt.pageY + ') ';
				}
*/
				return bInside;
			}

			function isOutside (args) {
				return !isInside(args);
			}

			function dump () {
				var r;
				for (r = 0; r < bounds.length; ++r) {
					window.status += 	'{' + bounds[r].l + '->' + bounds[r].r +
										'|' + bounds[r].t + 'vv' + bounds[r].b + '} ';
				}
			}

			_init();

			return {
					isInside: isInside,
					isOutside: isOutside
			}
		}
	}	// OCMS.boundsChecker === undefined


	OCMS.constructPageURL = function (
		page,		// string: required, NOT expected to include a query string, therefore NOT
					//			appropriate for processing Orchestra created content links
		args,		// string: optional, omits leading ?, includes embedded & but not a leading &, assumed non-empty string
		site)		// string: optional, seldom required, but its ommission assumes availability of standard api vars
	{
		var cmsData = $(document).data('cms'),
			basePage = 'cms__Main',		// default: production
			pageString,			// = '?name=' + page,
			siteString,			//= (typeof site === 'string') ? '&sname=' + site : '',
			argString = (typeof args === 'string') ? '&' + args : '',
			queryStart;

		// remove leading / if present (presumably a URL was passed, not a page name)
		if (page[0] === '/') {
			page = page.slice(1);
		}
		pageString = '?name=' + page;

		if (typeof site !== 'string') {
			// if site left empty, grab it from in-page cms api vars (assumed to be present!)
			site = cmsData.site_name;
		}
		if (site.length) {
			siteString = '&sname=' + site;
		}

		if (cmsData.page_mode === 'prev') {
			basePage = 'preview';
		}

		return basePage + pageString + siteString + argString;
	};	// OCMS.constructPageURL


	// set by user content OCMS.pageOptions({opt1: value1, opt2: value2});
	// retrieved individually by page script OCMS.pageOptions('opt2');

	OCMS.pageOptions = function (arg) {
		// more to come!

		if (typeof arg === 'string') {
			// getter
			// returns null if not found
			OCMS.consoleMsg('OCMS.pageOptions(' + arg + ') => ' + OCMS.pageOptions.options[arg]);
			return OCMS.pageOptions.options[arg];
		} else {
			// setter call; passed an object map of proerty/value pairs
			OCMS.consoleMsg('OCMS.pageOptions [set] ' + arg);
			$.extend(true, OCMS.pageOptions.options, arg);
		}
	};
	OCMS.pageOptions.options = {};


})(jQuery);

