/**
 * Tests used in ILR form validation routine (can also be used independently, thus they're not private
 * methods).
 * @namespace ILR.form.validation
 * @module util
 * @requires codelib/js/ilr/datatypes/String.js
 * @author Rebecca Younes
 */

ILR.namespace("form.validation.util");

ILR.form.validation.util = {

  /**
   * @description Punctuation symbols used in dates to separate month, day, and year values
   * @property _date_punct
   * @private  
   * @final
   * @type String
   */
	_date_punct : /[\.\-\/]/,


  /**
   * @description Determine whether a set of radio or checkbox elements has an
   * option selected
   * @method hasOptionSelected  
   * @param {HTMLElement|String} formName The name of the form containing the radio/checkbox elements
   * @param {String} elementName The name of the radio/checkbox elements
   * @return {Boolean} True iff an option is selected
   */
	hasOptionSelected : function(form, elementName) {
		
		var i,
			optionCount;

		if (YAHOO.lang.isString(form))	{
			form = YAHOO.Util.Dom.get(form);
		}
		
		// Singleton checkbox: it's not an array
		if (!form[elementName][0]) {
			return form[elementName].checked;
		}		
			
		optionCount	= form[elementName].length;
		
		for (i = 0; i < optionCount; i++) {
			if (form[elementName][i].checked) {
				return true;
			}
		}
		
		return false;
	},
	

  /**
   * @description Determine whether a string consists solely of whitespace
   * @method allblanks   
   * @param {String} s The string to test
   * @return {Boolean} True iff the string is all blank
   */
	allblanks : function(s) {
		var i, c;
		for (i=0; i < s.length; i++) {
			c = s.charAt(i);
	   	 	if ((c != ' ') && (c != '\n') && (c != '\t')) {
	     		return false;
	   		}
		}
		return true;
	},
	
	
  /**
   * @description Determine whether a string is null, empty, or consists solely of whitespace
   * @method isblank   
   * @param {String} s The string to test
   * @return {Boolean} True iff the string is null, empty, or all blank
   */	
	isblank : function(s) {
		return (s == null || s.length == 0 || ILR.form.validation.util.allblanks(s));
	},

	/** 
	 * @description Determine whether at least one in an array of elements is non-empty
	 * @method oneIsFilledIn
	 * @param {Array} elements An array of HTML element names
	 * @param {Object} formObj The form object containing elements
	 * @return {Boolean} Return true iff one of the form elements in the array is non-empty
	 */	
	 oneIsFilledIn : function(elements, formObj) {
	 	
		var i, el;

		for (i = 0; i < elements.length; i++) {
			el = elements[i];
			// Make sure the element is present in the DOM (the requireOne value of an element
			// might exist in the form in some cases and not others).
			if (formObj[el]) {
				if (!ILR.form.validation.util.isEmpty(formObj, el)) {				
					return true;
				}
			}
		}
		return false;		
	 },
	 
	/**
	 * @description Return true iff the element is empty. Apply test based on element type.
	 * @method isEmpty
	 * @param {String} elName An HTML element name
	 * @return {Boolean}
	 */
	isEmpty : function(formObj, elName) {
		
		// el is either an HTML element, or an array of radio/checkbox elements
		var el = formObj[elName];
		
		// If el is an array of radio/checkbox elements, it has no type
		if (el[0]) {
			if ( !ILR.form.validation.util.hasOptionSelected(formObj, el[0].name) ) {
				return true;
			}
		}
		// A singleton checkbox or radio is not an array, but does have a type
		else if (el.type == "radio" || el.type == "checkbox") {
			if ( !ILR.form.validation.util.hasOptionSelected(formObj, el.name) ) {
				return true;
			}			
		}
		else if ( /^(text|select|file)/.test(el.type) ) {
			if (ILR.form.validation.util.isblank(el.value)) {
				return true;
			}
		}

		return false;
	},
	
  /**
   * @description Determine if a string is a valid email address. The email_format regexp is adapted from 
   * http://javascript.internet.com/forms/email-validation---basic.html
   * The list of valid TLDs is from http://javascript.internet.com/forms/email-address-validation.html and
   * http://www.icann.org/tlds/. The former also provides a more detailed email validation routine.
   * RY 6/14/06 Amended to allow apostrophes and plus signs in the username, and any three letters in the TLD. 
   * @method isValidEmail  
   * @param {String} email The string to test
   * @return {Boolean} True iff the string is a valid email address
   */		
	isValidEmail : function(email) {
		var email_format = /^[\+'\w]+([\.-][\+'+\w]+)*@\w+([\.-]\w+)*\.([A-Za-z]{2,3}|aero|arpa|coop|info|museum|name|pro)$/;
		return email_format.test(email.trim());

	},


  /**
   * @description Determine if a string is a valid 5- or 9-digit US zipcode
   * @method isValidUSZipcode  
   * @param {String} zipcode The string to test; not of type Number because it may contain a hyphen
   * @return {Boolean} True iff the string is a valid US zipcode
   */	   	
	isValidUSZipcode : function(zipcode) {
		var zipcode_format = /^\d{5}(-\d{4})?$/;	
		return zipcode_format.test(zipcode.trim());

	},

  /**
   * @description Determine if a string is a valid Canadian postal code. Format is 
   * UdU dUd (U = uppercase letter, d = digit). Here, treat space as optional, allow multiple spaces, 
   * and accept lowercase letters too.
   * @method isValidCanPostalCode  
   * @param {String} pcode The string to test
   * @return {Boolean} True iff the string is a valid Canadian postal code
   */		
	isValidCanPostalCode : function(pcode) {
		var postalCodeFormat = /^[a-zA-Z]\d[a-zA-Z] *\d[a-zA-Z]\d$/;
		return postalCodeFormat.test(pcode.trim());

	},

   /**
    * @description Determine if a string is a valid US zipcode or Canadian postal code.
    * Could match against state vs province selection (i.e., only allow Canadian postal
	* code format with Canadian province selection).
    * @method isValidUSOrCanZipcode
    * @param {String} code The string to validate
    * @return {Boolean} True iff code is a valid US zipcode or Canadian postal code
    */
	isValidUSOrCanZipcode : function(code) {
		return ILR.form.validation.util.isValidUSZipcode(code) || ILR.form.validation.util.isValidCanPostalCode(code);
	},		

  /**
   * @description Determine if a string is a valid 10-digit phone number
   * @method isValidFullPhoneNumber  
   * @param {String} number The number to test
   * @return {Boolean} True iff the string is a valid phone number
   */	
	isValidFullPhoneNumber : function(number) {
		var phone_number = /^\d{10}$/;  // a 10-digit string
		number = number.trim();
		
		// strip out all phone number punctuation
		number = number.replace(/[\(\)\/\.\- ]/g, ""); 

		return phone_number.test(number);	
	},	

  /**
   * @description Determine if a string is a valid 7-digit phone number
   * @method isValidLocalPhoneNumber  
   * @param {String} number The number to test
   * @return {Boolean} True iff the string is a valid phone number
   */		
	isValidLocalPhoneNumber : function(number) {
		var phone_number = /^\d{7}$/;  // a 7-digit string
	
		number = number.trim();
		// strip out all phone number punctuation
		number = number.replace(/[\(\)\/\.\- ]/g, ""); 

		return phone_number.test(number);		
	},

  /**
   * @description Determine if a string is a valid 7- or 10-digit telephone number. 
   * @method isValidPhoneNumber  
   * @param {String} number The number to test
   * @return {Boolean} True iff the string is a valid phone number
   */
	isValidPhoneNumber : function(number) {
		return ILR.form.validation.util.isValidFullPhoneNumber(number) || ILR.form.validation.util.isValidLocalPhoneNumber(number);
	},

  /**
   * @description Determine if a string is a valid international telephone number: accept
   * any string of digits with phone number punctuation 
   * @method isValidInternationalPhoneNumber  
   * @param {String} number The number to test
   * @return {Boolean} True iff the string is a valid phone number
   */	
	isValidInternationalPhoneNumber : function(number) {
		var phone_number = /^\d+$/;  // a numeric string string
	
		number = number.trim();
		// strip out all phone number punctuation
		// Note the + for international numbers - this isn't stripped out of US phone numbers
		number = number.replace(/[+\(\)\/\.\- ]/g, ""); 

		return phone_number.test(number);			
		
	},


  /**
   * @description Determine if a string is a valid 4-digit year: 19nn or 20nn
   * @method isFourDigitYear  
   * @param {Number} year The number to test
   * @return {Boolean} True iff the number is a valid year
   */		
	isFourDigitYear : function(year) {
		var year_format = /^(19|20)\d{2}$/;
		return year_format.test(year.trim());
	},



  /**
   * @description Determine if a string is a 2-digit year: nn or 'nn
   * @method isTwoDigitYear  
   * @param {String} year The string to test
   * @return {Boolean} True iff the string is a 2-digit year
   */	
	isTwoDigitYear : function(year) {	
		var twoDigitYear = /^'?\d\d$/;
		return twoDigitYear.test(year.trim());	
	},


  /**
   * @description Determine if a string is a valid 2- or 4-digit year
   * @method isYear   
   * @param {String} year The string to test
   * @return {Boolean} True iff the string is a 2-digit year
   */		
	isYear : function(year) { 
		return ILR.form.validation.util.isFourDigitYear(year) || ILR.form.validation.util.isTwoDigitYear(year);
	},


  /**
   * @description Determine if a string is a date in mm-yyyy format
   * @method isDate_mmyyyy  
   * @param {String} date The string to test
   * @return {Boolean} True iff the string is a date in mm-yyyy format
   */		
	isDate_mmyyyy : function(date) {

		var dateArray,
			month,
			year;
			
		date = date.trim();
		dateArray = date.split(this._date_punct);  // strip out date punctuation

		if (dateArray.length != 2) { 
			return false; 
		}
		// Works in Perl, not JavaScript
		// var (month, year) = dateArray; 
		month = dateArray[0];
		year = dateArray[1];
	
		if (month > 12 || month < 1) { 
			return false;
		}
		return ILR.form.validation.util.isFourDigitYear(year);
	},


  /**
   * @description Determine if month and day are a valid month and day combination
   * @method isMonthDay  
   * @param {Number} month The numeric month to test
   * @param {Number} day The day to test
   * @return {Boolean} True iff the arguments are a valid month and day combination
   */		
	isMonthDay : function(month, day) {

		if (month > 12 || month < 1) { 
			return false;
		}
		if (day > 31 || day < 1) {
			return false;
		}	
		if (day == 31) {
			if (month == 4 || month == 6 || month == 9 || month == 11) {
				return false;
			}
		}
		if (month == 2) {
			// we're not dealing with leap year vs. non-leap year
			if (day > 29) {
				return false;
			}
		}
		return true;	
	},
	

  /**
   * @description Determine if date is in mdy format
   * m: 1 or 2 digits
   * d: 1 or 2 digits
   * y: 2 or 4 digits
   * @method isDate_mdy   
   * @param {String} date The date to test
   * @return {Boolean} True iff the arguments are a valid date in mdy format
   */		
	isDate_mdy : function(date) {
	
		var date_format = /^\d{1,2}([\.\-\/])\d{1,2}\1\d{2}(\d{2})?$/,
			dateArray,
			month,
			day,
			year;
	
		if (!date_format.test(date)) {
			return false;
		}
	
		dateArray = date.split(this._date_punct);  // strip out date punctuation	

		month = dateArray[0];
		day = dateArray[1];
		year = dateArray[2];
		if (!ILR.form.validation.util.isMonthDay(month, day)) {
			return false;
		}
		return ILR.form.validation.util.isYear(year);
	},
	
	
  /**
   * @description Determine if date is a valid date in mmddyyyy format
   * @method isDate_mmddyyyy   
   * @param {String} date The string to test
   * @return {Boolean} True iff the arguments are a valid date in mmddyyyy format
   */		
	isDate_mmddyyyy : function(date) {

		// Tried to define date_punct as a string, then use the string to define date_format, 
		// but can't get the escape sequences right
		// var date_punct = "[\.\-/]";
		// var date_format = new RegExp("^\\d{2}(" + date_punct + ")\\d{2}\\1\\d{4}$");
		var date_format = /^\d{2}([\.\-\/])\d{2}\1\d{4}$/,
			dateArray,
			month,
			day,
			year;
	
		if (!date_format.test(date)) {
			return false;
		}
	
		dateArray = date.split(this._date_punct);  // strip out date punctuation	
	
		// The regexp test above already ensures this
		/* if (dateArray.length != 3) { 
			return false; 
		} */
	
		// Works in Perl: var (month, day, year) = dateArray; 
		month = dateArray[0];
		day = dateArray[1];
		year = dateArray[2];
		if (!ILR.form.validation.util.isMonthDay(month, day)) {
			return false;
		}
		return ILR.form.validation.util.isFourDigitYear(year);
	},


  /**
   * @description Determine if a date range is valid
   * @method isValidDateRange   
   * @param {Number} startYear
   * @param {Number} startMonth
   * @param {Number} startDay
   * @param {Number} endYear
   * @param {Number} endDay
   * @return {Boolean} True iff the start date precedes or is equal to the end date
   */		
	isValidDateRange : function(startYear, startMonth, startDay, endYear, endMonth, endDay) {
	
		var startDate = new Date(),
			endDate = new Date();
		
		startDate.setFullYear(startYear, startMonth, startDay);
		endDate.setFullYear(endYear, endMonth, endDay);
	
		return (endDate >= startDate);
	},
	

  /**
   * @description Determine if an input element value is identical to some option element of a select element.
   * Use in a form where a user is asked to choose from an existing set of items in select element sel, 
   * can be used on return, if desired, to select the specfied option element in the form.
   * Test against the values of the option elements rather than the actual db values so that we can use
   * JavaScript to provide immediate feedback.
   * @method isIdentical   
   * @param {String} inp The value of the input element
   * @param {HTMLElement} sel The select element
   * @return {Number} The index of the matching option element. If no matching option, return 0.
   */		
	isIdentical : function(inp, sel) {
		
		var i;
		for (i = 0; i < sel.options.length; i++) {
			if (inp == sel.options[i].firstChild.nodeValue) { 
				return i;
			}
		}
		return 0;
	},
	

  /**
   * @description Checks for duplication between an input element and the options in a select element.
   * Similar to ILR.form.validation.util.isIdentical(), but uses certain types of "fuzzy" matching:
   * 1. Allows substitution of pattern pat with replacement sub, in both input values and option values.
   * 2. Provides both case-sensitive and case-insensitive matching.
   * Sample function call:
   * var pattern = /^The /;
   * var sub = "";
   * ILR.form.validation.util.isDuplicate : function("pub", "pub_list", pattern, sub, 0) {
   * 		msg += "The publisher you entered is already in the list. Because duplicates are not allowed,
   *      the form input has been adjusted accordingly.\n\n";
   * }
   * This does a case-insensitive match with an initial definite article stripped away.
   * Future enhancement: pass a hash of patterns and substitutions for more sophisticated 
   * "fuzzy" matching.
   * @method isDuplicate   
   * @param {String} inpId The id of the input element
   * @param {String} selId The id of the select element
   * @param {Regexp} pat The pattern to replace in the input element value
   * @param {Regexp} sub The substitution pattern
   * @param {Boolean} caseSensitive Whether the match is case sensitive or not
   * @return {Boolean} True iff the input value duplicates a select element option.
   */	
	isDuplicate : function(inpId, selId, pat, sub, caseSensitive) {

		var inp = document.getElementById(inpId),
			inpVal = inp.value,
			optVal,
			sel,
			i;
	
		if (inpVal) {
			sel = document.getElementById(selId); 
			if (pat) {
				inpVal = inpVal.replace(pat, sub);
			}
			for (i = 0; i < sel.options.length; i++) {
				optVal = sel.options[i].firstChild.nodeValue;
				if (pat) {
					optVal = optVal.replace(pat, sub);
				}
				if (optVal.equals(inpVal, caseSensitive)) {
					ILR.form.validation.util.switchInpValToSelect(inp, sel, i);
					return true;
				}
			}
		}	
		return false;
	},
	
	
  /**
   * @description Erase form input from an input element, and select option i of select element sel.
   * Used if the input value duplicates one of the select element options, and the form
   * rejects duplicate input. 
   * @method switchInpValToSelect
   * @see ILR.form.validation.util.isIdentical(), ILR.form.validation.util.isDuplicate()   
   * @param {HTMLElement} inp The input element
   * @param {HTMLElement} sel The select element
   * @param {Number} i The index of the matching option element. 
   * @return void
   */		
	switchInpValToSelect : function(inp, sel, i) {
		inp.value = "";
		sel.selectedIndex = i;
	},
	

  /**
   * @description Return the number of items in an array of select elements that have an option selected.
   * @method getNumSelected   
   * @param {HTMLElement} parent The parent element of the select elements
   * @return {Number} The number of select elements that have an option selected
   */			
	getNumSelected : function(parent) {

		var selectElements = parent.getElementsByTagName("select");
	
		var numSelected = 0;
		for (var i = 0; i < selectElements.length; i++) {
			if (selectElements[i].value) {
				numSelected++;
			}
		}
		return numSelected;
	
	},


  /**
   * @description Determine whether a filename has one of the types included in a list of filetypes
   * @method hasValidFiletype   
   * @param {String} filename The filename to test
   * @param {String} filetypes A comma-delimited list of file extensions
   * @return {Boolean} True iff the filename type matches one of the list items
   */		
	hasValidFileType : function(filename, filetypes) {

		var ext = new RegExp("\\b" + filename.split(".").pop() + "\\b", "i");
		return ext.test(filetypes);

	}

};

