/*	FormValidator
 *  (c) 2007 Costache Catalin, catacgc@gmail.com
 *
 *	All rights reserved.
 *	You can use / modify this script in your Web pages, provided these opening credit
 *  lines are kept intact.

 	Examples:

 	HTML CODE:
 	<form ...>
	 	<input type='text' 		name='a' id='a' value='24.5'/>
	 	<input type='text' 		name='b' id='b' value='mail@mail.co'/>
	 	<input type='checkbox'	name='c' id='c' value='3'/>
 	</form>

 	var validator = new FormValidator({
 		onFailure: function(id, message){
 			alert(id + ': ' + message);
 		}
 	});

 	validator.add({
		id:			['a','b','c'],
		type:		'required',
		message:	'the field is required'
 	})

 	validator.add({
 		id:	'a',
 		type: ['number','size:max=24.4'],
 		message:	'you must think of a number lower than 24.4'
 	})

 	validator.add({
 		id:		'b',
 		type:	'email',
 		event:	'keyup',
 		onFailure:	function(id){
			$(id).style.border = '1px solid #FF0000';
 		},
 		onSuccess:	function(id){
			$(id).style.border = '1px solid #00FF00';
 		}

 	})

 	function submit(){
 		if(validator.validateAll()){
 			form.submit();
 		}
 	}


 	Documentation:

 	methods:

 	initialize( Object arguments );	-	constructor for the validator
		form		[string optional]	-	id of the form to validate
		onSuccess	[handle(id) optional]	-	default callback for successfully validated element
		onFailure	[handle(id, message) optional]	-	default callback for unsuccessfully validated element
		onValidateAll[handle(ids) optional]	- called when validateAllMethod is called; ids - all the validated elements ids
		event		[string optional]	-	default event to observe

 	add( Object arguments );	- main method to add validaton to form items
 		id:			[string/array mandatory]	-	id of the element(s) to observe (trasmited to the onSuccess and onFailure callbacks)
 		type:		[string/array mandatory]	-	validator type/name ( go to 'Default validators' )
 		onSuccess:	[handle(id) optional]	-	if set, it overrides the callback from the constructor
		onFailure:	[handle(id, message) optional]	-	if set, it overrides the callback from the constructor
		parameters: [hash	optionsl]	-	parameters for the selected validator ( go to 'Default validators' )
		event:		[string optional]	-	event to observe ( besides the one from the constructor )
		message:	[string optional]	-	message transmited to the onFailure callback when a validation fails

	register (string name, handle validationHandle)		-	register a new validator
		name:				-	name of the new validator (Eg: 'mail') (overrides the validator whith the same name)
		validationHandle:	-	functinon of form handle(id, Object parameters) which returns false if the validation criteria fails on the $(id) element, otherwise true

	remove (string id)	-	removes validations from an element
		id:		-	the id of the element on wich to clear all validations

	validateAll()	-	master function which triggers all validations on all elements ( this can be called
						manually but it is also called automatically if the form parameter is set in the
						constructor and the onsubmit event is triggered by a button or an input
		@return boolean


/*--------------------------------------------------------------------------*/



var FormValidator = Class.create();

FormValidator.prototype = {
	initialize: function(arguments){		//constructor
		if(typeof(arguments) != 'object')
			arguments = {};
		this.form 		= arguments.form 		|| null;
		this.successCallback 	= arguments.onSuccess 		|| Prototype.emptyFunction;
		this.failureCallback 	= arguments.onFailure 		|| Prototype.emptyFunction;
		this.onValidateAllCallback = arguments.onValidateAll	|| Prototype.emptyFunction;
		this.event			= arguments.event 	|| 'none';						//or any other event: blur, change, mouseout, mouseover, etc


		if(this.form){
			Event.observe($(this.form),'submit',this.validateAll.bindAsEventListener(this));
		}


		this.validatorTypes = $H({
			email	: 	this.emailValidator,
			length	: 	this.lengthValidator,
			numeric	: 	this.numericValidator,
			number	: 	this.numberValidator,
			alpha	:	this.alphaValidator,
			alphanum:	this.alphaNumValidator,
			username:	this.usernameValidator,
			size	:	this.sizeValidator,
			required: 	this.requiredValidator,
			equals	: 	this.equalsValidator,
			regexp	:	this.regexpValidator,
			least	:	this.leastValidator
		})


		this.validators = $H({});
	},
	add: function (arguments){
		if(typeof($(arguments.id)) == 'undefined'){
			alert('id of the validator is mandatory');
			return;
		}
		if(typeof(arguments.type) == 'undefined'){
			alert('type of the validator is mandatory');
		}

		if(typeof(arguments.id) == 'string')
		{
			ids = [arguments.id];
		}else{
			ids = arguments.id;
		}

		if(typeof(arguments.type) == 'string')
		{
			types = [arguments.type];
		}else{
			types = arguments.type;
		}

		var parameters = arguments.parameters   || {};

		for(var jj = 0; jj < ids.length  ; jj++){
			id = ids[jj];
			for(var ii = 0; ii < types.length; ii++){
				type = types[ii];
				//split a string like 'length|max=20|min=10' into type: length and parameters = {max: 20, min: 10}
				var sep = type.indexOf('|');
				if(sep != -1){
					var paramBlock = type.split('|');
					type = paramBlock[0];
					for(var i = 1; i < paramBlock.length; i++ ){
						var eq = paramBlock[i].indexOf('=');
						var obj = new Object();
						obj[paramBlock[i].substr(0,eq)] = paramBlock[i].substr(eq+1);
						parameters = Object.extend(parameters, obj);
					}
				}

				//multiple validators on same element
				if(typeof(this.validators.get(id)) == 'undefined'){
					var validator = $H({
						id				:id,
						types				:[type],
						successCallback 		:arguments.onSuccess 	|| this.successCallback,
						failureCallback 		:arguments.onFailure 	|| this.failureCallback,
						parameters			:parameters   			|| {},
						event				:arguments.event 		|| this.event,
						message				:[arguments.message]	|| ['']
					})
				}else{
					var validator 	= this.validators.get(id);
					validator.set('event',arguments.event || validator.event);
					validator.get('types').push(type);
					validator.get('message').push(arguments.message);
					validator.set('parameters' , Object.extend(validator.get('parameters'),parameters));
				}
				this.validators.set(id,validator);
			};

			if(validator.event != 'none'){

				Event.observe(
				id,
				validator.event ,
				function(){this.validate(id)}
				);
			}
		}
	},
	remove:	function (key) {
		this.validators.unset(key);
	},
	validate: function ( id ){
		var validator = this.validators.get(id);
		for(ii = 0; ii < validator.get('types').length; ii++){
			if(this.validatorTypes.get( validator.get('types')[ii] )(id, validator.get('parameters') )){
				validator.get('successCallback')(id);
			}else{
				validator.get('failureCallback')(id, validator.get('message')[ii]);
				return false;
			}
		}
		return true;
	},
	validateAll: function (){
		var mkeys = this.validators.keys();
		this.onValidateAllCallback(mkeys);
		var result = true;
		for( var ii = 0 ; ii < mkeys.length; ii++){
			good = this.validate(mkeys[ii]);
			if(!good){
				result = false;						//dont't break beacause we want all errors
			}
		}
		return result;
	},
	forceSuccess: function( ids ){
		ids.each(
			function( id ){
				this.validators.get(id).get('successCallback')(id);
			}
		);
	},
	forceFailure: function( ids ){
		ids.each(
			function( id ){
				this.validators.get(id).get('failureCallback')(id);
			}
		);
	},
	register: function(name, validationMethod){
		this.validatorTypes.set(name,validationMethod);

	},
	/*********************** IMPLICIT VALIDATORS ******************************/
	emailValidator: function(id,parameters){
		emailStr  = $(id).value;
		if(!emailStr.length){		//don't verify empty strings
			return true;
		}
		var splitted = emailStr.match("^(.+)@(.+)$");
		if(splitted == null){
			return false;
		}
		if(splitted[1] != null )
		{
			var regexp_user=/^\"?[\w-_\.]*\"?$/;
			if(splitted[1].match(regexp_user) == null)
			{
				return false;
			}
		}
		if(splitted[2] != null)
		{
			var regexp_domain=/^[\w-\.]*\.[A-Za-z]{2,4}$/;
			if(splitted[2].match(regexp_domain) == null)
			{
				var regexp_ip =/^\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\]$/;
				if(splitted[2].match(regexp_ip) == null){
					return false;
				}
			}
			return true;
		}
		return false;
	},
	lengthValidator: function(id, parameters){
		if(	parameters['max'] && $(id).value.length > parameters['max'] ){
			return false;
		}
		if(	parameters['min'] && $(id).value.length < parameters['min'] ){
			return false;
		}
		return true;
	},

	numericValidator: function(id, parameters){
		var num_regex = /[^0-9]/;
		var charpos = $(id).value.search(num_regex);
		if($(id).value.length > 0 && charpos >= 0){
			return false;
		}
		return true;
	},
	numberValidator: function(){
		var value = $(id).value;
		if(value.length > 0 && isNaN(value)){
			return false;
		}
		return true;
	},
	alphaValidator: function(id, parameters){
		var num_regex = /[^a-z]/i;
		var charpos = $(id).value.search(num_regex);
		if($(id).value.length > 0 && charpos >= 0){
			return false;
		}
		return true;
	},

	alphaNumValidator: function(id, parameters){
		var num_regex = /[a-z]{1}[^a-z0-9]/i;
		var charpos = $(id).value.search(num_regex);
		if($(id).value.length > 0 && charpos >= 0){
			return false;
		}
		return true;
	},
	//Validates a username that starts with a letter and is made of a-z0-9_ chars
	usernameValidator: function(id, parameters){
		var pattern = /^[a-z]+[a-z0-9_]*$/i;

		if(!$(id).value.match(pattern)){
			return false;
		}
		return true;
	},
	sizeValidator:	function(id, parameters){
		console.log(parameters);
		var val = parseFloat($(id).value);

		if(val.length == 0 || isNaN(val)){
			return false;
		}
		if(typeof(parameters.max) != 'undefined' && val > parameters.max){
			return false;
		}
		if(typeof(parameters.min) != 'undefined' && val < parameters.min){
			return false;
		}
		return true;
	},
	requiredValidator: function(id, parameters){
		var element = $(id);
		var type = element.type;
		if(type == 'checkbox' && !element.checked){
			return false;
		}
		if(element.value.length == 0){
			return false;
		}
		return true;
	},
	equalsValidator: function(id, parameters){
		var element = $(id);
		if( element.value != $(parameters.id).value ){
			return false;
		}

		return true;
	},
	leastValidator: function(id, parameters){
		var elements = $A(document.getElementsByName(parameters.name));
		good = false;
		elements.each(
			function( el ){
				el = $(el);
				if( el.checked == true){
					good = true;
				}
			}
		);
		return good;

	},
	regexpValidator: function(id, parameters){
		if($(id).value.length > 0)
		{
			var reg;
			if(typeof(parameters['modifier']) != 'undefined'){
				reg = new RegExp(parameters['pattern'], parameters['modifier']);
			}else{
				reg = new RegExp(parameters['pattern']);
			}
			if(!reg.test($(id).value))
			{
				return false;
			}

		}
		return true;
	}
}

