// Form validation utility functions

var date_punct = /[\.\-\/]/;

// Trim whitespace from beginning and end of string
// This should be in a more general util file, but unfortunately Javascript doesn't allow us to include a javascript file
// directly.
function trim(str) {

	str = str.replace(/^\s+/, ""); 
	str = str.replace(/\s+$/, "");
	return str;
}

// Returns true iff an option has been selected in a set of radio buttons or checkboxes.
function optionSelected(formObj, elementName) {
	for (i = 0; i < formObj[elementName].length; i++) {
		// return i + 1, not i, since i == 0 if the first element is selected
		if (formObj[elementName][i].checked) return (i + 1);
	}
	return false;
}

// Returns true iff string s contains only whitespace (spaces, tabs, and newlines).
function allblanks (s) {
	for (var i=0; i < s.length; i++) {
		var c = s.charAt(i);
	    if ((c != ' ') && (c != '\n') && (c != '\t')) {
	      return false;
	    }
	}
	return true;
}


// Returns true iff string s is empty or contains only whitespace.	
function isblank (s) {
	return (s == null || s.length == 0 || allblanks(s));
}

// Returns the value of a text, textarea, or single-select element for validation.
function getTextOrSelectValue(el) {
	
	var val;
	if (/^text/.test(el.type)) {
		val = el.value;
	}
	else {
		val = el.options[el.selectedIndex].value;
	}
	return val;
}


// Returns true iff string email 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. 
function isValidEmail(email) {

	var email_format = /^[\+'\w]+([\.-][\+'+\w]+)*@\w+([\.-]\w+)*\.([A-Za-z]{2,3}|aero|arpa|coop|info|museum|name|pro)$/;

	email = trim(email);

	// return email.search(email_format) + 1;
	return email_format.test(email);

}

// Returns true iff string zipcode is a valid 5- or 9-digit US zipcode.
function isValidUSZipcode(zipcode) {

	var zipcode_format = /^\d{5}(-\d{4})?$/;
	
	zipcode = trim(zipcode);
	
	//return zipcode.search(zipcode_format) + 1;
	return zipcode_format.test(zipcode);

}

// Returns true iff string pcode 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.
function isValidCanPostalCode(pcode) {
	
	var postalCodeFormat = /^[a-zA-Z]\d[a-zA-Z] *\d[a-zA-Z]\d$/;
	
	pcode = trim(pcode);
	
	return postalCodeFormat.test(pcode);
}

// Returns true iff string code 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).
function isValidUSOrCanZipcode(code) {
	return isValidUSZipcode(code) || isValidCanPostalCode(code);
}

// Returns true iff string number is a 10-digit telephone number. 
// This is a simplified validation in which separators () - . / and space are simply stripped out and ignored, and the 
// remaining string validates iff it consists of 10 digits. Therefore, it doesn't check for consistency of separators, although certain types of mixing are allowed anyway, e.g., 607/255-3456. 
function isValidFullPhoneNumber(number) {

	var phone_number = /^\d{10}$/;  // a 10-digit string

	number = trim(number);
	// strip out all phone number punctuation
	number = number.replace(/[\(\)\/\.\- ]/g, ""); 

	return phone_number.test(number);
	
}

// Returns true iff string number is a 7-digit telephone number. 
// This is a simplified validation in which separators () - . / and space are simply stripped out and ignored, and the 
// remaining string validates iff it consists of 7 digits. 
function isValidLocalPhoneNumber(number) {

	var phone_number = /^\d{7}$/;  // a 7-digit string

	number = trim(number);
	// strip out all phone number punctuation
	number = number.replace(/[\(\)\/\.\- ]/g, ""); 

	return phone_number.test(number);
	
}

// Returns true iff string number is a 7- or 10-digit telephone number. 
function isValidPhoneNumber(number) {

	return isValidFullPhoneNumber(number) || isValidLocalPhoneNumber(number);
	
}

// Returns true iff string year is a valid year. Used in date processing, so only accepts years 1900-2099.
function isYear(year) {

	var year_format = /^(19|20)\d{2}$/;
	year = trim(year);
	//return year.search(year_format) + 1;
	return year_format.test(year);

}

function isTwoDigitYear(year) {
	
	var twoDigitYear = /^'?\d\d$/;
	year = trim(year);
	return twoDigitYear.test(year);
	
}

function isTwoOrFourDigitYear(year) { 
	return ( isYear(year) || isTwoDigitYear(year) );
}

// Returns true iff date is a date in mm-yyyy format
function isDate_mmyyyy(date) {

	date = trim(date);
	var dateArray = date.split(date_punct);  // strip out date punctuation

	if (dateArray.length != 2) { 
		return false; 
	}
	// Works in Perl
	// var (month, year) = dateArray; 
	var month = dateArray[0];
	var year = dateArray[1];
	
	if (month > 12 || month < 1) { 
		return false;
	}
	return isYear(year);
}

// Returns true iff month and day are valid month and day values.
// Break this out into a separate function in case we want to validate mmdd, ddmmyyyy formats, etc.
function isMonthDay(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;	
}

// Returns true iff date is a date in mdy format
// m: 1 or 2 digits
// d: 1 or 2 digits
// y: 2 or 4 digits
function isDate_mdy(date) {
	
	var date_format = /^\d{1,2}([\.\-\/])\d{1,2}\1\d{2}(\d{2})?$/;
	
	if (!date_format.test(date)) {
		return false;
	}
	
	var dateArray = date.split(date_punct);  // strip out date punctuation	

	var month = dateArray[0];
	var day = dateArray[1];
	var year = dateArray[2];
	if (!isMonthDay(month, day)) {
		return false;
	}
	return isTwoOrFourDigitYear(year);
}	



// Returns true iff date is a date in mm-dd-yyyy format
function isDate_mmddyyyy(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}$/;
	
	if (!date_format.test(date)) {
		return false;
	}
	
	var dateArray = date.split(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; 
	var month = dateArray[0];
	var day = dateArray[1];
	var year = dateArray[2];
	if (!isMonthDay(month, day)) {
		return false;
	}
	return isYear(year);
}

// Returns true iff num is an integer
function isInt(num) {

	return num == parseInt(num, 10);
	
}

// Returns true iff num is a float
function isFloat(num) {
	
	return num == parseFloat(num, 10);
	
}

// Returns true iff start date precedes or is equal to end date
function isValidDateRange(startYear, startMonth, startDay, endYear, endMonth, endDay) {

	var startDate = new Date();
	startDate.setFullYear(startYear, startMonth, startDay);
	var endDate = new Date();
	endDate.setFullYear(endYear, endMonth, endDay);
	
	return (endDate >= startDate);
}

// If input value inp is already included in the text node values of select element sel's options,
// return the index of the option element, otherwise return 0. 
// Use in a form where a user is asked to choose from an existing set of items in select element sel, 
// or add a new one, to prevent a duplicate item from being added to the database. The index element
// 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.
function isIdentical(inp, sel) {

	for (var i = 0; i < sel.options.length; i++) {
		if (inp == sel.options[i].firstChild.nodeValue) { 
			return i;
		}
	}
	return 0;
}

// Checks for duplication between input element with id inpID and the options in a select element
// with id selID. Similar to function 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 = "";
// isDuplicate("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.
function isDuplicate(inpID, selID, pat, sub, caseSensitive) {

	if (!document.getElementById) { return false; }
	var inp = document.getElementById(inpID);
	var inpVal = inp.value;
	
	if (inpVal) {
		var optVal;
		//var optPat;
		var sel = document.getElementById(selID); 
		if (pat) {
			inpVal = inpVal.replace(pat, sub);
		}
		for (var i = 0; i < sel.options.length; i++) {
			optVal = sel.options[i].firstChild.nodeValue;
			if (pat) {
				optVal = optVal.replace(pat, sub);
			}
			if (stringsMatch(optVal, inpVal, caseSensitive)) {
				switchInpValToSelect(inp, sel, i);
				return true;
			}
		}
	}	
	return false;
}

// Return true iff str1 matches str2. The match is either case-sensitive or 
// case-insensitive, depending on the value of parameter caseSensitive.
function stringsMatch(str1, str2, caseSensitive) {

	if (caseSensitive) { return str1 == str2; }
	var str1Pat = new RegExp("^" + str1 + "$", "i");
	return str1Pat.test(str2);
}

// Erase form input from input element inp, 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. (See functions isIdentical(), isDuplicate().)
function switchInpValToSelect(inp, sel, i) {
	inp.value = "";
	sel.selectedIndex = i;
}

// Return true iff filename has one of the types listed in string filetypes. 
function hasValidFiletype(filename, filetypes) {

	var ext = new RegExp("\\b" + filename.split(".").pop() + "\\b", "i");
	return ext.test(filetypes);

}

// Return the number of items in an array of select elements that have an option
// selected. Argument parent is the parent element of the select elements.
function getNumSelected(parent) {

	var selectElements = parent.getElementsByTagName("select");
	
	var numSelected = 0;
	for (var i = 0; i < selectElements.length; i++) {
		if (selectElements[i].value) {
			numSelected++;
		}
	}
	return numSelected;
	
}

// Set all form fields to optional. Then the form-specific code specifies the fields that 
// are required, rather than those that are optional. Use when the majority of fields in
// a form are optional rather than required. Note that there is no need for the converse
// function to set all fields to required, since by default the value of an element's 
// optional property is false.
function setAllFieldsOptional(formObj) {

	for (var i = 0; i < formObj.elements.length; i++) {
		formObj.elements[i].optional = true;
	}

	return true;

}

