if (typeof com == "undefined") { com = {}; }
if (typeof com.digitaria == "undefined") { com.digitaria = {}; }

/**
 * Sets up the validator for the page.
 * Allows you to override most of the settings for the system.
 * You can also override when performing individual validations.
 *
 * <p>
 * The x of y system is complicated so I'm explaining it separately:
 * </p><p>
 * Say you have 3 ids, field1, field2, field3 that you need to ensure 
 * at least 2 of are valid but you're OK with the third being blank...
 * </p><p>
 * You would add the following class to each of them
 * validXofY2of3field1ORfield2ORfield3
 * </p><p>
 * validXofY is the prefix the system looks for
 * Two numbers xofy (2of3 above) tells it how many have to be valid.
 * </p><p>
 * Then the ids are specified afterwards, separated by ORs
 * </p><p>
 * If you wanted to check email1 or email2 was populated but didn't care which:
 * validXofY1of2email1ORemail2
 * </p>
 *
 *
 * @returns {void}
 * @param {hash} settings Pass any settings overrides you wish through here.
 *
 * @param {string} settings.classValidate This is the class to check for, on form elements, that indicates they should be validated. With this class alone, they will be validated as non "".
 *
 * @param {string} settings.classEmail This is the class to check for, on form elements, when you want to validate them as email addresses.
 * @param {string} settings.classURL This is the class to check for, on form elements, when you want to validate them as a URL.
 * @param {string} settings.className This is the class to check for, on form elements, when you want to validate them as names.
 * @param {string} settings.classZip This is the class to check for, on form elements, when you want to validate them as zip codes.
 * @param {string} settings.classPhone This is the class to check for, on form elements, when you want to validate them as 10 digit phone numbers. (Must have at least 10 digits. Can have any combination of any other characters so long as there are at least 10 digits. So 123-456-7890, 123 4567890, etc. are all valid but lotsofjunk123-4567 isn't as it has too few digits)
 *
 * @param {string} settings.classDate This is the class to check for, on form elements, when you want to validate them as having a valid mm/dd/yyyy date. It will accept single digit days and months and two digit years but will still check the end result to make sure it's a date that actually existed (i.e. no 2/30/2010).
 * @param {string} settings.classMinAgePrefix You can specify any minimum age by using this field followed by a number of years. Assuming the default validMinAge prefix, validMinAge18 would require the field to be filled in with a date from at least 18 years ago. Note: You can use negative values. So validMinAge-2 would let you enter dates UP TO two years in the future. Note2: Because a valid date is required, this automatically performs the validDate check.
  * @param {string} settings.classMaxAgePrefix You can specify any maximum age by using this field followed by a number of years. Assuming the default validMaxAge prefix, validMaxAge18 would require the field to be filled in with a date no greater than 18 years ago (say to ensure only children can enter a competition for children). Note: You can use negative values. So validMaxAge-2 would let you enter dates NO MORE RECENT THAN two years in the future. You can use this for validMaxAge0 to ensure credit card expiry dates haven't already expired. Note2: Because a valid date is required, this automatically performs the validDate check.
 *
 * @param {string} settings.classUpper This is the class to check for, on form elements, when you want to validate them as having at least one upper case character in them.
 * @param {string} settings.classLower This is the class to check for, on form elements, when you want to validate them as having at least one lower case character in them.
 * @param {string} settings.classNumber This is the class to check for, on form elements, when you want to validate them as having at least one number in them.
 * @param {string} settings.classSymbol This is the class to check for, on form elements, when you want to validate them as having at least one punctuation symbol in them.
 *
 * @param {string} settings.classMatchPrefix This is the prefix of a class. For example, if you want to check a field MATCHES the field with id MyField, you would add the class [settings.classMatchPrefix]MyField - i.e. validMatchMyField with the default value. This can be useful for checking that a confirm email address matches the first entered email address or both versions of a password match.
 *
 * @param {string} settings.classNonMatchPrefix This is the prefix of a class. For example, if you want to check a field DOES NOT MATCH the field with id MyField, you would add the class [settings.classMatchPrefix]MyField - i.e. validNonMatchMyField with the default value. This can be useful for ensuring an alternate email address is different to the primary email address.
 *
 * @param {string} settings.classLengthPrefix This is the prefix of a class. For example, if you want to check a field has a minimum length of 8 characters, you would add the class [settings.classMatchPrefix]8 - i.e. validLength8 with the default value.
 *
 * @param {string} settings.classError This is the class that gets added to fields, their rows and the form, when there're errors.
 *
 * @param {string} settings.expressionParent This is a CSS expression that's used to match parent elements of the field that doesn't validate, to then apply the error class to them. With the default value of .formRow, it will look for all .formRow parents of the field and apply settings.classError to them.
 *
 * @param {boolean} revCountsAsEmpty If this is true and the field has a rev value, if the field's value matches the rev value, it'll also count as empty. This way, we can pass in default values that we want to also count as empty.
 *
 * @author Nick Davison
 */
com.digitaria.validator=function(settings) {
	this.settings=jQuery.extend({
		classValidate: "validate",
		
		classEmail: "validEmail",
		classURL: "validURL",
		className: "validName",
		classUsername: 'validUsername',
		classZip: "validZip",
		classPhone: "validPhone",
		classDate: "validDate",
		classMinAgePrefix: "validMinAge",
		classMaxAgePrefix: "validMaxAge",
		classLower: "validLower",
		classUpper: "validUpper",
		classNumber: "validNumber",
		classSymbol: "validSymbol",
		
		classMatchPrefix: "validMatch",
		classNonMatchPrefix: "validNonMatch",
		
		classLengthPrefix: "validLength",
		
		classXofYPrefix: "validXofY",
		
		classError: "validatorError",
		
		expressionParent: '.formRow',
		
		revCountsAsEmpty: false
	}, settings);
	
	this.checkEmail = new RegExp(/^([\w-]+\.)*?[\w-]+@[\w-]+\.([\w-]+\.)*?[\w]+$/);
	this.checkURL = new RegExp(/https?:\/\/([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?/);
	this.checkName = new RegExp(/^[a-zA-Z\'\-\s\.]{1,35}$/);
	this.checkUsername = new RegExp(/^[a-zA-Z0-9_\'\-\s\.\?\+!#@]{8,25}$/);
	this.checkZip = new RegExp(/^(\d{5}-\d{4}|\d{5}|\d{9})$|^([a-zA-Z]\d[a-zA-Z] \d[a-zA-Z]\d)$/);
	this.checkPhone = new RegExp(/(\d+.*){10,}/);
	this.checkDate = new RegExp(/\d{1,2}\/\d{1,2}\/\d{2,4}/);
	this.checkUpper = new RegExp(/[A-Z]+/);
	this.checkLower = new RegExp(/[a-z]+/);
	this.checkNumber = new RegExp(/\d+/);
	this.checkSymbol = new RegExp(/[!\"#$%&'()*+,./:;<=>?@\\^_\[\]`{|}~-]+/);
};

com.digitaria.validator.prototype.getClassSuffix=function(element, prefix) {
	/* Ensure we have a non jQueried element. */
	var $_element=jQuery(element);
	var _element=$_element.get(0);
	
	/* Search to see if the prefix exists anywhere in the class string */
	if (_element.className.toString().indexOf(prefix)!=-1) {
		// If it does, break out all of the classes
		var classes=_element.className.toString().split(" ");
		// Go through the classes
		for (var i=0; i<classes.length; i++) {
			// If the class starts with the prefix
			if (classes[i].indexOf(prefix)===0) {
				// Grab everything after the prefix
				var match=classes[i].substring(prefix.toString().length);
				return match;
			}
		}
	}
	
	/* Return null if there was no match */
	return null;
};

/**
 * Add the error class to a field (and its appropriate parents).
 * @returns {void}
 * @param {jQuery} field The field we're applying to.
 * @param {hash} settings Allows overriding of the default settings.
 * @author Nick Davison
 */
com.digitaria.validator.prototype.showError=function(field, settings) {
	// Allow settings to be overridden
	settings=jQuery.extend(this.settings, settings);
		
	// Ensure the field is in jQuery form
	field=jQuery(field);
	
	// Add the error class to the element itself
	field.addClass(settings.classError);

	// Add the error class to any parent elements that matcht the expressionParent setting
	field.parents(settings.expressionParent).addClass(settings.classError);
};

/**
 * Remove the error class to a field (and its appropriate parents).
 * @returns {void}
 * @param {jQuery} field The field we're applying to.
 * @param {hash} settings Allows overriding of the default settings.
 * @author Nick Davison
 */
com.digitaria.validator.prototype.hideError=function(field, settings) {
	// Allow settings to be overridden
	settings=jQuery.extend(this.settings, settings);
		
	// Ensure the field is in jQuery form
	field=jQuery(field);
	
	// Remove the error class to the element itself
	field.removeClass(settings.classError);
	
	// Remove the error class to any parent elements that matcht the expressionParent setting
	field.parents(settings.expressionParent).removeClass(settings.classError);
};

/**
 * Perform validation of a form.
 * 
 * <p>It will go through all elements with class [settings.classValidate] that are either an input, select or textarea.
 * Each one is then checked for classes to indicate what types of validation should be performed.
 * If successfully validated, any prior errors will be stripped via hideError().
 * If unsuccessfully validated, the [settings.classError] will be applied via showError().
 * If ANY elements were unsuccessfully validated, the form will gain the error class and we return false.
 * Otherwise, the form is stripped of the error class and we return true.</p>
 *
 * @returns {boolean}
 * @param {jQuery} form The form we're validating.
 * @param {hash} settings Allows overriding of the default settings.
 * @author Nick Davison
 */
com.digitaria.validator.prototype.validate=function(form, settings) {
	var _thisValidator=this;
	
	// Ensure the form is jQuery friendly
	form=jQuery(form);
	
	// Allow settings to be overridden
	settings=jQuery.extend(this.settings, settings);
	
	// Store whether the form validates or not.
	var isValid=true;
	
	// Find all elements with the validate class within the form
	jQuery('.'+settings.classValidate, form).each(function() {
		var $_this=jQuery(this);
		
		// Only perform checks on inputs, selects, textareas
		if ($_this.is("input, select, textarea")) {
			
			var thisIsValid=true;
														  
			// Handle everything other than checkboxes
			if (!$_this.is(':checkbox')) {
				var val=$_this.val(); // Get the current value
				
				if (val==="" || val == null) { // Check for empty strings
					// Find out if there is an X of Y class assigned
					var XofYSuffix=_thisValidator.getClassSuffix(this, settings.classXofYPrefix);
					// If not, being empty is never OK
					if (XofYSuffix==null) {
						thisIsValid=false;
					} else {
						// Split either side of the "of"
						var parts=XofYSuffix.split("of");
						if (parts.length<2) {
							thisIsValid=false;
						} else {
							try {
								// Get the X and Y values
								var x=parseInt(parts[0], 10);
								var y=parseInt(parts[1], 10);
								// Get the list of IDs
								var xOfyString=x+'of'+y;
								var ids=XofYSuffix.substring(xOfyString.length);
								parts=ids.split("OR");
								// Count how many of them are non empty
								var numNonEmpty=0;
								for (var idCount=0; idCount<parts.length; idCount++) {
									if (jQuery('#'+parts[idCount]).val()!=="") {
										numNonEmpty++;
									}
								}
								// If too few of them are empty, fail
								if (numNonEmpty<x) {
									thisIsValid=false;
								} 
							} catch(e) {
								thisIsValid=false;
							}
						}
					}
				} else if ((settings.revCountsAsEmpty) && (val==$_this.attr('rev'))) { // If revCountsAsEmpty is true and the value is the same as the rev
					thisIsValid=false;									
				} else {
					// Check for emails
					if ($_this.is('.'+settings.classEmail)) {
						if (!_thisValidator.checkEmail.test(val)) { thisIsValid=false; }
					}
					// Check for urls
					if ($_this.is('.'+settings.classURL)) {
						if (!_thisValidator.checkURL.test(val)) { thisIsValid=false; }
					}
					// Check for Username
					if ($_this.is('.'+settings.classUsername)) {
						if (!_thisValidator.checkUsername.test(val)) { thisIsValid=false; }
					}
					// Check for names
					if ($_this.is('.'+settings.className)) {
						if (!_thisValidator.checkName.test(val)) { thisIsValid=false; }
					}
					// Check for zips
					if ($_this.is('.'+settings.classZip)) {
						if (!_thisValidator.checkZip.test(val)) { thisIsValid=false; }
					}
					// Check for phone numbers
					if ($_this.is('.'+settings.classPhone)) {
						if (!_thisValidator.checkPhone.test(val)) { thisIsValid=false; }
					}
					// Check for uppercase
					if ($_this.is('.'+settings.classUpper)) {
						if (!_thisValidator.checkUpper.test(val)) { thisIsValid=false; }
					}
					// Check for lowercase
					if ($_this.is('.'+settings.classLower)) {
						if (!_thisValidator.checkLower.test(val)) { thisIsValid=false; }
					}
					// Check for numbers
					if ($_this.is('.'+settings.classNumber)) {
						if (!_thisValidator.checkNumber.test(val)) { thisIsValid=false; }
					}
					// Check for symbols
					if ($_this.is('.'+settings.classSymbol)) {
						if (!_thisValidator.checkSymbol.test(val)) { thisIsValid=false; }
					}
					
					// Check for dates
					var minAge=_thisValidator.getClassSuffix(this, settings.classMinAgePrefix);
					var maxAge=_thisValidator.getClassSuffix(this, settings.classMaxAgePrefix);
					if ( ($_this.is('.'+settings.classDate)) || (minAge!==null) || (maxAge!==null) ) {
						
						/* Do a fast, basic regexp check for broadly valid dates */
						if (!_thisValidator.checkDate.test(val)) { thisIsValid=false; }
						
						// Make sure it's a real date
						try {
							var now=new Date();
							
							var parts=val.split("/");
							var m=parseInt(parts[0], 10)-1;
							var d=parseInt(parts[1], 10);
							var y=parseInt(parts[2], 10);
							/* Convert two digits that're lower than or the same as the current year in to 2000+ dates */
							if (y<=(now.getFullYear()-2000)) {
								y+=2000;
							}
							/* Convert any remaining two digit numbers to 1900+ dates */
							if (y<100) { y+=1900; } // Convert to 4 digit
							var newDate=new Date(y, m, d);
							
							/* Throw out if any of the dates components don't add up
							   i.e. 2/30 will convert to 3/1 or 3/2 depending on the year */
							if (newDate.getDate()!=d) { thisIsValid=false; }
							if (newDate.getMonth()!=m) { thisIsValid=false; }
							if (newDate.getFullYear()!=y) { thisIsValid=false; }
							
							/* Check for ages younger than the min age */
							minAge=parseInt(minAge, 10);
							newDate=new Date(y+minAge, m, d);
							if (newDate.getTime()>now.getTime()) { thisIsValid=false; }
							
							/* Check for ages older than the max age */
							maxAge=parseInt(maxAge, 10);
							newDate=new Date(y+maxAge, m, d);
							if (newDate.getTime()<now.getTime()) { thisIsValid=false; }
							
						} catch(e) {
							/* Couldn't parse the date correctly, not a valid date */
							thisIsValid=false;
						}
					}
					
					var matchId=_thisValidator.getClassSuffix(this, settings.classMatchPrefix);
					if (matchId!==null) {
						if (jQuery('#'+matchId).val()!=val) {
							// The field fails
							thisIsValid=false;
						}
					}
					
					var nonMatchId=_thisValidator.getClassSuffix(this, settings.classNonMatchPrefix);
					if (nonMatchId!==null) {
						if (jQuery('#'+nonMatchId).val()==val) {
							// The field fails
							thisIsValid=false;
						}
					}
					
					var minLength=_thisValidator.getClassSuffix(this, settings.classLengthPrefix);
					if (minLength!==null) {
						try {
							var lengthInt=parseInt(minLength, 10);
							if ($_this.val().toString().length<lengthInt) {
								thisIsValid=false;
							}
						} catch(err) {
						}
					}
				}
			} else { // Handle checkboxes - all they get is checking they're checked.
				if (!this.checked) {
					thisIsValid=false;
				}
			}
			
			if (thisIsValid) { // If the field's valid, show it as valid
				_thisValidator.hideError($_this, settings);
			} else { // If the field's invalid, show it as invalid and invalidate the form
				_thisValidator.showError($_this, settings);
				isValid=false;
			}
		}
	});
	
	// Depending on whether the whole form had any errors or no errors, add or remove classError from the form itself.
	if (isValid) {
		form.removeClass(settings.classError);
	} else {
		form.addClass(settings.classError);
	}
	
	// Return whether the form validated or not
	return isValid;
};

com.digitaria.initRevFieldDefaults=function() {
	jQuery('input[rev], textarea[rev]').blur(function() {
		if (jQuery(this).val()==="") { /* Set the rev on blur */
			jQuery(this).val(jQuery(this).attr('rev'));
			jQuery(this).addClass("inactive");
			jQuery(this).removeClass("active");
		}
	 }).focus(function() { /* Clear the rev on focus */
		 if (jQuery(this).val()===jQuery(this).attr('rev')) {
			jQuery(this).val('');
			jQuery(this).addClass("active");
			jQuery(this).removeClass("inactive");
		 }
	 }).each(function() { /* Initialize, setting any blanks to the revs */
		 if ( (jQuery(this).val()==="") || (jQuery(this).val()===jQuery(this).attr('rev')) ) {
			jQuery(this).val(jQuery(this).attr('rev'));
			jQuery(this).addClass("inactive");
			jQuery(this).removeClass("active");
		}
	 });
};
