﻿(function($){

$.fn.validate = function(fields, options) {
	var validations = $.validate.parseValidations(fields);
	
	// Binding submit event
	$(this).bind('submit', function() {
		var validationErrors = $.validate.validates(validations);
		
		if (validationErrors.length > 0) {
			$.validate.showErrors(validationErrors);
		
			return false;
		} else {
			return true;
		}
	});
};

$.validate = {
	version: '0.0.1',
	
	validates: function(validations) {
		var validationErrors = []; 
		for (id in validations) {
			for (var i = 0; i < validations[id].length; i++) {
				var validation = validations[id][i];
				var field = $('#' + id);
				var string = field.val();
				var arguments = { title: field.attr('title') };
				
				var error = false;
				switch (validation.rule) {
					case 'required':
						error = $.validate.isBlank(string);
						break;
					
					case 'between':
						error = !$.validate.isBetween(string, validation.options[0], validation.options[1]);
						break;
					
					case 'date':
						error = !$.validate.isBlank(string) && !$.validate.isDate(string, validation.options);
						break;
					
					case 'email':
						error = !$.validate.isBlank(string) && !$.validate.isEmail(string);
						break;
				}				
				
				if (error) {
					if (typeof validation.options != 'undefined' && validation.options instanceof Array) { 
						for (var j = 0; j < validation.options.length; j++) {
							arguments['opt_' + (j + 1)] = validation.options[j];
						}
					} else if (typeof validation.options != 'undefined') {
						for (j in validation.options) {
							arguments[j] = validation.options[j];
						}
					}
				
					var message = $.sprintf(validation.message, arguments);
					validationErrors.push({
						id: id,
						message: message
					});
				}
			}
		}
		
		return validationErrors;
	},
	
	_messages: {
		between: 'Preencha o campo "%(title)s" com no mínimo %(opt_1)d e máximo de %(opt_2)d caracteres',
		date: 'O campo "%(title)s" deve ser preenchido com uma data válida',
		email: 'O campo "%(title)s" deve ser preenchido com um e-mail válido',
		required: 'É obrigatório o preenchimento de "%(title)s"'
	},
	
	parseValidations: function(fields) {
		var validations = {};
		for (id in fields) {
			var field = fields[id];
			var rules = [];
			for (var i = 0; i < field.length; i++) {
				if (typeof field[i] == 'string') {
					// Has only the rule name
					rules[i] = {
						rule: field[i]
					};
				} else if (field[i] instanceof Array) {
					// Rule name and options. ie: [between, min, max]
					rules[i] = {
						rule: field[i].shift(),
						options: field[i]
					};
				} else if (field[i].rule instanceof Array) {
					// Rule name and options inside a object
					rules[i] = {
						rule: field[i].rule.shift(),
						options: field[i].rule
					};
				} else {
					// Object
					rules[i] = field[i];
				}
				
				// Default/Custom error message
				rules[i].message = field[i].message || this._messages[rules[i].rule];
			}
			
			validations[id] = rules;
		}
		
		return validations;
	},
	
	showErrors: function(errors) {
		// Has errors?
		if (!(errors instanceof Array) || errors.length == 0) {
			return false;
		}
		
		// Verify if error container exists
		if ($('#validationError').html() == null) {
			$('body').append('<div id="validationError"></div>');
		}
		
		// Show first error
		var error = errors[0];
		$('#validationError').html(error.message);		
		$('#validationError').insertBefore($('#' + error.id).parent());
		$('#validationError').show();
		
		return true;
	}
};

/**
 * Default validations
 */
$.extend($.validate, {
	isBetween: function(string, min, max) {
		var length = string.length;
		
		return (length >= min && length <= max);
	},
	
	isBlank: function(string) {
		return (string == '' || string.match(/^ +$/));
	},
	
	isDate: function(string, settings) {
		var settings = $.extend({
			format: 'dd/mm/yy',
			min: null,
			max: null
		}, settings);
		
		try {
			var date = $.datepicker.parseDate(settings.format, string);
		} catch (e) {
			return false;
		}
		
		// Min date
		if (settings.min != null && settings.min.getTime() > date.getTime()) {
			return false;
		}
		
		// Max date
		if (settings.max != null && settings.max.getTime() < date.getTime()) {
			return false;
		}
		
		// Everything is ok
		return true;
	},

	isEmail: function(string) {
		return string.match(/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[A-Z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)$/);
	},
	
	isMinLength: function(string, min) {
		return (string.length >= min);
	},
	
	isMaxLength: function(string, max) {
		return (string.length <= max);
	}
});

/**
 * Validating Brazilians registers
 */
$.extend($.validate, {
	isCNPJ: function(string) {
		// Only accepts non-formated and full-formated
		if (!string.match(/^[0-9]{14}$/) && !string.match(/^[0-9]{2}.[0-9]{3}.[0-9]{3}\/[0-9]{4}-[0-9]{2}$/)) {
			return false;
		}
		var cnpj = string.replace(/\D/g, '');
		
		// Verify 0-9 sequences
		for (var i = 0; i <= 9; i++) {
			if (new Array(15).join(new String(i)) == cnpj) {
				return false;
			}
		}
		
		// Validates the document
		var a = [];
		var b = new Number;
		var c = [6,5,4,3,2,9,8,7,6,5,4,3,2];
		for (i=0; i < 12; i++) {
			a[i] = cnpj.charAt(i);
			b += a[i] * c[i+1];
		}
		if ((x = b % 11) < 2) { a[12] = 0; } else { a[12] = 11 - x; }
		b = 0;

		for (y=0; y<13; y++) { b += (a[y] * c[y]); }
		if ((x = b % 11) < 2) { a[13] = 0; } else { a[13] = 11 - x; }
		
		if (cnpj.charAt(12) != a[12] || cnpj.charAt(13) != a[13]){
			return false;
		}
		
		return true;
	},
	
	isCPF: function(string) {
		// Only accepts non-formated and full-formated
		if (!string.match(/^[0-9]{11}$/) && !string.match(/^[0-9]{3}.[0-9]{3}.[0-9]{3}-[0-9]{2}$/)) {
			return false;
		}
		var cpf = string.replace(/\D/g, '');
		
		// Verify 0-9 sequences
		for (var i = 0; i <= 9; i++) {
			if (new Array(12).join(new String(i)) == cpf) {
				return false;
			}
		}
		
		// Validates the document
		var a = [];
		var b = new Number;
		var c = 11;
		for (var i = 0; i < 11; i++) {
			a[i] = cpf.charAt(i);
			if (i < 9) { b += (a[i] * --c); }
		}
		if ((x = b % 11) < 2) { a[9] = 0; } else { a[9] = 11 - x; }

		b = 0;
		c = 11;
		for (var y = 0; y < 10; y++) { b += (a[y] * c--); }
		if ((x = b % 11) < 2) { a[10] = 0; } else { a[10] = 11 - x; }
		if (cpf.charAt(9) != a[9] || cpf.charAt(10) != a[10]){
			return false;
		}
			
		return true;
	}
});

})(jQuery);