var W3CDOM = (document.getElementsByTagName && document.createElement);
var validForm;
var firstError;
var errorstring;


if (typeof addEvent == "undefined") {
	/**
	 * Cross-browser event handling for IE5+,  NS6 and Mozilla; by Scott Andrew
	 */
	function addEvent(elm, evType, fn, useCapture) {
		if (elm.addEventListener){
			elm.addEventListener(evType, fn, useCapture);
			return true;
		}
		if (elm.attachEvent){
			var r = elm.attachEvent("on"+evType, fn);
			return r;
		}
	}
}

addEvent(window, "load", validate_init);

function validate_init() {
	// Find all tables with class validate and make them validatable
	if (!document.getElementsByTagName) return;
	frms = document.getElementsByTagName("form");
	for (i = 0; i < frms.length; i++) {
		if (frms[i].className.match(/\bvalidate\b/)) {
			addEvent(frms[i], "submit", validateHandler);
		}
	}
}

/*************************************************
 * Utiliy Functions
 *************************************************/
if (typeof Node == "undefined") {
	var Node = {
		ELEMENT_NODE: 1,
		ATTRIBUTE_NODE: 2,
		TEXT_NODE: 3,
		CDATA_SECTION_NODE: 4,
		ENTITY_REFERENCE_NODE: 5,
		ENTITY_NODE: 6,
		PROCESSING_INSTRUCTION_NODE: 7,
		COMMENT_NODE: 8,
		DOCUMENT_NODE: 9,
		DOCUMENT_TYPE_NODE: 10,
		DOCUMENT_FRAGMENT_NODE: 11,
		NOTATION_NODE: 12
	}
}

/**
 * Strip leading and trailing whitespace from a string. Extends String object.
 *
 * @param string
 * @return string
 **/
function trimString (str) {
  str = this != window? this : str;
  return str.replace(/^\s+/g, '').replace(/\s+$/g, '');
}
String.prototype.trim = trimString;

/**
 * Used to pull array of selected indexes from an HTML select element,
 * useful where multiple selection is enabled.
 *
 * May be used to extend select objects.
 *
 * @param select
 * @return false|string False on err, else Array of values
 **/
function getSelectedIndexes (sel) {
	if (!sel) {var sel = this;}
	if (sel.nodeType != Node.ELEMENT_NODE || !sel.nodeName.match(/^select$/i)) {
		return false;
	}
	var ret = new Array();
	for (var i = 0; i < sel.options.length; i++) {
		if (sel.options[i].selected) {ret.push(i);}
	}
	return ret;
}

/*************************************************
 * Validation Functions
 *************************************************/

function validateHandler(e) {
	if (!e) {e = window.event;}
	var f;
	
	if (e.srcElement) {f = e.srcElement;}
	else if (e.target) {f = e.target}
	else {return true;}
	
	//Depending on how the form was submited, Firefox may have an input 
	// as target instead of the form
	while (f.nodeName != "FORM" && f.parentNode) {
		f = f.parentNode;
	}
	if (f.nodeName != "FORM") {return true;}
	
//	alert (f +':'+f.nodeName+'; val:'+f.value+'; id:'+f.id+'; name:'+f.name);
	if (validate(f)) {return true;}
	e.returnValue = false;
	if (e.preventDefault) {e.preventDefault();}
	return false;
}

function validate(f) {
	if (!f) {return true;}
	
	validForm = true;
	firstError = null;
		
	var x = f.elements;
	for (var i=0; i < x.length; i++) {
		if (typeof x[i].value == "undefined") {continue;} //fieldset
		var cn = x[i].className;
		if (cn.match(/\brequired\b/)) {
			if (x[i].type == "checkbox" && !x[i].checked) {
				writeError(x[i],'Required');
				continue;
			}
			if (!(x[i].value.trim())) {
				writeError(x[i],'Required');
				continue;
			}
		}
		if (x[i].value.trim() == '') {continue;}
		if (cn.match(/\bfutureUSDate\b/) && !isFutureUSDate(x[i])) {
			writeError(x[i],'Invalid Date');
		} else if (cn.match(/\bdate\b/)) {
			if((cn.match(/\bpast\b/) && !isPastDate(x[i])) || !isDate(x[i])) {
				writeError(x[i],'Invalid Date');
			}
		} 
		else if (cn.match(/\bemail\b/) && !isEmail(x[i]) ) {
			writeError(x[i],'Invalid Email Address');
		}
		else if (cn.match(/\bzip\b/) && !isZip(x[i]) ) {
			writeError(x[i],'Invalid US Zipcode');
		} 
		else if (cn.match(/\binteger\b/)) {
			if (!isInteger(x[i]) ) {writeError(x[i],'Invalid');}
			else if (cn.match(/\bpositive\b/) && !isPositive(x[i])) {
				writeError(x[i], 'Must be a positive integer.');
			}
		} else if (cn.match(/\bfloat\b/)) {
			if (!isFloat(x[i]) ) {writeError(x[i],'Must be a number.');}
			else if (cn.match(/\bpositive\b/) && !isPositive(x[i])) {
				writeError(x[i], 'Must be a positive number.');
			}
		} 
		else if (cn.match(/\bcolor\b/) && !isColor(x[i]) ) {
			writeError(x[i],'Unrecognizable color.');
		}
	}

	if (firstError) {
		firstError.focus();
	}

	return validForm;
}

function writeError(obj, message)
{
	validForm = false;
	if (obj.hasError) return;
	if (W3CDOM)
	{
		obj.className += ' error';
		obj.onchange = removeError;
		var sp = document.createElement('span');
		sp.className = 'error';
		sp.appendChild(document.createTextNode(message));
		obj.parentNode.appendChild(sp);
		obj.hasError = sp;
	}

	if (!firstError) {
		firstError = obj;
	}
}

function removeError()
{
	this.className = this.className.replace(/ error$/,'');
	//substring(0,this.className.lastIndexOf(' '));
	this.parentNode.removeChild(this.hasError);
	this.hasError = null;
	this.onchange = null;
}

/**
 * Locale specific date conversion
 **/
function toDate(inp) {
	var str = inp.value.trim();
//	str = str.replace(/\\-/, '/');
	return new Date(Date.parse(str));
}

/**
 * Locale specific date in the past
 **/
function isPastDate(inp) {
	var dt = toDate(inp);
	if (!dt) {return false;}
	
	var now = new Date();
	if (dt.getFullYear() < now.getFullYear()) {return true;}
	if (dt.getFullYear() == now.getFullYear()) {
		if (dt.getMonth() < now.getMonth()) {return true;}
		if (dt.getMonth() == now.getMonth()
		    && dt.getDate() < now.getDate()) {return true;}
	}
	return false;
}

/**
 * Locale specific date
 **/
function isDate(inp) {
	var dt = toDate(inp);
	if (!dt) {return false;}
	return true;
}

/**
 * Validates that a string contains only valid dates with 
 * 1 or 2 digit month, 1 or 2 digit day, 4 digit year. Date separator 
 * may be ., -, or /.
 *   Ex. m/d/yyyy or mm-dd-yyyy or mm.dd.yyyy
 *
 * Avoids some of the limitations of the Date.parse()
 * method such as the date separator character.
 *
 * @param input Field where value string contains date to test.
 *              Will be overwritten.
 * @return bool true if valid, else false
 * //TODO: rewrite isFuturreUsDate to accept locale-specific dates; tighten code
 **/

function isFutureUSDate(inp) {	

	//force to '/' as separator
	var strValue = inp.value.trim();
	
	// testing whether the field has a value is handled by class="required"
	// this allows for optional date fields
	if (strValue == '') {return true;}
	
	var strSeparator = "/";
	strValue = strValue.replace(/\-/g, strSeparator);
	strValue = strValue.replace(/\./g, strSeparator);
	
	
	var objRegExp = /^[01]?\d(\/)[0123]?\d\1\d{4}$/

  //check to see if in correct format
	if(!objRegExp.test(strValue)) {
		return false; //doesn't match pattern, bad date
	}
	
	inp.value = strValue;

	var arrayDate = strValue.split(strSeparator); //split date into month, day, year
	//create a lookup for months
	var arrayLookup = new Array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	var intDay = parseInt(arrayDate[1]);
	var intMonth = parseInt(arrayDate[0]);
	var intYear = parseInt(arrayDate[2]);

	if (!intMonth || intMonth < 1 || intMonth > 12) {
		return false;
	}
	
		//check if month value and day value agree
	if(!intDay || intDay > arrayLookup[ intMonth - 1]) {
		return false;
	}


	if (intMonth == 2) { 
		// February has 29 days in any year evenly divisible by four,
		// EXCEPT for centurial years which are not also divisible by 400.


		if ( (intYear % 4 != 0) && intDay >= 29) {
			return false;
		}
    
    if ( (intYear % 400 == 0) && intDay >=29 ) {
    	return false;
		}
	}

	//is it in the future?
	var nowDate = new Date(); //now
	nowDate.setHours(0, 0, 0, 0);
	var valiDate = new Date( intYear, intMonth - 1, intDay, 0, 0, 0, 0);
	if (nowDate.valueOf() >= valiDate.valueOf()) {
		return false;
	}
	
	return true;   

}//end function isFutureUSDate( strValue )


/**
 * Validates that a string looks something like an email address
 *
 * Accounts for email with country appended. Does not validate that email
 * contains valid top level domain or country suffix. Accommodates TLD names
 * with more than three letters (like .name or .info).
 *
 * This regexp is intentionally loose, but should validate for newer domains.
 *
 * Also, see http://en.wikipedia.org/wiki/Email_address for information re the
 * "local" portion of addresses, where many characters are technically accepted.

 *

 * @param input Field where value string contains date to test.
 * @return bool true if valid, else false
 */


function isEmail(inp) {
	var strValue = inp.value.trim();
	var objRegExp  = /^.+\@(([a-z0-9_\-])+\.)+([a-z0-9]{2,})$/i;
	
  //check for valid email
  return objRegExp.test(strValue);
}//end function isEmail

/**
 * Validates that a string is an (base 10) integer.
 * Allows for leading '+' and zeroes.
 *
 * @param input Field where value string contains data to test.
 * @return bool true if valid, else false
 */


function isInteger(inp) {
	var strValue = inp.value.trim();

	strValue = strValue.replace(/^(\+?)(0+)/, '');
	if (strValue == '') {strValue = '0';}
	
	var intValue = parseInt(strValue, 10);
  return (strValue == String(intValue));
}//end function isInteger

/**
 * Validates that a string is a float (base 10 is implied).
 * Allows for leading '+' and zeroes, and trailing zeros following decimal
 *
 * @param input Field where value string contains data to test.
 * @return bool true if valid, else false
 */


function isFloat(inp) {
	var strValue = inp.value.trim();

	//leading zeros  
	strValue = strValue.replace(/^(\+?)(0+)/, '');
	//lone decimal  
	strValue = strValue.replace(/\.0*$/, '');
	//trailing zeros 
	strValue = strValue.replace(/(\.(\d*?))(0+)$/, '$1');
	if (strValue == '') {strValue = '0';}
	
	var floatValue = parseFloat(strValue);
  return (strValue == String(floatValue));
}//end function isFloat

/**
 * Validates that a field value is a positive number.
 * Feld must also have class integer or float, or this function has no effect
 *
 * @param input Field where value string contains data to test.
 * @return bool true if valid, else false
 */


function isPositive(inp) {
	if (!inp.className.match(/integer|float/)) {return true;}
	var strValue = inp.value.trim();

	var val = Number(strValue);
	return (!isNaN(val) && val > -1);
}//end function isPositive

/**
 * Validates that a string is a CSS color reference.

 *

 * @param input Field where value contains string to test.
 * @return bool true if valid, else false
 */
function isColor(inp) {
	var str = inp.value.trim();
	if (str == "") {
		return false;
	}
	
	//Match against a 3 or 6-digit hex number, leading # is required
	var pattern = /^#([0-9a-f]{3}){1,2}$/i;
	if (pattern.test(str)) {
		return true;
	}
	
	//Match against rgb(X,X,X) where X is an int 0-255. 
	//Allows for whitespace around numbers per W3C.
	// Matches 0-255:  (([1-9]?[0-9]{1})|(1([0-9]{2}))|(2(([0-4][0-9])|(5[0-5]))))
	// pattern =  /^rgb\(\s*(([1-9]?[0-9]{1})|(1([0-9]{2}))|(2(([0-4][0-9])|(5[0-5]))))\s*,\s*(([1-9]?[0-9]{1})|(1([0-9]{2}))|(2(([0-4][0-9])|(5[0-5]))))\s*,\s*(([1-9]?[0-9]{1})|(1([0-9]{2}))|(2(([0-4][0-9])|(5[0-5]))))\s*\)$/i;
	pattern = /^rgb\(\s*([01]?\d\d?|2[0-4]\d|25[0-5])\s*\,\s*([01]?\d\d?|2[0-4]\d|25[0-5])\s*\,\s*([01]?\d\d?|2[0-4]\d|25[0-5])\s*\)$/;
	if (pattern.test(str)) {
		return true;
	}
	
	//Match against rgb(X,X,X) where X is a float 0.0-100.0%.
	//Allows for whitespace around numbers per W3C.
	// Matches 0.0 - 100.0:  (([1-9]?[0-9](\.[0-9])?)|(100(\.0)?))
	pattern =  /^rgb\(\s*(([1-9]?[0-9](\.[0-9])?)|(100(\.0)?))%\s*,\s*(([1-9]?[0-9](\.[0-9])?)|(100(\.0)?))%\s*,\s*(([1-9]?[0-9](\.[0-9])?)|(100(\.0)?))%\s*\)$/i;
	if (pattern.test(str)) {
		return true;
	}
	
	//Match the sixteen W3C-valid color values
	pattern = /^(black|green|silver|lime|gray|olive|white|yellow|maroon|navy|red|blue|purple|teal|fuchsia|aqua)$/i;
	if (pattern.test(str)) {
		return true;
	}
	
	//Match the CSS2 W3C-valid system colors
	pattern = /^(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)$/i;
	if (pattern.test(str)) {
		return true;
	}

	return false;
}//end function isColor( string )

function isZip(z) 
{
	var str = z.value.trim();
	var pattern = /^\d{5}$/i;
	if (pattern.test(str)) {
		return true;
	}
	return false;
}