function FormValidator(){};
FormValidator.elements = [];
FormValidator.form = null;

FormValidator.setForm = function( fName ) {	
	FormValidator.form = document.forms[ fName ];
};

FormValidator.getForm = function() {	
	if( FormValidator.form == null )
		FormValidator.form = document.forms[ 0 ];

	return FormValidator.form;
};

FormValidator.getElement = function( id ) {
	var len = FormValidator.elements.length;
	for( var i=0; i<len; i++ ){
		if( FormValidator.elements[ i ].id == id )
			return FormValidator.elements[ i ];
	}

	return null;
};

FormValidator.addFormElement = function( element ) {
	FormValidator.removeFormElement( element.id );
	FormValidator.elements.push( element.setForm( FormValidator.getForm() ).initObj() );
};

FormValidator.removeFormElement = function( id ){
	var arrayIndex = null;
	var len = FormValidator.elements.length;
	for( var i=0; i<len; i++ ) {
		if( FormValidator.elements[i].id == id ){
			arrayIndex = i;
			break;
		}
	}

	if( arrayIndex != null ) {
		FormValidator.elements.splice( arrayIndex ,1 );
	}
};

FormValidator.run = function(){
	var errors = [];
	var len = FormValidator.elements.length;
	for( var i=0; i<len; i++ ){
		if( ! FormValidator.elements[ i ].validate() ){
			errors.push( FormValidator.elements[ i ] );
		}
	}

	if( errors.length > 0 ){
		FormValidator._showErrors( errors );
		return false;
	}

	return true;
};

FormValidator._showErrors = function( errors ){
	var len = errors.length;
	var e = ( len > 1 )? " errors" : " error";
	var a = ( len > 1 )? "are " : "is ";
	var errMsg = "There " + a + len + e + " on this form :\n";
	for( var i=0; i<len; i++ ){
		errMsg += "[ " + ( i + 1 ) + " ] - " + errors[ i ].getErrorMsg() + "\n";
	}
	errors[ 0 ].htmlElement.focus();

	alert( errMsg );
};

/**
* base class
*/
function FormField(id, displayName) {
	this.id = id;
	this.displayName = displayName;
	this.htmlElement = null;
	this.errMsg = null;
};

FormField.prototype.setForm = function( form ){
	this.htmlElement = form.elements[ this.id ];
	this.htmlElement.obj = this;
	return this;
};

FormField.prototype.initObj = function(){
	return this;
};

FormField.prototype.validate = function(){
	return false;
};

FormField.prototype.getErrorMsg = function(){
	if( this.errMsg == null ){
		this.setErrorMsg( "There was an error with " + this.displayName + "." );
	}

	return this.errMsg;
};

FormField.prototype.setErrorMsg = function( msg ){
	this.errMsg = msg;
	return this;
};

FormField.prototype.showError = function(){
	alert( "Error - " + this.getErrorMsg() );
};

/**
* used for <select> html elements
*/
function SelectBox( id, displayName ){
	this.constructor( id, displayName );
	this.min = 0;
	this.max = null;
};
SelectBox.prototype = new FormField; //extends FormField

SelectBox.prototype.initObj = function()
{	// add an onChange event to the select box if a max is set
	if( this.max != null ){
		this.htmlElement.onchange = checkMaxSelected;
	}

	return this;
};

SelectBox.prototype.setMax = function( max ){
	this.max = max;
	if( this.min != 0 ){
		if( this.min > this.max ){
			this.min = this.max;
		}
	}
	return this;
};

SelectBox.prototype.setMin = function( min ){
	this.min = min;
	if( this.max != null ){
		if( this.max < this.min ){
			this.max = this.min;
		}
	}
	return this;
};

function checkMaxSelected(){
	if( this.type == "select-multiple" ){
		var obj = this.obj;
		var alerted = false;
		var numSelected = 0;
		var len = this.options.length;
		for (var i=0; i<len; i++){
			if ( this.options[ i ].selected ){
				if( numSelected < obj.max ){
					numSelected ++;
				} else {
					if( ! alerted ){
						obj.setErrorMsg( "Selection will be discarded as limit of " + obj.max + " has been reached." );
						obj.showError();
						alerted = true;
					}
					this.options[ i ].selected = false;
				}
			}
		}
	}
};

SelectBox.prototype.validate = function(){
	var selCount = 0;
	var len = this.htmlElement.options.length;
	for( var i=0; i<len; i++ ){
		if( this.htmlElement.options[ i ].selected )
			selCount ++;
	}

	if( selCount < this.min ){
		var o = ( this.min > 1 )? "option's" : "option";
		this.setErrorMsg( "Please select at least " + this.min + " " + o + " from " + this.displayName );
		return false;
	}

	return true;
};

/**
* used for <input type="text"> html elements
*/
function TextBox( id, displayName ){
	this.constructor( id, displayName );
	this.regExp = null;
	this.func = null;
};
TextBox.prototype = new FormField; //extends FormField

TextBox.NOT_NULL = /^.+$/m;
TextBox.NUMBER = /^\d+$/;
TextBox.LETTER_NUMBER_UNDERSCORE = /^\w+$/;
TextBox.EMAIL_ADDRESS = /^.+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3}|[0-9]{1,3})(\]?)$/;

TextBox.prototype.getValue = function(){
	return this.htmlElement.value;
};

TextBox.prototype.setValue = function( value ){
	this.htmlElement.value = value;
};

TextBox.prototype.setRegExp = function( reg ){
	this.regExp = reg;
	return this;
};

TextBox.prototype.setFunction = function( func ){
	this.func = func;
	return this;
};

TextBox.prototype.validate = function(){
	if( this.func != null ){
		return this.func( this );
	} else if( this.regExp != null ) {
		return this.regExp.test( this.getValue() );
	}
	
	return false;
};

/**
*	use this to validate dates
*/
function DateBox( id, displayName ){
	this.constructor(id, displayName);
	//this.setRegExp(/^([0-9]{1,2})(-|\/| )([0-9]{1,2})(-|\/| )([0-9]{4})$/i);
	this.setRegExp(/^([0-9]{1,2})(\/)([0-9]{1,2})(\/)([0-9]{4})$/i);
	this.dateFormat = DateBox.MM_DD_YYYY;
	this.validValue = null;
	this.afterDate = null;
	this.day = null;
	this.month = null;
	this.year = null;
};
DateBox.prototype = new TextBox; //extends TextBox
DateBox.prototype.setFunction = null;
DateBox.DD_MM_YYYY = "dd/mm/yyyy";
DateBox.MM_DD_YYYY = "mm/dd/yyyy";

DateBox.days = {m1:31, m2:28, m3:31, m4:30, m5:31, m6:30, m7:31, m8:31, m9:30, m10:31, m11:30, m12:31};
DateBox.getMonthDays = function( month ){
	return DateBox.days[ "m" + month ];
};

DateBox.names = {m1:"Jan", m2:"Feb", m3:"Mar", m4:"Apr", m5:"May", m6:"Jun", m7:"July", m8:"Aug", m9:"Sept", m10:"Oct", m11:"Nov", m12:"Dec"};
DateBox.getMonthName = function( month ){
	return DateBox.names[ "m" + month ];
};

DateBox.isLeap = function( year ){
	return ( ( ( year % 4 == 0 ) && ( year % 100 != 0 ) ) || ( year % 400 == 0 ) );
};

DateBox.prototype.initObj = function(){
	_getDMY( this );
	return this;
};

function _getDMY( o ){
	var obj = ( o instanceof FormField )? o : this.obj;
	
	if( obj.getValue() != obj.validValue ){
		if( obj.regExp.test( obj.getValue() ) ){
			if( obj.dateFormat == DateBox.MM_DD_YYYY ){
				obj.day = parseInt( RegExp.$3, 10 );
				obj.month = parseInt( RegExp.$1, 10 );
			} else {
				obj.day = parseInt( RegExp.$1, 10 );
				obj.month = parseInt( RegExp.$3, 10 );
			}
			obj.year = parseInt( RegExp.$5, 10 );
		}
	}
};

DateBox.prototype.defaultValue = function( defaultD ){
	this.defaultVal = defaultD;
	this.htmlElement.onfocus = function() {
		var temp = null;
		if( this.obj.getValue() == "" || this.obj.getValue() == DateBox.MM_DD_YYYY || this.obj.getValue() == DateBox.DD_MM_YYYY ) {	
			if( this.obj.defaultVal instanceof DateBox ){
				temp = this.obj.defaultVal.afterDate;
				this.obj.defaultVal.afterDate = null;
				if( this.obj.defaultVal.validate() )
					this.obj.setValue( this.obj.defaultVal.getValue() );
			} else { 
				this.obj.setValue( this.obj.defaultVal );
			}
			
			this.obj.defaultVal.afterDate = temp;
			this.select();
		}
	};
};

DateBox.prototype.setFormat = function( format ){
	this.dateFormat = format;
	return this;
};

DateBox.prototype.setValidValue = function( value ){
	this.validValue = value;
	return this;
};

DateBox.prototype.mustBeBefore = function( dateBox ){
	this.afterDate = dateBox;
	return this;
};

DateBox.prototype.isBefore = function( after ){
	_getDMY( after );
	
	if( DateBox.prototype.isBefore.caller != DateBox.prototype.validate ) {
		_getDMY( this );
	}
	
	if( ! ( this.year == after.year && this.month == after.month && this.day == after.day ) ){
		if( this.year < after.year ) {
			return true;
		} else {
			if( this.month < after.month && this.year == after.year ) {
				return true;
			} else {
				if( this.day < after.day && this.month == after.month && this.year == after.year ) {
					return true;
				}
			}
		}
	} else {
		return true;
	}

	return false;
};

DateBox.prototype.validate = function() { 
	_getDMY( this );

	this.setErrorMsg( "There was an error with " + this.displayName + ": " );
	if( this.getValue() != this.validValue ){
		if( this.afterDate != null && this.day != null ){
			if( ! this.isBefore( this.afterDate ) ){
				this.setErrorMsg( this.getErrorMsg() + this.displayName + " must be before " + this.afterDate.displayName );
				return false;
			}
		} 
		
		if( this.regExp.test( this.getValue() ) ){
			if( this.month == 2 ){
				DateBox.days[ "m2" ] = ( DateBox.isLeap( this.year ) )? 29 : 28;
			}
			
			if( this.day == 0 ){
				this.setErrorMsg( this.getErrorMsg() + this.day + " is not a valid day." );
				return false;
			}
			
			if( this.month > 12 || this.month < 1 ){
				this.setErrorMsg( this.getErrorMsg() + this.month + " is not a valid month." );
				return false;
			}
			
			if( this.day <= DateBox.getMonthDays( this.month ) ) {
				return true;
			} else {
				this.setErrorMsg( this.getErrorMsg() + "There are " + DateBox.getMonthDays( this.month ) + " days in " + DateBox.getMonthName( this.month ) + "." );
			}
		} else { 
			this.setErrorMsg( this.getErrorMsg() + "Date must be in " + this.dateFormat + " format." );
		}
	} else {
		return true;
	}

	return false;
};