﻿jQuery.fn.validate = function(options) {
	var validator = new jQuery.validator(options);
	
	if( this.is('form') ) {
		// validate the form on submit
		return this.submit(validator.validateForm);
	} else {
		// validate all elements immediately
		this.each(function() {
		    var element = this;
			validator.hideElementErrors(element);
			validator.validateElement(element);
		});
		validator.showErrors();
	}
	return this;
};

jQuery.validator = function(options) {

	var v = this;
	
	this.errorList = {};
	this.currentForm;
	this.rules = jQuery.validator.rules;
	this.utility = jQuery.validator.utility;
	this.settings = {
		parameterDelimiter: ":",
		rulesClassStart: "$v(",
		rulesClassEnd: ")",
		rulesDelimiter: ",",
		errorLabelClass: "error",
		errorContainer: $('div.alert'),
		debug: false,
		focusInvalidElement: false,
		selectErrorNests: false
	};
	
	// override defaults with client settings
	if(options) {
		jQuery.extend(this.settings, options);
	}
	
	if(v.settings.errorContainer) {
		v.settings.errorContainer.hide();
		jQuery("li", v.settings.errorContainer).hide();
	}

	v.validateForm = function(submitEvent) {
		if(v.settings.debug) {
			submitEvent.preventDefault();
		}
		
		// reset errors
		v.errorList = {};
		v.currentForm = this;

		var context = this;
		
		var errorContainer = v.settings.errorContainer;
		if(errorContainer) {
			errorContainer.hide();
			jQuery("li", errorContainer).hide();
//			context = errorContainer;
		}
        
		// remove all error class in the labels for the form
        var labels = jQuery("label." + v.settings.errorLabelClass, v.currentForm).removeClass(v.settings.errorLabelClass);
		
		
		// hide all error labels for the form
/*		var labels = $("label." + v.settings.errorLabelClass, context).hide();
		if( v.settings.selectErrorNests ) {
			v.settings.selectErrorNests(labels).hide();
		}
*/	
		// select all valid inputs inside the form (no submit or reset buttons)
		// TODO: exchange with :input selector ASAP
		$(":input", v.currentForm)
		.not("[@type=button]")
		.not("[@type=submit]")
		.not("[@type=reset]")
		.each(function() {
			v.validateElement(this);
		});

		// check if the form is valid and return
		return v.isFormValid();
	};
	
	v.validateElement = function(element) {
	   
		var rules = this.findRules(element);
		for( var i=0, rule; rule = rules[i]; i++ ) {
			try {
				var value = $(element).val();
				if( this.rules[rule.name](value, element, rule.parameters, this.utility) ) {
					// add the error to the array of errors for the element
					var id = ( element.type.toLowerCase().match(/radio|checkbox/) ) ? element.name : element.id;
					//var id = element.id;					
					if(!id && options.debug) {
						alert("could not find id/name for element")
						console.error("could not find id/name for element, please check the element (see next line)");
						console.debug(element);
					}
					var list = this.errorList[id] || (this.errorList[id] = []);
					list[list.length] = rule.name;
					//if (element.type.toLowerCase().match(/radio|checkbox/)) {
					//    list[list.length] = element.name;
					//} else { 
					//    list[list.length] = "";
				   // }
				}
			} catch(e) {
				if(this.settings.debug) {
					alert("exception occured when checking element " + element.id
						 + ", check the '" + rule.name + "' test");
					console.error("exception occured when checking element " + element.id
						 + ", check the '" + rule.name + "' test");
				}
				throw e;
			}
		}
	};

};

jQuery.validator.prototype = {
    
	hideElementErrors: function(element) {
        var errorLabel = $("label." + this.settings.errorLabelClass + "[@for=" + element.id + "]").removeClass(this.settings.errorLabelClass);

/*		var errorLabel = $("label." + this.settings.errorLabelClass + "[@for=" + element.id + "]").hide();
		if( this.settings.selectErrorNests ) {
			this.settings.selectErrorNests(errorLabel).hide();
		}
*/	},
	
	isFormValid: function() {
		var count = 0;
		// iterate over properties and count them
		for( i in this.errorList ) {
			count++;
		}
		if(count == 0) {
			// form has no errors, submit it
			return true;
		} else {
			// form has errors, display them and do not submit
			this.showErrors();
			
			if(this.settings.errorContainer && ! this.settings.focusInvalidElement){
			    window.scroll(0,0);
			}    
			
			return false;
		}
	},

	showErrors: function() {
		if(this.settings.errorContainer) {
			this.settings.errorContainer.show();
		}
		first = true;
		for(var elementID in this.errorList) {
			if( first && this.settings.focusInvalidElement ) {
				// focus the first invalid element
				// does not work with elementID being a name
				try {
					jQuery("#"+elementID)[0].focus();
				} catch(e) { if( this.settings.debug ) console.error(e) }
				first = false;
			}
			// display the error label for the first failed test
			this.showErrorLabel(elementID, this.errorList[elementID][0]);
		}
	},
	
	showErrorLabel: function(elementID, rule) {
        var context = this.currentForm || document;
        
        var errorLabel = 0;
        //if (type != ""){
         //   var elements = $("#" + elementID).parents("form");
           //var elm =  elements.find('[@name=' + type + ']').filter(':checked');
            //errorLabel =.addClass(this.settings.errorLabelClass);
            //if (elm.checked == false) var errorLabel = $("input[@name=" + type + "]").parents("label").addClass(this.settings.errorLabelClass);
       // } else {
            errorLabel = $("label[@for=" + elementID + "]", context).addClass(this.settings.errorLabelClass);
       // }
       	//alert(elementID + " <> " + rule)
		jQuery("li."+ elementID, this.settings.errorContainer).each(function(){
			if (jQuery("span", this).attr("class") == rule )
			    jQuery(this).show();
		});
		
	},
	
	findRules: function(element) {
		//var rulesForElement = this.getAttributeRules(element) || this.getClassNameRules(element) || [];
		var rulesForElement = this.getClassNameRules(element) || [];
		var ruleObjects = [];
		for(var i=0, rule; rule = rulesForElement[i]; i++) {
			ruleObjects[i] = {};
			if( rule.indexOf(this.settings.parameterDelimiter) == -1 ) {
				// no parameters given, just take the string as name
				ruleObjects[i].name = rule;
			} else {
				// split the name and parameters (default delimiter is ":")
				var parameters = rule.split(this.settings.parameterDelimiter);
				ruleObjects[i].name = parameters[0];
				// remove the first element (the name) and take the rest as parameters
				ruleObjects[i].parameters = parameters.slice(1);
			}
		}
		return ruleObjects;
	},
	
	getClassNameRules: function(element) {
		var className = element.className;
		var start = className.indexOf(this.settings.rulesClassStart);
		var end = className.indexOf(this.settings.rulesClassEnd, start);
		if (start != -1 && end != -1){
		    var validate = className.substring(start+this.settings.rulesClassStart.length, end);
		   if(!validate)
			    return;
		    return validate.split(this.settings.rulesDelimiter);
		} else return;
	}
/*	,
	getAttributeRules: function(element) {
		var validate = element.getAttribute(this.settings.rulesAttribute);
		if(!validate)
			return;
		return validate.split(this.settings.rulesDelimiter);
	}
*/	
}

jQuery.validator.utility = {

	countChecked: function(element) {
	/*	var elements = $(element);
		while( !elements.get(0).nodeName.toLowerCase().match(/form|body/) ) {
			elements.parents();
		}*/
		var elements = jQuery(element).parents("form");
		return elements.find('[@name=' + element.name + ']').filter(':checked').length;
	},
	
	getLength: function(value, element) {
		var length;
		switch( element.nodeName.toLowerCase() ) {
		case 'select':
			length = this.getSelectedOptions(element).length;
			break;
		case 'input':
			switch( element.type.toLowerCase() ) {
			case 'checkbox':
				length = this.countChecked(element);
				break;
			default: 
				length = value.length;
			}
			break;
		default: 
			length = value.length;
		}
		return length;
	},

	getSelectedOptions: function(select) {
		return jQuery("option:selected", select).get();
	},
	
	isRadioButtonSelected: function(radio) {
		var elements = document.getElementsByName(radio.name);
		for(var i=0, element; element = elements[i]; i++) {
			if(element.checked) {
				return true;
			}
		}
		return false;
	}
}