/*
 * jValidate - jQuery Plugin
 * version: 1.0 
 * Created by: Matt Null
 * Examples and documentation at: http://mattnull.com/jvalidate
 * c) 2010 Matt Null - www.mattnull.com
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 */

(function($) {
 
	/**** define defaults ****/
	var validation = {
		blank : "Please complete this field.",
		number : "Numbers only.",
		email : "Invalid e-mail.",
		phone : "Invalid phone number.",
		zip : "Invalid zip code.",
		url : "Invalid URL."
	},
	validation_classes = [
		'zip',
		'email',
		'phone',
		'url',
		'number',
		'forcewholenumber',
		'forcepercent',
		'forcecurrency'
	],
	regex = {
		//http://projects.scottsplayground.com/email_address_validation/
		email : /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i,
		//http://projects.scottsplayground.com/iri/
		url : /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i,
		phone : /^\([1-9]\d{2}\)\s?\d{3}\-\d{4}$/,
		zip: /(^\d{5}$)|(^\d{5}-\d{4}$)/
	},
	force_vals = {
		addCommas : false,
	},
	form_config = {
		validateEvent : 'submit',
		errorLabelOffset : 0,
		errorLabelType : '',
		errorLabelEffect : '',
		passwordMeter : false,
		weakBarColor : '#CC0033',
		mediumBarColor : '#3678FF',
		strongBarColor : '#20C233'
	},
	error_label = '<label class="error"></label>',
	strUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
	strLowerCase = "abcdefghijklmnopqrstuvwxyz",
	strNumbers = "0123456789",
	strCharacters = "!@#$%^&*?_~";

	function validate(el,config){

		var val = el.val(),
		error = false;

		if(el.next('label.error').length > 0){
			el.next('label.error').remove();			
		}
		
		//configure error label
		var error_el = $(error_label);
		var pos = el.position();

		el.after(error_el);	

		error_el.attr('for', $(el).attr('name'));
		error_el.css('display','none');
	
		if(el.hasClass('number')){
			var val = val.replace(/,/g,'').replace(/\$/g,'').replace(/%/g,'');	
			if(isNaN(val)){
				error_el.html(validation.number);
				error = true;
			}
		}
		else if(el.hasClass('email')){ //e-mail validation
			if(!regex.email.test(val)){
				error_el.html(validation.email);
				error = true;
			}
		}
		else if(el.hasClass('url')){ //url validation
			if(!regex.url.test(val)){
				error_el.html(validation.url);
				error = true;
			}
		}
		else if(el.hasClass('phone')){ //phone number validation
			if(!regex.phone.test(val)){
				error_el.html(validation.phone);
				error = true;
			}
		}
		else if(el.hasClass('zip')){ //zip code validation
			if(!regex.zip.test(val)){
				error_el.html(validation.zip);
				error = true;
			}
		}
		
		//if an item has an "error" class and there is no error, remove the class
		if(el.hasClass('error') && error == false){
			el.removeClass("error");
			error_el.html('');
			el.removeClass("error");
		}
		
		//do required validation
		error = (!error) ? checkRequired(el,error_el) : error;						
		
		//if there is an error, display error appropriatly
		if(error){		
			el.addClass("error");
			var top = pos.top - error_el.outerHeight() - config.errorLabelOffset;
			var left = pos.left + el.outerWidth() + config.errorLabelOffset;

			if(config.errorLabelType == 'tooltip'){
				error_el.addClass('jval-tooltip');
				error_el.css('position','absolute');
				error_el.css('top',top+'px');
				error_el.css('left',left+'px');	
			}
				
			//show error
			if(config.errorLabelEffect == 'fade'){
				error_el.fadeIn('slow');
			}
			else if(config.errorLabelEffect == 'slidedown'){
				error_el.slideDown('slow');
			}
			else if(config.errorLabelEffect == 'slidedownfade'){
				 error_el.animate({
				    width: ['show', 'swing'],
				    height: ['show', 'swing'],
				    opacity: 'show'
				  }, 1000, 'linear');
			}
			else{
				error_el.css('display','block');
			}
		}
		
		return !error;
	}
	
	function clearDupeValidation(el){
		for(var v_class in validation_classes){
			if(el.hasClass(validation_classes[v_class])){
				el.removeClass(validation_classes[v_class]);		
			}
		} 
	}
	
	function checkRequired(el,error_el){
		var val = el.val();
		var error = false;
		
		if(el.hasClass('required')){
			//is a checkbox/radio
			if(el.is(':radio') || el.is(':checkbox')){
				var el_name = el.attr('name'),
				items = $('[name='+el_name+']'),
				isChecked = false;
				
				items.each(function(){
					if($(this).is(':checked')){
						isChecked = true;
					}
				});
				
				if(!isChecked){
					error_el.html(validation.blank);
					error = true;
				}
			}
			else if(!val || val == ''){
				error_el.html(validation.blank);
				error = true;
			}
		}
		
		return error;
	}
	
	function addCommas(nStr)
	{
		nStr += '';
		x = nStr.split('.');
		x1 = x[0];
		x2 = x.length > 1 ? '.' + x[1] : '';
		var rgx = /(\d+)(\d{3})/;
		while (rgx.test(x1)) {
			x1 = x1.replace(rgx, '$1' + ',' + '$2');
		}
		return x1 + x2;
	}
	
	function validator(form,config){

		var errors = [],
		form = $(form);
		
		config.event = config.event || 'submit';
		
		form.find(':input.required').not(':submit, :reset, :image, [disabled]').filter(':input').each(function(){
			var el = $(this);
			
			if(config.event == 'blur'){
				$(this).blur(function(){
					if(!validate(el,config)){
						errors.push(1);
					}
				});
			}
			else{
				if(!validate(el,config)){
					errors.push(1);
				}
			}
		});

		if(errors.length > 0){
			form.addClass('hasErrors');
			return false;
		} 
		else if(form.hasClass('hasErrors')){
			form.removeClass('hasErrors');
		}
		
		return true;
	}
	
	function createPasswordMeter(el){
		var meter = $('<div class="jvalidate_meter"></div>'),
		bar  = '<div class="jvalidate_bar"></div>';
		
		meter.html(bar);
		el.after(meter);
	}
	
	function attachPasswordMeter(el,config){
		el.keyup(function(){calculatePasswordStrength(this,config);});
	}
	
	function calculatePasswordStrength(el,config){
	
		var password = $(el).val(),
		score = 0;
		if(password.length <1){
			score = 0;
		}
		//password length
		if(password.length <= 4){
		    score += 5;
		}
		else if(password.length >= 3 && password.length <= 7){
		    score += 10;
		}
		else if(password.length >= 8){
		    score += 25;
		}
		
		//characters
	    var upperCount = contains(password, strUpperCase);
	    var lowerCount = contains(password, strLowerCase);
	    var lowerUpperCount = lowerCount + upperCount;
	
	    if(upperCount == 0 && lowerCount != 0){ 
	        score += 10; 
	    }
	    // -- Letters are upper case and lower case
	    else if(upperCount != 0 && lowerCount != 0){ 
	        score += 20; 
	    }
	
	    // Numbers
	    var numberCount = contains(password, strNumbers);
	    // -- 1 number
	    if(numberCount == 1){
	        score += 10;
	    }
	    // -- 3 or more numbers
	    if(numberCount >= 3){
	        score += 20;
	    }
	
	    // Characters
	    var characterCount = contains(password, strCharacters);
	    // -- 1 character
	    if(characterCount == 1){
	        score += 10;
	    }   
	    // -- More than 1 character
	    if(characterCount > 1){
	        score += 25;
	    }
	
	    // -- Letters and numbers
	    if(numberCount != 0 && lowerUpperCount != 0){
	        score += 2;
	    }
	    // -- Letters, numbers, and characters
	    if(numberCount != 0 && lowerUpperCount != 0 && characterCount != 0){
	        score += 3;
	    }
	    // -- Mixed case letters, numbers, and characters
	    if(numberCount != 0 && upperCount != 0 && lowerCount != 0 && characterCount != 0){
	        score += 5;
	    }
	    
	    //styling stuff
	    var color = '';
		var width = 150;
		
		if(score < 50){
			color = config.weakBarColor;
		}
		else if(score >= 50 && score < 70){
			color = (config.mediumBarColor) ? config.mediumBarColor : config.strongBarColor;
		}
		else{
			color = config.strongBarColor;
		}
		
			if(password == ''){
			score = 0;
		}
		
		$('.jvalidate_bar').html(score+'%');
		$('.jvalidate_bar').css("background-color",color).css("width",score+"%");
	}
	
	//tests to see if a password contains anything within the given string
	function contains(password, str){
		var count = 0,
		i=0;
		
		for(i = 0; i < password.length; i++) {
		    if (str.indexOf(password.charAt(i)) > -1) { 
		        count++;
		    } 
		}	
		return count;
	} 
	/***** Public Methods ********************/
	/* format: $.fn.jvalidate.init();
	/*****************************************/
	
	jValidate = $.fn.jvalidate = $.jvalidate = function(params) {		
		params = params || {};

		if(params) var config = $.extend({},form_config, params);

		jValidate.init(this,config);
					
		if(config.validateEvent){
			 if(config.validateEvent == 'blur'){
			 	config.event = 'blur';
				validator(this,config);
			}
		}
	
		$(this).submit(function(){	
			config.event = 'submit'
			if(!validator(this,config)) return false;
		});
	};
	
	jValidate.init = function(form,config){
		var form = $(form);
		form.find(':input').not(':submit, :reset, :image, [disabled]').filter(':input').each(function(){
			var el = $(this);	
			if(el.is(':password') && config.passwordMeter == true){
				createPasswordMeter(el);
				attachPasswordMeter(el,config);
			}
		});

	}
	
	jValidate.forceValues = function(params){		
		if(params) var config = $.extend({},force_vals, params);
		
		var el = $(config.selector),
		val,
		prefix = '',
		suffix = '';
		
		//remove duplicate validation classes ex. email
		clearDupeValidation(el);
		
		el.addClass('force'+config.type);
		
		//add number validation if forcing a number value
		if(config.type == 'numeric' || config.type == 'currency' || config.type == 'percent'){
			el.addClass('number');
		}
		
		if(config.type == 'percent'){
			suffix = '%';
		}
		
		if(config.type == 'currency'){
			prefix = '$';
		}
		
		el.each(function()
		{
			$(this).blur(function()
			{
				val = $(this).val();
				val = val.replace(/[\$\%,]/g,'');
				if(val && val != '' && !isNaN(val))
				{
					if(config.type == 'numeric')
					{
						val = val.replace(/,/g,'');					
					}
					else if(config.addCommas == true)
					{
						val = val.replace(/,/g,'');
						if(val >= 1000)
						{
							val = addCommas(val);
						}
					}
					$(this).val(prefix+''+val+''+suffix);
				}
			});
		});
	};
	
})(jQuery);


