var Validator = function( form ) {
	this.setForm(form);
	Validator.instances.push(this);
};

Validator.instances = new Array();

Validator.getValidator = function( form ) {
	var validator;
	if( form ) {
		if( typeof form == 'string' ) {
			//* debug */ console.log('document.forms[' + form + ']');
			form = document.forms[form];
		}
		for( var i = 0; i < Validator.instances.length; i++ ) {
			if( Validator.instances[i].form == form ) {
				//* debug */ console.log('Validator.instances[' + i + ']');
				validator = Validator.instances[i];
				break;
			}
		}
	}
	else {
		form = document.forms[0];
		if( !Validator.instances.length ) {
			//* debug */ console.log('new Validator(document.forms[0])');
			validator = new Validator(form);
		}
		else if( Validator.instances.length == 1 ) {
			//* debug */ console.log('Validator.instances[0]');
			validator = Validator.instances[0];
			if( !validator.form || !validator.isInDocument(validator.form) ) {
				validator = new Validator(form);
				//* debug */ console.log('isInDocument(validator.form) :: false; Validator.instances[0] = new Validator(document.forms[0])');
				Validator.instances[0] = validator;
			}
		}
	}
	if( !validator ) {
		validator = new Validator();
	}
	return validator;
};

Validator.addRule = function( rule ) {
	return Validator.getValidator().addRule(rule);
};

Validator.setRequired = function( elements ) {
	return Validator.getValidator().setRequired(elements);
};

Validator.handleSubmit = function( form, validator ) {
	if( !validator ) {
		validator = Validator.getValidator(form);
	}
	if( validator ) {
		return validator.validate(form);
	}
};

Validator.prototype = {

	form: null,
	rules: new Array(),

	setAlertErrorMessages: function( bool ) {
		if( typeof bool == 'undefined' ) {
			bool = true;
		}
		if( bool ) {
			this.showErrorMessages = this.alertErrorMessages;
		}
	},

	addRule: function( rule, validateEmpty ) {
		if( rule ) {
			rule.setValidator(this);
			this.rules.push(rule);
			if( validateEmpty ) {
				rule.validateEmpty = true;
			}
		}
	},

	setRequired: function( elements ) {
		if( typeof elements == 'string' ) {
			elements = elements.split(/ *, */);
		}
		for( var i = 0; i < elements.length; i++ ) {
			this.addRule(new RequiredRule(elements[i]));
		}
	},

	setForm: function( form ) {
		if( typeof form == 'string' ) {
			this.form = document.forms[this.form];
		}
		else if( typeof form == 'object' ) {
			this.form = form;
		}
		else if( !form ) {
			this.form = document.forms[0];
		}
		/*
		if( this.form && (typeof YAHOO != 'undefined') && YAHOO.env ) {
			if( YAHOO.env.modules.event && this.form.onsubmit ) {
				this.form.onsubmit = false;
				YAHOO.util.Event.addListener(this.form, 'submit', this.handleSubmitEvent, this, true);
			}
		}
		*/
	},

	handleSubmitEvent: function( e ) {
		if( !Validator.handleSubmit(this.form, this) ) {
			YAHOO.util.Event.stopEvent(e);
		}
	},

	getForm: function() {
		return this.form;
	},

	validate: function( form ) {
		var focussed, valid, rule, input, rv = true;
		//* debug */ console.log(this.rules.length + ' rules in validator');
		for( var i = 0; i < this.rules.length; i++ ) {
			var rule = this.rules[i];
			try {
				valid = rule.validate(form);
			}
			catch(e) {
				console.log(e);
				valid = false;
			}
			rule.wasValid = valid;
			if( !valid ) {
				if( !focussed ) {
					try {
						if( input = rule.getInput() ) {
							input.focus();
							focussed = true;
						}
					}
					catch(e) {
						console.log(e);
					}
				}
				rv = false;
			}
		}
		this.showErrorMessages();
		return rv;
	},

	showErrorMessages: function() {
		for( var i = 0; i < this.rules.length; i++ ) {
			try {
				rule = this.rules[i];
				if( rule.wasValid ) {
					rule.hideErrorMessage();
				}
				else {
					rule.showErrorMessage();
					var input = rule.getInput();
					Validator.addClass('invalidValue');
				}
			}
			catch(e) {
				alert(rule.getErrorMessage());
			}
		}
	},

	alertErrorMessages: function() {
		var str = '';
		for( var i = 0; i < this.rules.length; i++ ) {
			rule = this.rules[i];
			if( !rule.wasValid ) {
				str += rule.getErrorMessage() + '\n';
			}
		}
		if( str ) {
			alert(str);
		}
	},

	isInDocument: function( el ) {
		while( el ) {
			el = el.parentNode;
			if( el == document ) {
				return true;
			}
		}
	}

};

Validator.hasClass = function(el, className) {
	var re = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
	return re.test(el.className);
};
Validator.addClass = function(el, className) {
	if( Validator.hasClass(el, className) ) { return; }
	el.className = el.className + ' ' + className;
};
Validator.replaceClass = function(el, oldClassName, newClassName) {
	if( oldClassName === newClassName ) { return false; };
	if( !Validator.hasClass(el, oldClassName) ) {
		Validator.addClass(el, newClassName);
	}
	else {
		var re = new RegExp('(?:^|\\s+)' + oldClassName + '(?:\\s+|$)', 'g');
		el.className = el.className.replace(re, ' ' + newClassName + ' ');
	}
};

var ValidationRule = function( elementId, regEx, func, errorMessageId, validateEmpty ) {
	this.elementId = elementId;
	this.regEx = regEx;
	this.func = func;
	this.errorMessageId = errorMessageId;
	if( validateEmpty ) {
		this.validateEmpty = true;
	}
};

ValidationRule.prototype = {

	validator: null,
	elementId: null,
	element: null,
	regEx: null,
	func: null,
	errorMessageId: null,
	wasValid: false,
	validateEmpty: false,

	setValidator: function( validator ) {
		this.validator = validator;
	},

	validate: function( form ) {
		var rv = true;
		if( !this.getInput() ) {
			return true;
		}
		var value = this.getValue();
		if( !value && !this.validateEmpty ) {
			return true;
		}
		if( this.func ) {
			if( !this.func(form) ) {
				rv = false;
			}
		}
		if( this.regEx ) {
			if( !this.regEx.exec(value) ) {
				rv = false;
			}
		}
		return rv;
	},

	getInput: function() {
		if( !this.element ) {
			this.element = document.getElementById(this.elementId);
			if( !this.element ) {
				var form = this.validator.form;
				for( var i = 0; i < form.elements.length; i++ ) {
					if( form.elements[i].name == this.elementId ) {
						this.element = form.elements[i];
						if( this.element.id ) {
							this.elementId = this.element.id;
						}
						break;
					}
				}
			}
		}
		return this.element;
	},

	getValue: function() {
		return this.getInput().value;
	},

	showErrorMessage: function() {
		var el;
		if( this.errorMessageId ) {
			el = document.getElementById(this.errorMessageId);
			if( el && !el.innerHTML ) {
				this.setErrorMessageText(el, this.getErrorMessage());
			}
		}
		else {
			el = this.createErrorMessage(this.getErrorMessage());
		}
		if( el ) {
			Validator.replaceClass(el, 'hiddenErrorMessage', 'visibleErrorMessage');
			el.style.visibility = 'visible';
		}
	},

	hideErrorMessage: function() {
		if( this.errorMessageId ) {
			var el = document.getElementById(this.errorMessageId);
			if( el ) {
				Validator.replaceClass(el, 'visibleErrorMessage', 'hiddenErrorMessage');
				el.style.visibility = 'hidden';
			}
		}
	},

	getLabel: function() {
		return document.getElementById(this.elementId + '_label');
	},

	getErrorMessage: function() {
		return 'Please check that you have entered a valid value';
	},

	setErrorMessageText: function( el, errorMessage ) {
		el.innerHTML = errorMessage;
	},

	createErrorMessage: function( errorMessage ) {
		var suffix = '_0', i = 0;
		do {
			this.errorMessageId = this.elementId + '_errorMessage' + suffix;
			i++;
			suffix = '_' + i;
		}
		while( document.getElementById(this.errorMessageId) );
		
		var element = document.createElement('DIV');
		element.id = this.errorMessageId;
		element.innerHTML = errorMessage;
		if( i ) {
			element.suffix = i;
		}
		var input = this.getInput();
		return input.parentNode.insertBefore(element, input);
	},

	setValidateEmpty: function( bool ) {
		this.validateEmpty = bool;
	}

};

var RequiredRule = function( elementId, errorMessageId ) {
	this.elementId = elementId;
	if( errorMessageId ) {
		this.errorMessageId = errorMessageId;
	}
};

RequiredRule.prototype = new ValidationRule();
RequiredRule.prototype.parentClass = ValidationRule.prototype;
RequiredRule.prototype.constructor = ValidationRule;

RequiredRule.prototype.validate = function( form ) {
	var input = this.getInput();
	var rv = false;
	if( input ) {
		if( input.tagName == 'SELECT' ) {
			for( var i = 0; i < input.options.length; i++ ) {
				var option = input.options[i];
				if( option.selected && option.value && (option.value != '__NULL__') ) {
					rv = true;
					break;
				}
			}
		}
		else if( input.type == 'radio' ) {
			for( var i = 0; i < form.elements.length; i++ ) {
				if( form.elements[i].name == input.name ) {
					if( form.elements[i].checked ) {
						rv = true;
						break;
					}
				}
			}
		}
		else if( input.type == 'checkbox' ) {
			if( input.checked ) {
				rv = true;
			}
		}
		else if( input.value ) {
			rv = true;
		}
	}
	else {
		var len = this.elementId.length;
		for( var i = 0; i < form.elements.length; i++ ) {
			if( this.elementId == form.elements[i].id.substr(0, len) ) {
				if( form.elements[i].checked ) {
					rv = true;
					break;
				}
			}
		}
	}
	return rv;
};

RequiredRule.prototype.getErrorMessage = function() {
	var input = this.getInput();
	var inputLabel = this.getLabel();
	var str = '';
	if( inputLabel && inputLabel.firstChild && inputLabel.firstChild.nodeValue ) {
		inputLabel = ' for ' + inputLabel.firstChild.nodeValue.toLowerCase();
	}
	else {
		inputLabel = '';
	}
	if( input ) {
		if( input.tagName == 'SELECT' ) {
			return 'Please select an option' + inputLabel;
		}
		else if( input.type == 'radio' || input.type == 'checkbox' ) {
			return 'Please make a selection' + inputLabel;
		}
		else if( input.type == 'file' ) {
			return 'Please make sure that you have selected a file to upload';
		}
	}
	return 'Please enter a value' + inputLabel;
};

var EmailAddressRule = function( elementId, errorMessageId, validateEmpty ) {
	this.elementId = elementId;
	if( errorMessageId ) {
		this.errorMessageId = errorMessageId;
	}
	this.regEx = new RegExp('^[a-z0-9_.-]+@[a-z0-9_.-]+(\.[a-z0-9_.-]+)*$', 'i');
	if( validateEmpty ) {
		this.validateEmpty = true;
	}
};

EmailAddressRule.prototype = new ValidationRule();
EmailAddressRule.prototype.parentClass = ValidationRule.prototype;
EmailAddressRule.prototype.constructor = ValidationRule;

EmailAddressRule.prototype.getErrorMessage = function() {
	return 'Please check that you have entered a valid email address';
};

if( console == undefined ) {
	var console = {};
	with( console ) {
		log = debug = info = warn = error = time = timeEnd = profile = profileEnd = trace = group = groupEnd = dir = dirxml = function() {};
	}
}
