AVE.Validation = Class.create({	
	patterns: {
		required: function(value, bool) {
			return value.strip().length;
		},
		
		isExactly: function(value, len) {
		    if(!value) {
		        return true;
		    } else {
			    if(value.strip().length) {
				    if(value.length == len) return true;
				    else return false;
			    } else {
				    return true;
			    }
			}
		},
		
		maxLength: function(value, len) {
		    if(!value) {
		        return true;
		    } else {
			    if(value.strip().length) {
				    if(value.length <= len) return true;
				    else return false;
			    } else {
				    return true;
			    }
			}
		},
		
		
		isNumeric: function(value, lens) {
		    if(!value) {
		        return true;
		    } else {
	    		if(value.strip().length) {
	    		    if(!lens[0] && !lens[1]) {
	    		        var reg = new RegExp("^(\\d)*(\\.(\\d)+)?$");
	    		    } else if(lens[0] && !lens[1]) {
	    		        var reg = new RegExp("^(\\d){1," + lens[0] + "}$");
	    		    } else if(!lens[0] && lens[1]) {
	    		        var reg = new RegExp("^(0)?\\.(\\d){1," + lens[1] + "}$");
	    		    } else if(lens[0] && lens[1]) {
	    		        var reg = new RegExp("^(\\d){0," + lens[0] + "}(\\.(\\d){1," + lens[1] + "})?$");
	    		    } 
	    		    return reg.test(value);
		    	} else {
		    	    return true;
		    	}
		    }
		},
		
		isInteger: function(value, len) {
		    if(!value) {
		        return true;
		    } else {
		        if(!len[0]) return (/^\d*$/).test(value);
		        else return this.isNumeric(value, [len[0],0]);
		    }
		},
		
		oneRequired: function(value, els) {
			var lens = els.collect(function(el){
				return this.required($F(el));
			}.bind(this)).without(0);
			if(!lens.length && !this.required(value)) return false;
			else return true;
		},
		
		matches: function(value, els) {
			var values = els.collect(function(el){
				return $F(el);
			});
			values.push(value);			
			var len = values.length;

			values = values.findAll(function(v){ return value == v; });
			var len2 = values.length;
			if(len != len2) return false;
			else return true;
		},
		
		pattern: function(value, reg) {
			if(!value) {
			    return true;
			} else {
			    if(reg.test(value)) return true;
			    else return false;
			}
		},
		
		isDate: function(value, bool) {
			var reg = /(0[1-9]|1[012])[- \/\.](0[1-9]|[12][0-9]|3[01])[- \/\.](19|20)?\d\d/;
			if(this.pattern(value, reg)) return true;
			else return false;
		}/*,
		
		isBetweenDates: function(value, start, end) {
		    if(!value) {
		        return true;
		    } else {
		        var insideDate = new Date();
		        var startDate = new Date();
		        var endDate = new Date();
		        var insideDateArr = value.split('/');
		        var startDateArr = start.split('/');
		        var endDateArr = end.split('/');
		        insideDate.setDate(insideDateArr[0]);
		        insideDate.setMonth(insideDateArr[1]);
		        insideDate.setYear(insideDateArr[2]);
		        startDate.setDate(startDateArr[0]);
		        startDate.setMonth(startDateArr[1]);
		        startDate.setYear(startDateArr[2]);
		        endDate.setDate(endDateArr[0]);
		        endDate.setMonth(endDateArr[1]);
		        endDate.setYear(endDateArr[2]);
		        var insideDate = insideDate.toGMTString();
		        var insideDate = Date.parse(insideDate);
		        var startDate = startDate.toGMTString();
		        var startDate = Date.parse(startDate);
		        var endDate = endDate.toGMTString();
		        var endDate = Date.parse(endDate);
		        
		        if( insideDate >= startDate && insideDate <= endDate) return true;
		        else return false;
		    }
		}*/
	},
	
	initialize: function(el, rules, options) {
		this.options = Object.extend(Object.clone(AVE.Validation.defaults), options || {});
		
		if(!rules.length || !el) {
			throw('Please supply a DOM element and a rule set.');
		}

		this.element		= $(el);
		this.validators		= $A(rules).compact();
		this.errors			= [];
		this.errorIds		= [];
		this.afterInsertion	= this.options.afterInsertion;
		this._onEvent = this.validate.bindAsEventListener(this);
		this.element.observe(this.options.eventType, this._onEvent);
	},
	
	createErrorContainer: function() {
		var wrap = $(document.body).select(this.options.insertPoint)[0] || $(this.options.insertPoint) || this.element;
		this.container = new Element(this.options.container).addClassName(this.options.errorClassName);
		
		var insertion = {};
		insertion[this.options.insertion] = this.container;
		$(wrap).insert(insertion);
		
		if(this.options.title) this.container.insert({top: '<' + this.options.titleTag + '>' + this.options.title + '</' + this.options.titleTag + '>'});
		if(this.options.innerContainer) this.container.insert({bottom: new Element(this.options.innerContainer)});
		
		if(Element.round && this.options.roundEdges) this.container = this.container.round();
	},
	
	validate: function(e) {
		this.validators.each(function(validator) { this.validateElement(validator);}.bind(this));
		if(!this.checkErrors()) {
		    e.stop();
		} else {
		    var evt = this.element.fire('form:validated', {submitEvent: e});
		    if(evt.stopped) e.stop();
		}
	},
	
	validateElement: function(validator) {
	    	var id		= validator.id;
	    	var value 	= ($(id)) ? $F(id) : null;
	    	if(value !== null) {
	    	    $H(validator.rules).each(function(rule) {
	    		    this.checkRule(id, value, rule); 
	    	    }.bind(this));
	    	}
	},
	
	checkRule: function(id, value, rule) {
		var valid = this.patterns[rule.key](value, rule.value);
		if(!valid) {
			var message = this.validators.findAll(function(v){
				return v.id == id;
			}).find(function(v){
				return $H(v.messages).any(function(pair){ return pair.key == rule.key; });
			}).messages[rule.key];
			this.errors.push(message);
			this.errorIds.push(id);
		}
	},
	
	checkErrors: function() {
		if(this.errors.length) {
			this.cleanPreviousErrors();
			this.createErrorContainer();
			this.displayErrors();
			this.highlightFields();
			this.container.scrollTo();
			this.afterInsertion(); 
			return false;
		} else {
			return true;
		}
	},
	
	cleanPreviousErrors: function() {
		//if (this.container) this.container.remove();
		$$('.error').invoke('remove');
		$$('.' + this.options.alertClassName).invoke('removeClassName', this.options.alertClassName);	
	},
	
	displayErrors: function() {
		if(this.errors.length) {
			var error = this.errors.pop();
			var insertPoint = (this.options.innerContainer) ? this.container.down(this.options.innerContainer) : this.container;
			insertPoint.insert({top : '<' + this.options.style + '>' + error + '</' + this.options.style + '>'});
			this.displayErrors();
		}
	},
	
	highlightFields: function() {
		if(this.errorIds.length) {
			var err = $(this.errorIds.pop());
			if(err.up(this.options.alertPoint)) err.up(this.options.alertPoint).addClassName(this.options.alertClassName);
			this.highlightFields();
		}
	}
});

AVE.Validation.defaults = {
		style: 'li',
		container: 'div',
		innerContainer: 'ul',
		title: "Sorry! We ran into a problem.",
		titleTag: 'h3',
		errorClassName: 'error',
		alertClassName: 'ave-element-alert',
		alertPoint: '.field-rim',
		insertPoint: false,
		insertion: 'top',
		eventType: 'submit',
		roundEdges: false,
		afterInsertion: Prototype.emptyFunction
};

AVE.Validation.onPageLoad = function() {
	// This will always add the default className to errors
	// If you override the option in AVE.Validator, the styling could look different
	if($$('.error li')) {
		var errors = $$('.error li')
			.map(function(li){
					return li.readAttribute('element'); 
				})
			.compact()
			.each(function(el){
				if($(el) && $(el).up(AVE.Validation.defaults.alertPoint)) 
					$(el).up(AVE.Validation.defaults.alertPoint).addClassName(AVE.Validation.defaults.alertClassName);
			});
	}
}

$(document).observe("dom:loaded", AVE.Validation.onPageLoad);

/*
AVE.Validation.elements = [{ 
	id: "Zip",
	rules: {
		oneRequired: ['']
		isExactly: 5,
		isNumeric: true
	},
	messages: {
		oneRequired; "You must enter a zip code or outlet name.",
		isExactly: "Zip codes must be exactly 5 digits.",
		isNumeric: "Zip codes should only contain numbers."
	}
}];
*/

