var FormCheck = new Class({

	Implements: [Options, Events],

	options : {
		submit : true,						//false : just validate the form and do nothing else. Use onValidateSuccess event to execute some code
		submitAction: false,				//Action page used to submit the form data to.
		submitMethod: false,				//Method used to submit the form, valid options : 'post' or 'get'

		trimValue : false,					//trim (remove whitespaces before and after) the value
		validateDisabled : false,			//skip validation on disabled input if set to false.

		submitByAjax : false,				//false : standard submit way, true : submit by ajax
		ajaxResponseDiv : false,			//element to inject ajax response into (can also use onAjaxSuccess) [cronix]
		onAjaxRequest : $empty,				//Function to fire when the Request event starts
		onAjaxComplete : $empty,			//Function to fire when the Request is complete, before and regardless of Success or Failure
		onAjaxSuccess : $empty,				//Function to fire when the Request receives .  Args: response [the request response] - see Mootools docs for Request.onSuccess
		onAjaxFailure : $empty,				//Function to fire if the Request fails

		onSubmit		  : $empty,			//Function to fire when user submit the form
		onValidateSuccess : $empty,			//Function to fire when validation pass
		onValidateFailure : $empty,			//Function to fire when validation fails

		display : {
			showErrors : 0,
			titlesInsteadNames : 0,
			errorsLocation : 1,
			indicateErrors : 1,
			indicateErrorsInit : 0,
			keepFocusOnError : 0,
			checkValueIfEmpty : 1,
			addClassErrorToField : 0,
			removeClassErrorOnTipClosure : 0,
			replaceTipsEffect : 1,
			flashTips : 0,
			closeTipsButton : 1,
			tipsPosition : "right",
			tipsOffsetX : -45,
			tipsOffsetY : 0,
			listErrorsAtTop : false,
			scrollToFirst : true,
			fadeDuration : 300
		},

		alerts : {
			required : "This field is required.",
			alpha : "This field accepts alphabetic characters only.",
			alphanum : "This field accepts alphanumeric characters only.",
			nodigit : "No digits are accepted.",
			digit : "Please enter a valid integer.",
			digitltd : "The value must be between %0 and %1",
			number : "Please enter a valid number.",
			email : "Please enter a valid email.",
			image : 'This field should only contain image types',
			phone : "Please enter a valid phone.",
			phone_inter : "Please enter a valid international phone number.",
			url : "Please enter a valid url.",

			confirm : "This field is different from %0",
			differs : "This value must be different of %0",
			length_str : "The length is incorrect, it must be between %0 and %1",
			length_fix : "The length is incorrect, it must be exactly %0 characters",
			lengthmax : "The length is incorrect, it must be at max %0",
			lengthmin : "The length is incorrect, it must be at least %0",
			words_min : "This field must concain at least %0 words, currently: %1 words",
			words_range : "This field must contain %0-%1 words, currently: %2 words",
			words_max : "This field must contain at max %0 words, currently: %1 words",
			checkbox : "Please check the box",
			checkboxes_group : 'Please check at least %0 box(es)',
			radios : "Please select a radio",
			select : "Please choose a value",
			select_multiple : "Please choose at least one value"
		},

		regexp : {
			required : /[^.*]/,
			alpha : /^[a-z ._-]+$/i,
			alphanum : /^[a-z0-9 ._-]+$/i,
			digit : /^[-+]?[0-9]+$/,
			nodigit : /^[^0-9]+$/,
			number : /^[-+]?\d*\.?\d+$/,
			email : /^([a-zA-Z0-9_\.\-\+%])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
			image : /.(jpg|jpeg|png|gif|bmp)$/i,
			phone : /^\+{0,1}[0-9 \(\)\.\-]+$/, // alternate regex : /^[\d\s ().-]+$/,/^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/
			phone_inter : /^\+{0,1}[0-9 \(\)\.\-]+$/,
			url : /^(http|https|ftp)\:\/\/[a-z0-9\-\.]+\.[a-z]{2,3}(:[a-z0-9]*)?\/?([a-z0-9\-\._\?\,\'\/\\\+&amp;%\$#\=~])*$/i
		}
	},

	/*
	Constructor: initialize
		Constructor

		Add event on formular and perform some stuff, you now, like settings, ...
	*/
	initialize : function(form, options) {
		this.formo=form;
		this.formo.options.autosubmit=false; 
		
		if (this.form = form.form) {
			this.form.isValid = true;
			this.regex = ['length'];
			this.groups = {};

			//internalization
			if (typeof(formcheckLanguage) != 'undefined') this.options.alerts = $merge(this.options.alerts, formcheckLanguage);

			this.setOptions(options);

			this.form.setProperty('action',
				this.options.submitAction || this.form.getProperty('action') || 'post');

			this.form.setProperty('method',
				this.options.submitMethod || this.form.getProperty('method') || '');

			this.validations=[];
			this.errortips=[];
			
			this.alreadyIndicated = false;
			this.firstError = false;

			$H(this.options.regexp).each(function(el, key) {
				this.regex.push(key);
			}, this);

			/*this.form.getElements("*[class*=validate]").each(function(el) {
				this.register(el);
			}, this);*/

			this.form.addEvents({
				"submit": this.onSubmit.bind(this)
			});

			document.addEvent('mousewheel', function(){
				this.isScrolling = false;
			}.bind(this));

			if (this.options.display.indicateErrorsInit) {
				this.validations.each(function(el) {
					if(!this.manageError(el,'submit')) this.form.isValid = false;
				}, this);
			}
		}
	},

	/*
	Function: validate
		Private method

		Dispatch check to other methods
	*/
	validate : function(input) {
		var errors=[];
		
		if (this.options.trimValue && input.el.value) input.el.value=input.el.value.trim();

		input.validators.each(function(rule) {
			var ruleArgs=[];
			var ruleMethod=rule;

			if(rule.match(/^.+\[/)) {
				ruleMethod=rule.split('[')[0];
				ruleArgs=eval(rule.match(/^.+(\[.+\])$/)[1].replace(/([A-Z0-9\._-]+)/i, "'$1'"));
			}
			
			if (this.regex.contains(ruleMethod) && input.el.get('tag')!="select") {
				errors.append(this.validateRegex(input.el, ruleMethod, ruleArgs));
			}
			
			if (ruleMethod == 'required' && (input.el.get('tag') == "select" || input.el.type == "checkbox")) {
				errors.append(this.simpleValidate(input.el));
			}
			
				/*if (rule.match(/confirm:.+/)) {
					ruleArgs = [rule.match(/.+:(.+)$/)[1]];
					if (this.validateConfirm(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				if (rule.match(/differs:.+/)) {
					ruleArgs = [rule.match(/.+:(.+)$/)[1]];
					if (this.validateDiffers(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}
				if (ruleMethod == 'words') {
					if (this.validateWords(el, ruleArgs) == false) {
						el.isOk = false;
					}
				}*/
				
			if(rule.match(/%[A-Z0-9\._-]+$/i) || (errors.length==0 && rule.match(/~[A-Z0-9\._-]+$/i))) {
				errors.append(eval(rule.slice(1)+'(input.el)'));
			}
			
			input.el.fireEvent('validate', [errors]);
		}, this);
		
		return errors;
	},

	/*
	Function: simpleValidate
		Private method

		Perform simple check for select fields and checkboxes
	*/
	simpleValidate : function(el) {
		var errors=[];
		
		if(el.get('tag') == 'select'){
			if(!el.multiple) {
				if(!el.value) {
					errors.push(this.options.alerts.select);
				}
			} else {
				var selected = false;
				el.getChildren('option').each(function(el){
					if(el.selected) selected = true;
				});

				if(!selected){
					errors.push(this.options.alerts.select_multiple);
				}
			}
		} else if (el.type == "checkbox" && el.checked == false) {
			errors.push(this.options.alerts.checkbox);
		}
		
		return errors;
	},

	/*
	Function: validateRegex
		Private method

		Perform regex validations
	*/
	validateRegex : function(el, ruleMethod, ruleArgs) {
		var errors=[];
		
		var msg = "";
		
		if (ruleMethod=='length' && ruleArgs[1]) {
			if (ruleArgs[1] == -1) {
				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +",}$");
				msg = this.options.alerts.lengthmin.replace("%0",ruleArgs[0]);
			} else if(ruleArgs[0] == ruleArgs[1]) {
				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +"}$");
				msg = this.options.alerts.length_fix.replace("%0",ruleArgs[0]);
			} else {
				this.options.regexp.length = new RegExp("^[\\s\\S]{"+ ruleArgs[0] +","+ ruleArgs[1] +"}$");
				msg = this.options.alerts.length_str.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
			}
		} else if (ruleArgs[0] && ruleMethod == 'length') {
			this.options.regexp.length = new RegExp("^.{0,"+ ruleArgs[0] +"}$");
			msg = this.options.alerts.lengthmax.replace("%0",ruleArgs[0]);
		} else {
			msg = this.options.alerts[ruleMethod];
		}
		
		if ((ruleMethod == 'digit' || ruleMethod == 'number') && ruleArgs[1]) {
			var valueres, regres = true;
			
			if (!this.options.regexp[ruleMethod].test(el.value)) {
				errors.push(this.options.alerts[ruleMethod]);
				regres = false;
			}
			
			if (ruleArgs[1] == -1) {
				valueres = ( el.value.toFloat() >= ruleArgs[0].toFloat() );
				msg = this.options.alerts.digitmin.replace("%0",ruleArgs[0]);
			} else {
				valueres = ( el.value.toFloat() >= ruleArgs[0].toFloat() && el.value.toFloat() <= ruleArgs[1].toFloat() );
				msg = this.options.alerts.digitltd.replace("%0",ruleArgs[0]).replace("%1",ruleArgs[1]);
			}
			
			if (regres == false || valueres == false) {
				errors.push(msg);
				return false;
			}
		} else if (this.options.regexp[ruleMethod].test(el.value) == false)  {
			errors.push(msg);
			return errors;
		}
		
		return errors;
	},

	/*
	Function: validateConfirm
		Private method

		Perform confirm validations
	*/
	validateConfirm: function(el,ruleArgs) {
		var confirm = ruleArgs[0];
		if(el.value != this.form[confirm].value){
			var msg = ( this.options.display.titlesInsteadNames ) ?
				this.options.alerts.confirm.replace("%0",this.form[confirm].getProperty('title')) :
				this.options.alerts.confirm.replace("%0",confirm);
			el.errors.push(msg);
			return false;
		}
		return true;
	},

	/*
	Function: validateDiffers
		Private method

		Perform differs validations
	*/
	validateDiffers: function(el,ruleArgs) {
		var differs = ruleArgs[0];
		if(el.value == this.form[differs].value){
			var msg = ( this.options.display.titlesInsteadNames ) ?
				this.options.alerts.differs.replace("%0",this.form[differs].getProperty('title')) :
				this.options.alerts.differs.replace("%0",differs);
			el.errors.push(msg);
			return false;
		}
		return true;
	},

	/*
	Function: validateWords
		Private method

		Perform word count validation
	*/
	validateWords: function(el,ruleArgs) {
		var min = ruleArgs[0];
		var max = ruleArgs[1];

		var words = el.value.replace(/[ \t\v\n\r\f\p]/m, ' ').replace(/[,.;:]/g, ' ').clean().split(' ');

		if(max == -1) {
			if(words.length < min) {
				el.errors.push(this.options.alerts.words_min.replace("%0", min).replace("%1", words.length));
				return false;
			}
		} else {
			if(min > 0)	{
				if(words.length < min || words.length > max) {
					el.errors.push(this.options.alerts.words_range.replace("%0", min).replace("%1", max).replace("%2", words.length));
					return false;
				}
			} else {
				if(words.length > max) {
					el.errors.push(this.options.alerts.words_max.replace("%0", max).replace("%1", words.length));
					return false;
				}
			}
		}
		return true;
	},


	/*
	Function: isFormValid
		public method

		Determine if the form is valid

		Return true or false
	*/
    isFormValid: function() {
		this.form.isValid = true;
		this.validations.each(function(el) {
			var validation = this.manageError(el,'testonly');
			if(!validation) this.form.isValid = false;
		}, this);
		return this.form.isValid;
	},

	/*
	Function: isChildType
		Private method

		Determine if the field is a group of radio, of checkboxes or not.
	*/
	isChildType: function(el, validators) {
		var validator;
		if($defined(el.type) && el.type == 'radio') {
			return true;
		} else if(validator = validators.join().match(/group(\[.*\])/)) {
			var group = eval(validator[1]);
			this.groups[group[0]] = this.groups[group[0]] || [];
			this.groups[group[0]][0] = this.groups[group[0]][0] || [];
			this.groups[group[0]][1] = group[1] || this.groups[group[0]][1] || 1;
			this.groups[group[0]][0].push(el);
			el.groupID = group[0];
			return true;
		}
		return false;
	},

	/*
	Function: validateGroup
		Private method

		Perform radios validations
	*/
	validateGroup : function(el) {
		el.errors = [];
		if(el.type == 'radio') {
			var nlButtonGroup = this.form[el.getProperty("name")];
			el.group = nlButtonGroup;
			var cbCheckeds = false;

			for(var i = 0; i < nlButtonGroup.length; i++) {
				if(nlButtonGroup[i].checked) {
					cbCheckeds = true;
				}
			}
			if(cbCheckeds == false) {
				el.errors.push(this.options.alerts.radios);
				return false;
			} else {
				return true;
			}
		// we have group of checkboxes
		} else if(el.type == 'checkbox') {
			//we get length of checked elements
			var checked = 0;
			this.groups[el.groupID][0].each(function(groupEl){
				if(groupEl.checked) checked++;
			});
			if(checked >= this.groups[el.groupID][1]) {
				return true;
			} else {
				( this.groups[el.groupID][0].length > 1 ) ?
					el.errors.push(this.options.alerts.checkboxes_group.replace('%0', this.groups[el.groupID][1])) :
					el.errors.push(this.options.alerts.checkbox);
				return false;
			}
		// we have unmanaged type
		} else {
			return false;
		}
	},

	/*
	Function: addError
		Private method

		Add error message
	*/
	showError: function(input, errors) {
		var tip=new BLT.Tip(errors[0], input.el);
		
		/*var blurEvent=function() { tip.close(); };
		
		tip.addEvent('show', function() { input.el.addEvent('blur', blurEvent); });
		tip.addEvent('destroy', function() { input.el.removeEvent('blur', blurEvent); });*/
		
		tip.show();
		
		this.errortips.push(tip);
		this.fireEvent('showError');
	},
	
	clearErrorTips: function() {
		this.errortips.each(function(tip) {
			if (tip.element) tip.destroy();
		}, this);
		
		this.errortips=[];
	},

	/*
	Function: addPositionEvent

		Update tips position after a browser resize
	*/
	addPositionEvent : function(obj) {
		if(this.options.display.replaceTipsEffect) {
			obj.event = function(){
				var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();
				new Fx.Morph(obj.element, {
					'duration' : this.options.display.fadeDuration
				}).start({
					'left':[obj.element.getStyle('left'), coord.right + this.options.display.tipsOffsetX],
					'top':[obj.element.getStyle('top'), coord.top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY]
				});
			}.bind(this);

		} else {
			obj.event = function(){
				var coord = obj.target ? $(obj.target).getCoordinates() : obj.getCoordinates();
				obj.element.setStyles({
					'left':coord.right + this.options.display.tipsOffsetX,
					'top':coord.top - obj.element.getCoordinates().height + this.options.display.tipsOffsetY
				});
			}.bind(this);
		}
		window.addEvent('resize', obj.event);
	},

	/*
	Function: focusOnError
		Private method

		Create set the focus to the first field with an error if needed
	*/
	focusOnError : function (input) {
		if (!this.isScrolling) {
			var dest=input.el.getCoordinates().top-70;
			this.isScrolling = true;

			if (window.getScroll().y != dest) {
				new Fx.Scroll(window, {
					onComplete : function() {
						this.isScrolling = false;
						if (input.el.getProperty('type') != 'hidden') input.el.focus();
					}.bind(this)
				}).start(0,dest);
			} else {
				this.isScrolling = false;
				input.el.focus();
			}
		}
	},

	getValidators: function(el) {
		var validators=[];
		
		el.getProperty("class").split(' ').each(function(classX) {
			if (classX.match(/^validate(\[.+\])$/)) {
				var cssvalidators= eval(classX.match(/^validate(\[.+\])$/)[1]);

				for(var i=0; i<cssvalidators.length; i++) {
					validators.push(cssvalidators[i]);
					
					/*if (validators[i].match(/^confirm:/)) {
						var field=validators[i].match(/.+:(.+)$/)[1];
						if (this.form[field].validation.contains('required')) el.validation.push('required');
					}
					if (validators[i].match(/^target:.+/)) {
						el.target = validators[i].match(/^target:(.+)/)[1];
					}*/
				}

				//we check if group is already registered
				/*el.isChild = this.isChildType(el, validators);
				if (el.isChild && el.type == 'radio') {
					this.validations.each(function(registeredEl){
						if (registeredEl.name == el.name) valid = false;
					}, this);
				}
				if (el.isChild && el.type == 'checkbox') {
					this.validations.each(function(registeredEl){
						if (registeredEl.groupID == el.groupID) valid = false;
					}, this);
				}

				if (position && position <= this.validations.length) {
					var newValidations = [];
					this.validations.each(function(valider, i){
						if (position == i+1 && valid) {
							newValidations.push(el);
							this.addListener(el);
						}
						newValidations.push(valider);
					}, this);
					this.validations = newValidations;
				} else if (valid) {
					this.validations.push(el);
					this.addListener(el);
				}*/
			}
		}, this);
		
		return validators;
	},
	
	getInputs: function() {
		var inputs=[];
		
		this.form.getElements("*[class*=validate]").each(function(el) {
			if (el.isVisible()) {
				inputs.push({
					el:	el,
					validators: this.getValidators(el)
				});
			}
		}, this);
		
		return inputs;
	},
	
	onSubmit: function(event) {
		var inputs=this.getInputs();
		
		this.clearErrorTips();
		this.fireEvent('onSubmit');

		var iserror=false;
		
		inputs.some(function(input) {
			var errors=this.validate(input);
			
			if (errors.length>0) {
				this.showError(input, errors);
				this.focusOnError(input);
				
				iserror=true;
				
				return true;
			}

			return false;		
		}, this);
		
		if (iserror) {
			this.fireEvent('validateFailure');			
		} else {
			this.fireEvent('validateSuccess');
			
			return this.formo.submit();
		}
		
		return false;
	}
});
