/*

validate(field)						Public function to validate a particular field
getInvalid()						Return an array of invalid objects {field: field reference, options: options object}




*/

var FormValidator = new Class({
	Implements: Options,
	
	options: {
		classValid: false,
		classInvalid: false,
		classNeutral: false,
		
		fieldEffectOptions: {duration: 300},
		
		required: {type: "required", re: /[^.*]/, msg: "This field is required."},
		alpha: {type: "alpha", re: /^[a-z ._-]+$/i, msg: "This field accepts alphabetic characters only."},
		alphanum: {type: "alphanum", re: /^[a-z0-9 ._-]+$/i, msg: "This field accepts alphanumeric characters only."},
		integer: {type: "integer", re: /^[-+]?\d+$/, msg: "Please enter a valid integer."},
		real: {type: "real", re: /^[-+]?\d*\.?\d+$/, msg: "Please enter a valid number."},
		date: {type: "date", re: /^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$/, msg: "Please enter a valid date (mm/dd/yyyy)."},
		email: {type: "email", re: /^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i, msg: "Please enter a valid email."},
		phone: {type: "phone", re: /^[\d\s ().-]+$/, msg: "Please enter a valid phone."},
		phonenumber: {type: "phonenumber", re: /^\(\d{3}\)[-\s.]\d{3}[.-]\d{4}$/, msg: "Please enter a valid phone."},
		creditcard: {type: "creditcard", re: /^((4\d{3})|(5[1-5]\d{2})|(6011))-?\d{4}-?\d{4}-?\d{4}|3[4,7]\d{13}$/, msg: "This is not a valid credit card number"},
		zipcode: {type: "zipcode", re: /^\d{5}$|^\d{5}-\d{4}$/, msg: "This is not a valid zip code"},
		postalcode: {type: "postalcode", re: /^[a-zA-Z0-9-]+$/, msg: "This is not a valid zip code"},
		confirm: {type: "confirm", msg: "Confirm Password does not match original Password."},
		expiration: {type: "expiration", re: /^[0-3][0-9]\/[0-1][0-9]$/, msg: "This is not a valid date."},

		onValid: Class.empty,
		onInvalid: Class.empty,
		onNeutral: Class.empty,
		
		onFormValid: Class.empty,
		onFormInvalid: Class.empty
	},
	
	initialize: function(form, options){
		
		this.form = $(form);
		this.setOptions(options);
		this.validations = new Array();

		this.fields = this.form.getElements("*[class^=validate]");
		
		this.fields.each(function(el) {
			if(!this._isChildType(el)) el.setStyles(this.options.styleNeutral);
			el.cbErr = 0;
			var classes = el.get('class').split(' ');
			classes.each(function(klass) {
				if(klass.match(/^validate(\[.+\])$/)) {
					var aFilters = eval(klass.match(/^validate(\[.+\])$/)[1]);
					for(var i = 0; i < aFilters.length; i++) {
						if(this.options[aFilters[i]]) this.register(el, this.options[aFilters[i]]);
						if(aFilters[i].charAt(0) == '=') this.register(el, $extend(this.options.confirm, {idField: aFilters[i].substr(1)}));
					}
				}
			}.bind(this));
		}.bind(this));
		
		// clean up classes.
		if(this.options.classValid && this.options.classValid.substr(0, 1) != '.'){
			this.options.classValid = '.'+this.options.classValid;
		}
		if(this.options.classInvalid && this.options.classInvalid.substr(0, 1) != '.'){
			this.options.classInvalid = '.'+this.options.classInvalid;
		}
		if(this.options.classNeutral && this.options.classNeutral.substr(0, 1) != '.'){
			this.options.classNeutral = '.'+this.options.classNeutral;
		}
		
		this.form.addEvents({
			"submit": this._onSubmit.bind(this),
			"reset": this._onReset.bind(this)
		});
		
	},
	
	register: function(field, options) {
		this.unregister(field, options);
		options = $extend({suspended: false}, options);
		field = $(field);
		this.validations.push([field, options]);
		field.addEvent("blur", function() {
			//alert('blur: '+field.get('id'));
			var fieldValid = this._validate(field, options);
			this._validateForm(fieldValid, false);
		}.bind(this));
		field.addEvent("keyup", function(e) {
			if(!e.key.shift && e.key != "tab"){
				var fieldValid = this._validateValid(field, options);
				this._validateForm(fieldValid, false);
			}
		}.bind(this));
		field.set('morph', this.options.fieldEffectOptions);
		this._onNeutral(field, options);
	},
	
	unregister: function(field, options){
		var field = $(field);
		this.validations.each(function(el, i, array){
			if(el[0] == field){
				if(!options || (options && options == el[1])){
					array.splice(i, 1);
				}
			}
		});
	},
	
	suspend: function(fields){
		if($type(fields) != 'array') fields = [fields];
		fields.each(function(el){
			var validation = this.getValidation(el);
			validation[1].suspended = true;
			this._onNeutral(validation[0], validation[1]);
		}.bind(this));
	},
	
	unsuspend: function(fields){
		if($type(fields) != 'array') fields = [fields];
		fields.each(function(el){
			var validation = this.getValidation(el);
			validation[1].suspended = false;
		}.bind(this));
	},
	
	getValidation: function(field, options){
		var field = $(field);
		var validation = false;
		if(field){
			this.validations.each(function(el, i, array){
				if(el[0] == field){
					if(!options || (options && options == el[1])){
						validation = el;
					}
				}
			});
		}
		return validation;
	},
	
	getInvalid: function(){		// get an array of the invalid objects
		var items = new Array();
		this.validations.each(function(el, i, array){
			if(!this._checkValid(el[0], el[1])){
				items.push(el);
			}
		}.bind(this));
		return items;
	},

	_isChildType: function(el) {
		var elType = el.type.toLowerCase();
		if((elType == "radio") || (elType == "checkbox")) return true;
		return false;
	},
	
	_checkValid: function(field, options){
		switch(options.type) {
			case "confirm":
				if($(options.idField).getValue() == field.get('value')) this._onValid(field, options);
				else this._onInvalid(field, options);
				break;
			default:
				if(options.suspended == true){
					return "suspended";
				}
				if(options.re.test(field.get('value'))){
					return true;
				}else{	
					return false;
				}
		}
		return false;
	},
	
	_validate: function(field, options) {
		var fieldValid = this._checkValid(field, options);
		
		switch(fieldValid){
			case "suspended":
				this._onNeutral(field, options);
				return true;
				break;
			case true:
				this._onValid(field, options);
				return true;
				break;
			case false:
				this._onInvalid(field, options);
				return false;
				break;
		}
	},
	_validateValid: function(field, options) {
		var fieldValid = this._checkValid(field, options);
		
		switch(fieldValid){
			case "suspended":
				this._onNeutral(field, options);
				return true;
				break;
			case true:
				this._onValid(field, options);
				return true;
				break;
		}
	},
	
	validate: function(field){
		var a = this.getValidation(field);
		if(a) a = this._validate(a[0], a[1]);
		return a;
	},
	
	_validateForm: function(force, applyField){
		if(force === false){
			this.isValid = false;
		}else{
			this.isValid = true;
			this.validations.each(function(array) {
				// if applyFields = false don't show field notices (exception for on blur)
				if(applyField === false) fieldValid = this._checkValid(array[0], array[1]);
				// otherwise validate as normal (on submit);
				else fieldValid = this._validate(array[0], array[1]);
				
				if(!fieldValid) this.isValid = false;
			}.bind(this));
		}
		
		if(this.isValid){
			this.options.onFormValid();
		}else{
			this.options.onFormInvalid();
		}
	},
	
	_onNeutral: function(field, options){
		if(this.options.classNeutral)
			field.set('class', this.options.classNeutral);
		if(this.options.onNeutral)
			this.options.onNeutral(field, options);
	},
	_onValid: function(field, options){
		if(this.options.classValid)
			field.morph(this.options.classValid);
		this.options.onValid(field, options);
	},
	_onInvalid: function(field, options){
		if(this.options.classInvalid)
			field.morph(this.options.classInvalid);
		this.options.onInvalid(field, options);
	},
	
	
	_onSubmit: function(event) {
		if(event){
			var event = new Event(event);
		}
		
		this._validateForm();
		
		if(!this.isValid && event != false){
			event.stop();
		}else{
			return this.isValid;
		}
	},

	_onReset: function() {
		this.validations.each(function(array) {
			//if(!this._isChildType(array[0])) array[0].setStyles(this.options.styleNeutral);
			//array[0].cbErr = 0;
			this._onNeutral(array[0], array[1]);
		}.bind(this));
	}

	
});