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

Validator.instances = new Array();

Validator.getValidator = function( form ) {
	if( Validator.instances.length == 0 ) {
		return new Validator(form);
	}
	else if( Validator.instances.length == 1 ) {
		return Validator.instances[0];
	}
	else {
		if( typeof form == 'string' ) {
			form = document.forms[form];
		}
		else if( !form ) {
			form = document.forms[0];
		}
		for( var i = 0; i < Validator.instances.length; i++ ) {
			if( Validator.instances[i].form.id == form.id ) {
				return Validator.instances[i];
			}
		}
	}
};

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

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

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

Validator.prototype = {

	form: null,
	rules: new Array(),

	addRule: function( rule ) {
		if( rule ) {
			this.rules.push(rule);
		}
	},

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

	validate: function( form ) {
		var focussed, valid, rule, rv = true;
		for( var i = 0; i < this.rules.length; i++ ) {
			var rule = this.rules[i];
			try {
				valid = rule.validate(form);
			}
			catch(e) {
				valid = false;
			}
			rule.wasValid = valid;
			if( !valid ) {
				rv = false;
			}
		}
		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();
					if( !focussed ) {
						try {
							input.focus();
							focussed = true;
						}
						catch(e) {}
					}
					CssUtils.addClass('invalidValue');
				}
			}
			catch(e) {
				alert(rule.getErrorMessage());
			}
		}
		return rv;
	}

};

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

var ValidationRule = function( elementId, regExp, func, errorMessageId ) {
	this.elementId = elementId;
	this.regExp = regExp;
	this.func = func;
	this.errorMessageId = errorMessageId;
};

ValidationRule.prototype = {

	elementId: null,
	element: null,
	regExp: null,
	func: null,
	errorMessageId: null,
	wasValid: false,
	validateEmpty: true,

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

	getInput: function() {
		if( !this.element ) {
			this.element = document.getElementById(this.elementId);
		}
		return this.element;
	},

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

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

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

	getLabel: function() {
		var input = this.getInput();
		return document.getElementById(input.id + '_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;
			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.tagName == 'SELECT' ) {
		for( var i = 0; i < input.options.length; i++ ) {
			if( input.options[i].selected && input.options[i].value ) {
				rv = true;
				break;
			}
		}
	}
	else if( input.type == 'radio' ) {
		var name = input.name;
		do {
			var form = input.parent;
		}
		while( form && form.tagName != 'FORM' );
		for( var i = 0; i < form.elements.length; i++ ) {
			if( form.elements[i].name == 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;
	}
	return rv;
};

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