/*
	Purpose:
		This object provides live form validation.

	Example Usage:
		validator.set_error_messages({ min_length: 'My custom message: %s should be atleast %s characters long', my_custom_validation: 'You may pass' });

		validator.add_rule('email_address', 'E-mail address', 'required|email');

		$('#first_name, #last_name').blur(function() { validator.validate_field(this); });

		validator.set_required_fields('first_name, last_name');
		$('#form_name').submit(function() { return validator.check_required_fields(); });

		function my_custom_validation(value) { return true; }
		validator.add_validator(my_custom_validation);
*/

validator = {
	required_fields: '',
	field_names: {},
	rules: {},
	custom_validators : {},

	/* Default error messages */
	error_messages: {
		required: '%s is required',
		numeric: '%s needs to be in numeric format',
		decimal: '%s needs to be in decimal format',
		email: '%s needs to be a valid email address',
		phone: '%s needs to be a valid phone number',
		matches: '%s does not match the %s field',
		length: '%s needs to have exactly %s characters',
		min_length: '%s needs to have a minimum of %s characters',
		max_length: '%s can have a maximum of %s characters'
	},

	add_rule: function(field_id, field_name, rule) {
		this.field_names[field_id] = field_name;

		if (/required/.test(rule)) this.required_fields += (this.required_fields.length > 0) ? ', ' + field_id : field_id;
		this.rules[field_id] = rule.replace(/(\|required)|(required\|)|(required)/g, '');
	},

	add_validator: function(custom_validator, function_name) {
		if (!function_name) function_name = custom_validator.toString().match(/function\s+([^\s\(]+)/)[1];
		if (typeof custom_validator === 'function') this.custom_validators[function_name] = custom_validator;
	},

	set_error_messages: function(error_messages) {
		if (typeof error_messages === 'object') {
			$.each(
				error_messages,
				function(rule_name, message) {
					validator.error_messages[rule_name] = message;
				}
			);
		};
	},

	check_required_fields: function() {
		var required_fields = this.required_fields.replace(/\s/g, '').split(/,/);
		var error_messages = {};

		for (var i = 0; i < required_fields.length; i++) {
			var field_object = $('#' + required_fields[i]);
			var error_message = this.error_messages['required'].replace(/%s/, this.field_names[field_object.attr('id')]);

			if (field_object.attr('disabled') === true) continue;

			/* Generate error messages */
			if (!this.required(field_object.val())) {
				error_messages[field_object.attr('id')] = this.field_names[field_object.attr('id')] + " is required";
			}
		}
		return error_messages;
	},

	validate: function(field_object) {
		var field_object = $(field_object);
		var rules = this.rules[field_object.attr('id')].replace(/\s/g, '').split(/\|/);
		var is_valid = true;

		/* Loop through each rule */
		for (var i = 0; i < rules.length; i++) {
			var function_name = "";
			var parameters = [];
			var function_call = "";
			var is_passed = false;

			/* Extract function name */
			if (/^(\w+)/.test(rules[i])) function_name = rules[i].match(/^(\w+)/)[1];
			if (!function_name) break;

			/* Extract the parameters to an array, if existing */
			if (/\[[\w,]+\]$/.test(rules[i])) parameters = rules[i].match(/\[([\w,]+)\]$/)[1].split(/,/);

			/* Generate the function call: function_name('value', 'param1', 'param2', etc...) */
			function_call = function_name + "('" + field_object.val() + "'";
			for (var j = 0; j < parameters.length; j++) {
				if (j < parameters.length) function_call += ", ";
				function_call += "'" + parameters[j] + "'";
			}
			function_call += ")";

			/* Check if the function exists */
			if (eval('typeof this.' + function_name) === 'function') {
				function_call = "this." + function_call;
			} else if (eval('typeof this.custom_validators.' + function_name) === 'function') {
				function_call = "this.custom_validators." + function_call;
			} else {
				/* Should never get here if your code is correct */
				alert(function_name + ' does not exist!');
				continue;
			}

			/* Validate the field value */
			is_passed = eval(function_call);

			$('.error_message', field_object.parent()).remove();
			if (!is_passed) {
				/* Insert values into error message '%s' placeholders */
				var error_message = this.error_messages[function_name].replace(/%s/, this.field_names[field_object.attr('id')]);
				for (var j = 0; j < parameters.length; j++) {
					error_message = error_message.replace(/%s/, (!this.field_names[parameters[j]]) ? parameters[j] : this.field_names[parameters[j]].toLowerCase());
				}

				/* Display error message */
				field_object.after($('<div />').addClass('error_message').html(error_message));
				is_valid = false;
				break;
			}
		}
		return is_valid;
	},

	/* -------- Validation functions -------- */
	required: function(value) {
		var is_passed = false;
		if ($.trim(value).length > 0) is_passed = true;
		return is_passed;
	},

	numeric: function(value) {
		var is_passed = false;
		if (/^[\d]+$/.test(value)) is_passed = true;
		return is_passed;
	},

	decimal: function(value) {
		var is_passed = false;
		if (/^[\d]*[.]?[\d]*$/.test(value)) is_passed = true;
		return is_passed;
	},

	email: function(value) {
		var is_passed = false;
		if (/^(?:[a-zA-Z0-9_'^&amp;/+-])+(?:\.(?:[a-zA-Z0-9_'^&amp;/+-])+)*@(?:(?:\[?(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))\.){3}(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\]?)|(?:[a-zA-Z0-9-]+\.)+(?:[a-zA-Z]){2,}\.?)$/.test(value)) is_passed = true;
		return is_passed;
	},

	phone: function(value) {
		var is_passed = false;
		if (/^[\d]{10}$/i.test(value.replace(/[^\d]/g, ''))) is_passed = true;
		return is_passed;
	},

	matches: function(value, form_field) {
		var is_passed = false;
		if (value.toLowerCase() === $('#' + form_field).val().toLowerCase()) is_passed = true;
		return is_passed;
	},

	length: function(value, length) {
		var is_passed = false;
		if (value.length === parseInt(length)) is_passed = true;
		return is_passed;
	},

	min_length: function(value, min_length) {
		var is_passed = false;
		if (value.length >= parseInt(min_length)) is_passed = true;
		return is_passed;
	},

	max_length: function(value, max_length) {
		var is_passed = false;
		if (value.length <= parseInt(max_length)) is_passed = true;
		return is_passed;
	}
}
