﻿JHCMS = window.JHCMS ||
{};

JHCMS.Validation = (function($)
{
	var jqEventNamespace = '.validation';
	var rulesList = {};
	var splitter = '__5pl1773r__';
	var events = {
		onBeforeValidation: "",
		add: function(ev, f)
		{
			if (typeof ev != 'string' || ev == 'add')
			{
				return;
			}

			if (typeof events[ev] != 'function')
			{
				events[ev] = f;
			}
			else
			{
				events[ev] = function()
				{
					events[ev]();
					f();
				};
			}
		}
	};

	/*
	Allow for specification of a customised error message handler. Receives a
	messages object as created by eventHandler(). If it returns false, cancels
	any default handling.
	 
	Bear in mind that your custom message handler should not alter the messages
	object if it intends to allow default handling.
	*/
	var customMessageHandler;

	/*
	core holds the basic validation functions which rely only on their own
	inputs, with no knowledge of the document. This is publicised and can be used
	anywhere.
	*/
	var core = {};

	/*
	generic holds the namespaced functions that can be supplied as isValid
	parameters.
	*/
	var generic = {};

	var strengths = ['enforce', 'suggest'];
	var overrides = ['transform', 'silence'];

	function compareOverrides(a, b)
	{
		function indexOf(arr, value)
		{
			for (var i = 0; i < arr.length; i++)
			{
				if (arr[i] == value)
				{
					return i;
				}
			}
			return -1;
		}

		if (indexOf(overrides, a.type) < indexOf(overrides, b.type))
		{
			return -1;
		}
		if (indexOf(overrides, a.type) > indexOf(overrides, b.type))
		{
			return 1;
		}
		return 0;
	}

	function extend(fns, ns)
	{
		/*
		fns is an object containing core:{} and/or generic:{} objects, which are
		used to extend the set of available validation functions under the given
		namespace ns (or 'base' by default). Anything added will be accessible
		publically.
		*/
		if (ns === undefined)
		{
			ns = 'base';
		}

		if (fns.core)
		{
			if (core[ns] === undefined)
			{
				core[ns] = {};
			}
			jQuery.extend(core[ns], fns.core);
		}

		if (fns.generic)
		{
			if (generic[ns] === undefined)
			{
				generic[ns] = {};
			}
			$.extend(generic[ns], fns.generic);
		}
	}

	function createEventHandler(rules)
	{
		/*
		createEventHandler() returns an anonymous function which will have the
		object "rules" bound by closure from the parameter of createEventHandler()
		This is necessary so that we can allow the call to createEventHandler() to
		be associated with an event and still return a function call with its
		parameter "rules" already initialized.
		 
		The anonymous function returned by createEventHandler() becomes the event
		handler for a DOM node, and runs all the validation functions stored in
		"rules", with appropriate resulting messages and functions.
		"rules" is an array of "rule" objects. These objects can expose one or more
		of the following properties:
		{
		strength: '[enforce|suggest]',
		isValid: <validation_function_ref or string>,
		parameters: { namedParams[1..n] },
		onSuccess: <function_ref>,
		onFailure: <function_ref>,
		message: ''
		}
		 
		The parameters object needs to be customised based on the requirements of
		the particular function used for isValid, eg. generic.base.isTextFilled()
		just requires { field: 'field_id' }, but generic.base.minimumTextLength()
		requires { field: 'field_id', minLen: <int> }.
		 
		*/
		return function eventHandler()
		{
			events.onBeforeValidation();

			var i, isValid, isValidNS, finalOutcome = true;
			var overrideRules = [];
			var successFunctions = {}, failureFunctions = {}, failureMessages = {};
			var finalSuccessFunctions = {}, finalFailureFunctions = {}, finalFailureMessages = {};

			for (i = strengths.length - 1; i >= 0; i--)
			{
				successFunctions[strengths[i]] = [];
				failureFunctions[strengths[i]] = [];
				failureMessages[strengths[i]] = [];
			}

			for (i = 0; i < rules.length; i++)
			{
				if (typeof rules[i].isValid == 'string')
				{
					isValidNS = rules[i].isValid.split('.');
					if (isValidNS.length == 1)
					{
						isValidNS.unshift('base');
					}
					isValid = generic[isValidNS[0]][isValidNS[1]];
				}
				else
				{
					isValid = rules[i].isValid;
				}

				if (isValid === undefined || isValid(rules[i].parameters))
				{
					if (rules[i].override)
					{
						overrideRules.push({
							type: rules[i].override,
							strength: rules[i].strength,
							options: rules[i].overrideOptions
						});
					}
					if (rules[i].onSuccess)
					{
						successFunctions[rules[i].strength].push(rules[i].onSuccess);
					}
				}
				else
				{
					finalOutcome = false;
					if (rules[i].message)
					{
						failureMessages[rules[i].strength].push({
							message: rules[i].message,
							parameters: rules[i].parameters
						});
					}
					if (rules[i].onFailure)
					{
						failureFunctions[rules[i].strength].push(rules[i].onFailure);
					}
				}
			}

			// check override list
			// perform TRANSFORM before SILENT
			overrideRules.sort(compareOverrides);

			for (i = strengths.length - 1; i >= 0; i--)
			{
				finalSuccessFunctions[strengths[i]] = successFunctions[strengths[i]].slice(0);
				finalFailureFunctions[strengths[i]] = failureFunctions[strengths[i]].slice(0);
				finalFailureMessages[strengths[i]] = failureMessages[strengths[i]].slice(0);
			}

			var transformFrom, transformTo;
			var silence = {};
			for (i = 0; i < overrideRules.length; i++)
			{
				switch (overrideRules[i].type)
				{
					case 'transform':
						transformFrom = overrideRules[i].strength;
						transformTo = overrideRules[i].options.to;
						silence[transformTo] = false;
						finalSuccessFunctions[transformTo] = successFunctions[transformTo].concat(successFunctions[transformFrom]);
						finalFailureFunctions[transformTo] = failureFunctions[transformTo].concat(failureFunctions[transformFrom]);
						finalFailureMessages[transformTo] = failureMessages[transformTo].concat(failureMessages[transformFrom]);
						// fallthrough, to silence the "from" list
					case 'silence':
						silence[overrideRules[i].strength] = true;
						finalSuccessFunctions[overrideRules[i].strength] = [];
						finalFailureFunctions[overrideRules[i].strength] = [];
						finalFailureMessages[overrideRules[i].strength] = [];
						break;
					default:
						// nothing to do here
				}
			}

			var allSilenced = true;
			for (i = 0; i < strengths.length; i++)
			{
				allSilenced = allSilenced && (finalFailureMessages[strengths[i]].length === 0 || silence[strengths[i]] === true);
			}

			if (allSilenced)
			{
				finalOutcome = true;
			}

			// process as necessary, yielding to other code
			/*
			setTimeout(processFunctions, 0, finalSuccessFunctions);
			setTimeout(processFunctions, 0, finalFailureFunctions);
			setTimeout(processMessages, 0, finalFailureMessages);
			*/
			/*
			IE requires a different approach, as it cannot handle passing the extra parameters to the function
			Cannot use try/catch, as the error occurs in the postponed function, therefore every browser gets
			the same over-complicated version:
			*/
			setTimeout((function(funcList)
			{
				return function()
				{
					processFunctions(funcList);
				};
			})(finalSuccessFunctions), 0);
			setTimeout((function(funcList)
			{
				return function()
				{
					processFunctions(funcList);
				};
			})(finalFailureFunctions), 0);
			setTimeout((function(msgList)
			{
				return function()
				{
					processMessages(msgList);
				};
			})(finalFailureMessages), 0);

			// return here to cancel event (onsubmit, for example)
			return finalOutcome;
		};
	}

	function processFunctions(functions)
	{
		var i, fLen;
		$.each(functions, function(strength, funcs)
		{
			fLen = funcs.length;
			for (i = 0; i < fLen; i++)
			{
				setTimeout(funcs[i], 0);
			}
		});
	}

	function processMessages(messages)
	{
		if (!customMessageHandler || (customMessageHandler && customMessageHandler(messages)))
		{
			var messageHtml = '';
			$.each(messages, function(key, val)
			{
				if (messages[key].length)
				{
					messages[key] = jQuery.map(messages[key], function(item, i)
					{
						return '<li>' + item.message + '</li>';
					});
					messageHtml += '<div id="' + key + '_message"><ul>' + messages[key].join('') + '</ul>';
					if (key == 'suggest' && document.forms[0])
					{
						messageHtml += '<input type="button" id="ignore-suggest" value="Bypass" onclick="JHCMS.Validation.register(document.forms[0],\'submit\',{strength:\'suggest\',override:\'silence\'})" />';
					}
					messageHtml += '</div>';
				}
			});
			$('#jh_validation').html(messageHtml).addClass('refreshed');
			$('html, body').animate({ scrollTop: 0 }, 'slow');
			setTimeout(function()
			{
				$('#jh_validation').removeClass('refreshed');
			}, 5000);
		}
	}

	function setCustomMessageHandler(fn)
	{
		customMessageHandler = fn;
	}

	function register(element, eventName, ruleObject)
	{
		/*
		This function populates the rulesList objects array of a particular event and
		then binds the result of the call to createEventHandler to the event.
		 
		eventName must be from the set of permissable jQuery event names:
		blur, focus, load, resize, scroll, unload, beforeunload, click, dblclick,
		mousedown, mouseup, mousemove, mouseover, mouseout, mouseenter, mouseleave,
		change, select,  submit, keydown, keypress, keyup, error
		*/
		var r, ruleId, jqEventName, elementId;

		if (typeof element == 'object' && element.id)
		{
			elementId = element.id;
		}
		else
		{
			elementId = element.toString();
		}

		ruleId = elementId + splitter + eventName;
		ruleObject.strength = ruleObject.strength || 'enforce';

		if (rulesList[ruleId] === undefined)
		{
			rulesList[ruleId] = [];
		}

		r = rulesList[ruleId];
		r.push(ruleObject);

		bindRules(eventName, elementId, r);
	}

	function bindRules(eventName, elementId, rulesArray)
	{
		unbindRules(eventName, elementId);
		$('#' + elementId).bind(jqEventName, createEventHandler(rulesArray));
	}
	function unbindRules(eventName, elementId)
	{
		jqEventName = eventName + jqEventNamespace;
		$('#' + elementId).unbind(jqEventName);
	}

	function silenceAll()
	{
		var i;
		for (i = 0; i < strengths.length; i++)
		{
			register(document.forms[0], 'submit', {
				strength: strengths[i],
				override: 'silence'
			});
		}
	}

	function disableAll()
	{
		var rule, elementId;
		for (rule in rulesList)
		{
			if (rulesList.hasOwnProperty(rule))
			{
				elementId = rule.split(splitter)[0];
				$('#' + elementId).unbind(jqEventNamespace);
			}
		}
	}

	function enableAll()
	{
		var rule, elementIdAndEventName;
		for (rule in rulesList)
		{
			if (rulesList.hasOwnProperty(rule))
			{
				elementIdAndEventName = rule.split(splitter);
				bindRules(elementIdAndEventName[1], elementIdAndEventName[0], rulesList[rule]);
			}
		}
	}


	// return public methods/properties
	return {
		core: core,
		generic: generic,
		extend: extend,
		register: register,
		silenceAll: silenceAll,
		disableAll: disableAll,
		enableAll: enableAll,
		setCustomMessageHandler: setCustomMessageHandler,
		addEvent: events.add
	};

})(jQuery);

jQuery(function()
{
	jQuery('.silence_validation').click(JHCMS.Validation.silenceAll);
	JHCMS.Validation.addEvent('onBeforeValidation', function()
	{
		if (typeof tinyMCE != 'undefined') 
		{
			tinyMCE.triggerSave();
		}
	});
});

/* *******************************
 BASE VALIDATION FUNCTIONS
 ********************************/
/*
 Base core validation functions.
 These should not be dependent on knowing about the DOM, they should be able to
 return true or false simply on the basis of their inputs. This enables the
 generic functions to access them permanently.
 */
JHCMS.Validation.extend({
	core: {
		isEmptyString: function(str)
		{
			return str === '';
		},
		isAtLeastMinLength: function(o, min)
		{
			if (o.length !== undefined) 
			{
				return o.length >= min;
			}
			return false;
		},
		isAtMostMaxLength: function(o, max)
		{
			if (o.length !== undefined) 
			{
				return o.length <= max;
			}
			return false;
		},
		isBetweenLength: function(o, min, max)
		{
			return JHCMS.Validation.core.base.isAtLeastMinLength(o, min) && JHCMS.Validation.core.base.isAtMostMaxLength(o, max);
		},
		areMatchingStrings: function(str1, str2)
		{
			return str1 === str2;
		},
		isEmailAddress: function(str)
		{
			var filter = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
			return filter.test(str);
		}
	}
});

/*
 Base generic validation functions.
 These can know about the DOM, and how to retrieve values from DOM nodes that
 will be passed on to simple validation functions that exist in the public
 validation core.
 */
JHCMS.Validation.extend({
	generic: {
		isTextFilled: function(parameters)
		{
			var text = document.getElementById(parameters.field).value;
			return !JHCMS.Validation.core.base.isEmptyString(text);
		},
		minimumTextLength: function(parameters)
		{
			var text = document.getElementById(parameters.field).value;
			return JHCMS.Validation.core.base.isAtLeastMinLength(text, parameters.minLen);
		},
		maximumTextLength: function(parameters)
		{
			var text = document.getElementById(parameters.field).value;
			return JHCMS.Validation.core.base.isAtMostMaxLength(text, parameters.maxLen);
		},
		rangeOfTextLength: function(parameters)
		{
			var text = document.getElementById(parameters.field).value;
			return JHCMS.Validation.core.base.isBetweenLength(text, parameters.minLen, parameters.maxLen);
		},
		minimumOptionsLength: function(parameters)
		{
			var opts = document.getElementById(parameters.field).options;
			return JHCMS.Validation.core.base.isAtLeastMinLength(opts, parameters.minLen);
		},
		maximumOptionsLength: function(parameters)
		{
			var opts = document.getElementById(parameters.field).options;
			return JHCMS.Validation.core.base.isAtMostMaxLength(opts, parameters.maxLen);
		},
		rangeOfOptionsLength: function(parameters)
		{
			var opts = document.getElementById(parameters.field).options;
			return JHCMS.Validation.core.base.isBetweenLength(opts, parameters.minLen, parameters.maxLen);
		},
		minimumSelectedOptionsLength: function(parameters)
		{
			var opts = document.getElementById(parameters.field).options;
			var selOpts = [];
			for (var i = 0; i < opts.length; i++)
			{
				if (opts[i].selected)
				{
					selOpts.push(opts[i]);
				}
			}
			return JHCMS.Validation.core.base.isAtLeastMinLength(selOpts, parameters.minLen);
		},
		maximumSelectedOptionsLength: function(parameters)
		{
			var opts = document.getElementById(parameters.field).options;
			var selOpts = [];
			for (var i = 0; i < opts.length; i++)
			{
				if (opts[i].selected)
				{
					selOpts.push(opts[i]);
				}
			}
			return JHCMS.Validation.core.base.isAtMostMaxLength(selOpts, parameters.maxLen);
		},
		rangeOfSelectedOptionsLength: function(parameters)
		{
			var opts = document.getElementById(parameters.field).options;
			var selOpts = [];
			for (var i = 0; i < opts.length; i++)
			{
				if (opts[i].selected)
				{
					selOpts.push(opts[i]);
				}
			}
			return JHCMS.Validation.core.base.isBetweenLength(selOpts, parameters.minLen, parameters.maxLen);
		},
		selectedOptionHasValue: function(parameters)
		{
			var select = document.getElementById(parameters.field);
			var value = select[select.selectedIndex].value;
			return !JHCMS.Validation.core.base.isEmptyString(value);
		},
		isChecked: function(parameters)
		{
			var elm = document.getElementById(parameters.field);
			return elm.checked;
		},
		isNotChecked: function(parameters)
		{
			var elm = document.getElementById(parameters.field);
			return !elm.checked;
		},
		textValuesMatch: function(parameters)
		{
			var elm1text = document.getElementById(parameters.field1).value;
			var elm2text = document.getElementById(parameters.field2).value;
			return JHCMS.Validation.core.base.areMatchingStrings(elm1text, elm2text);
		},
		isEmailAddress: function(parameters)
		{
			var text = document.getElementById(parameters.field).value;
			return JHCMS.Validation.core.base.isEmailAddress(text);
		},
		isRadioSelected: function(parameters)
		{
			var n, inputList, result = false, nodes = [];
			if (parameters.radioName && parameters.radioName !== '')
			{
				if (document.querySelectorAll)
				{
					nodes = document.querySelectorAll('input[name=' + parameters.radioName + ']');
				}
				else
				{
					inputList = document.getElementsByTagName('input');
					for (n = 0; n < inputList.length; n++)
					{
						if (inputList[n].getAttribute('name') == parameters.radioName)
						{ nodes.push(inputList[n]); }
					}
				}
			}

			if (parameters.radioIdList && nodes.length == 0)
			{
				for (n = 0; n < radioIdList.length; n++)
				{
					nodes.push(document.getElementById(parameters.radioIdList[n]));
				}
			}

			for (n = 0; n < nodes.length; n++)
			{
				if (nodes[n])
				{
					result = result || JHCMS.Validation.generic.base.isChecked({ field: nodes[n].id });
				}
			}
			return result;
		}
	}
});

/* Extensions for the GenericAssociation module */
JHCMS.Validation.extend({
	generic: {
		gaMinimumOptionsLength: function(parameters)
		{
			var opts = jQuery('#' + parameters.field + ' .target').get(0).options;
			return JHCMS.Validation.core.base.isAtLeastMinLength(opts, parameters.minLen);
		},
		gaMaximumOptionsLength: function(parameters)
		{
			var opts = jQuery('#' + parameters.field + ' .target').get(0).options;
			return JHCMS.Validation.core.base.isAtMostMaxLength(opts, parameters.maxLen);
		},
		gaRangeOfOptionsLength: function(parameters)
		{
			var opts = jQuery('#' + parameters.field + ' .target').get(0).options;
			return JHCMS.Validation.core.base.isBetweenLength(opts, parameters.minLen, parameters.maxLen);
		},
		llMinimumSelectedCheckboxes: function(parameters)
		{
			var items = jQuery('#' + parameters.field + ' input[type=checkbox]').get();
			var selItems = [];
			for (var i = 0; i < items.length; i++) 
			{
				if (items[i].checked) 
				{
					selItems.push(items[i]);
				}
			}
			return JHCMS.Validation.core.base.isAtLeastMinLength(selItems, parameters.minLen);
		},
		llMaximumSelectedCheckboxes: function(parameters)
		{
			var items = jQuery('#' + parameters.field + ' input[type=checkbox]').get();
			var selItems = [];
			for (var i = 0; i < items.length; i++) 
			{
				if (items[i].checked) 
				{
					selItems.push(items[i]);
				}
			}
			return JHCMS.Validation.core.base.isAtMostMaxLength(selItems, parameters.maxLen);
		},
		llRangeOfSelectedCheckboxes: function(parameters)
		{
			var items = jQuery('#' + parameters.field + ' input[type=checkbox]').get();
			var selItems = [];
			for (var i = 0; i < items.length; i++) 
			{
				if (items[i].checked) 
				{
					selItems.push(items[i]);
				}
			}
			return JHCMS.Validation.core.base.isBetweenLength(items, parameters.minLen, parameters.maxLen);
		}
	}
});




