function _o(object){
	for(o in object){
		return object[o];
	}
}
function _zeroPad(string){
	string = String(string);
	return (string.length<2) ? '0' + string : string;
}
function Luhn(digits){
	var sum = 0;
	var alt = false;
	var numvar = 0;
	for(var i = digits.length - 1; i >= 0; i--){
		numvar = parseInt(digits.charAt(i),10);
		if(alt){
			numvar *= 2;
			if(numvar > 9){
				numvar -= 9;
			}
		}
		sum += numvar;
		alt = !alt;
	}
	return sum % 10 === 0;
}
function Nip(digits){
	var nip = String(digits);				
	var ratio = [6,5,7,2,3,4,5,6,7];
	var checksum = 0;
	for(i=0;i<9;i++) {
		checksum += parseInt(nip.substring(i,(i+1))*ratio[i],10);
	}
	return ( (checksum%11) == nip.substring(9,10) ) ;				
}

Date.prototype.isLeapYear = function(){
	var y = this.getFullYear();
	return (y%4===0 && y%100!==0) || y%400===0;
};
Date.prototype.isWeekend = function(){
	return this.getDay()===0 || this.getDay()===6;	
};
Date.prototype.getDaysInMonth = function(){
	return [31,(this.isLeapYear() ? 29:28),31,30,31,30,31,31,30,31,30,31][this.getMonth()];
};
Date.prototype.getDayName=function(abbreviated) {
	return abbreviated ? Date.abbrDayNames[this.getDay()] : Date.dayNames[this.getDay()];
};
Date.prototype.getMonthName=function(abbreviated) {
	return abbreviated ? Date.abbrMonthNames[this.getMonth()] : Date.monthNames[this.getMonth()];
};
Date.prototype.asString = function(format){
	var r = Date.format || format || 'YYYY-MM-DD';
	return r
		.replace(/(yyyy|rrrr)/i, this.getFullYear())
		.replace(/(yy|rr)/i, (this.getFullYear()+'').substring(2))
		.replace(/mmm/i, this.getMonthName(true))
		.replace(/mm/i, _zeroPad(this.getMonth()+1))
		.replace(/dd/i, _zeroPad(this.getDate()));
};
Date.prototype.zeroTime = function(){
	this.setMilliseconds(0);
	this.setSeconds(0);
	this.setMinutes(0);
	this.setHours(0);
	return this;
};
Date.prototype.addYears = function(num){
	this.setFullYear(this.getFullYear() + num);
	return this;	
};
Date.prototype.addMonths = function(num) {
	var tmpdtm = this.getDate();

	this.setMonth(this.getMonth() + num);

	if (tmpdtm > this.getDate()) {
		this.addDays(-this.getDate());
	}
	return this;
};
Date.prototype.addDays=function(num) {
	this.setDate(this.getDate() + num);
	return this;
};
String.prototype.strToDate = function(){
	var arr = /^(\d+).(\d+).(\d+)$/.exec(this);

	if (arr !== null) {
		arr = arr.slice(1, 4);
		if (arr[0].length != 4) {
			arr.reverse();
		}
		var year = arr[0];
		var month = --arr[1];
		var day = arr[2];

		var date = new Date(year, month);
		return (date.getDaysInMonth() >= day) ? new Date(year, month, day) : false;
	}
	return false;
};
var filter = function(field){
	this._name = field.name;
	this._input = field.object;
	this._type = field.type;
	this._value = field.value;
	
	this.run = function(callback,index) {
		query = /^([a-z]+)\((.{0,})\)$/i.exec(callback);
		func = (query !== null) ? query[1] : callback;
		parm = (query !== null) ? query[2] : false;

		this._temp = [func, index];
		
		if (parm && func == 'Regex' && eval(parm) !== null) {
			eval('this.' + func + '(' + parm + ')');
		}
		else {
			if (query !== null) {
				if (typeof eval(query[2]) === 'function') {
					var params = "'" + eval(query[2])() + "'";
				}
				else {
					var params = query[2];
				}
				eval('this.' + query[1] + '(' + params + ')');
			}
			else {
				eval('this.' + callback + '()');
			}
		}
	};
	this.Digits = function(){
		this._input.val(this._value.replace(/\D/g,''));
	};
	this.Trim = function(){
		//.replace(/^ +/,'').replace(/ +$/,'');
		this._input.val(jQuery.trim(this._value));
	};				
	this.Number = function(){
		this._input.val(this._value.replace(/[^\d\-]/g,''));
	};				
};
var validator = function(field) {
	this._name = field.name;
	this._input = field.object;
	this._type = field.type;
	this._value = field.value;
	this._index = 0;
	this._id = 0;
	this._validator = null;
	this._messages = [];
	this.run = function(callback,index) {
		query = /^([a-z]+)\((.{0,})\)$/i.exec(callback);
		func = (query !== null) ? query[1] : callback;
		parm = (query !== null) ? query[2] : false;

		this._temp = [func, index];
		
		if (parm && func == 'Regex' && eval(parm) !== null) {
			eval('this.' + func + '(' + parm + ')');
		}
		else {
			if (query !== null) {
				if (typeof eval(query[2]) == 'function') {
					var params = "'" + eval(query[2])() + "'";
				}
				else {
					var params = query[2];
				}
				eval('this.' + query[1] + '(' + params + ')');
			}
			else {
				eval('this.' + callback + '()');
			}
		}
	};
	this.getInput = function(){
		return this._input;
	};
	this.getName = function(getFromSettings){
		return (this._type=='group' || getFromSettings) ? this._name : this._inputhis.attr('name') ;
	};
	this.setValue = function(value){
		this._value = value;
	};
	this.getValidator = function() {
		return this._validator;
	};
	this.getIndex = function(){
		return this._index;
	};
	this.getId = function(){
		return this._name + '_' + this._validator + '_' + this._id + '_' + this._index;
	};
	this.getLabel = function(){
		var label = this._input;
		switch(true){
			case $('label[for=' + this._name + ']').size() > 0:
				label = $('label[for=' + this._name + ']');
				break;
			case this._input.parent('label').size() > 0:
				label =  this._inputhis.parent('label');
				break;
			case this._input.siblings('label').size() > 0:
				label =  this._input.siblings('label');
				break;
		}
		if(this._type != 'radio') {
			label = label.filter(':first');
		}
		return label;
	};
	this.getMessages = function(first) {
		return (first) ? [this._messages[0]] : this._messages ;
	};
	this._formatMessage = function(string,values){
		string = string.replace('%value%',this._value).replace('%name%',this._name);
		if(typeof values == 'object' && values.length > 0){
			for (i=0,j=values.length;i<j;i++) {
				string = string.replace('%'+i+'%', values[i]);
			}
		}
		return string;
	};
	this._collectMessages = function(messages){
		if(messages.length > 0){
			this._messages.push(messages[0]); // Return only the first error.
			this._validator = this._temp[0];
			this._index = this._temp[1];
			return true;
		} else {
			return false;
		}
	};
	this._empty = function() {
		return (this._value === '');
	};
	this.AfterDate = function(date,inclusive){
		var messages = [];
		if (inclusive) {
			if (this._value.strToDate() < date.strToDate()) {
				messages.push({NOT_AFTER:this._formatMessage("'%value%' must be later then %0%, inclusively",[date])});
			}
		} else {
			if (this._value.strToDate() <= date.strToDate()) {
				messages.push({NOT_AFTER_STRICT:this._formatMessage("'%value%' must be strictly later then %0%",[date])});
			}
		}
		return this._collectMessages(messages);
	};
	this.Alnum = function(){
		var messages = [];
		if( this._empty() ) {
			messages.push({STRING_EMPTY:this._formatMessage("'%name%' is an empty string")});
		}
		if( !/^[a-z0-9]+$/i.test(this._value) ){
			messages.push({NOT_ALNUM:this._formatMessage("'%name%' has not only alphabetic and digit characters")});
		}
		return this._collectMessages(messages);
	};
	this.Alpha = function(){
		var messages = [];
		if( this._empty() ) {
			messages.push({STRING_EMPTY:this._formatMessage("'%name%' is an empty string")});
		}
		if( !/^[a-z]+$/i.test(this._value) ){
			messages.push({NOT_ALPHA:this._formatMessage("'%name%' has not only alphabetic characters")});
		}
		return this._collectMessages(messages);
	};
	this.BeforeDate = function(date,inclusive){
		var messages = [];
		if (inclusive) {
			if (this._value.strToDate() > date.strToDate()) {
				messages.push({NOT_BEFORE:this._formatMessage("'%value%' must be earlier then %0%, inclusively",[date])});
			}
		} else {
			if (this._value.strToDate() >= date.strToDate()) {
				messages.push({NOT_BEFORE_STRICT:this._formatMessage("'%value%' must be strictly earlier then %0%",[date])});
			}
		}
		return this._collectMessages(messages);
	};
	this.Between = function(min,max,inclusive){
		var messages = [];
		if( inclusive ){
			if( min > this._value || this._value > max ) {
				messages.push({NOT_BETWEEN:this._formatMessage("'%value%' is not between %0% and %1%, inclusively",[min,max])});
			}
		} else {
			if( min >= this._value || this._value >= max ) {
				messages.push({NOT_BETWEEN_STRICT:this._formatMessage("'%value%' is not strictly between %0% and %1%",[min,max])});
			}
		}
		return this._collectMessages(messages);
	};
	this.Ccnum = function(){
		var messages = [];
		var ccnum = String(this._value);
		if( ccnum.length < 13 || ccnum.length > 19 ){
			messages.push({LENGTH:this._formatMessage("%value%' must contain between 13 and 19 digits")});
		}
		if( !Luhn(ccnum) ){
			messages.push({CHECKSUM:this._formatMessage("Luhn algorithm (mod-10 checksum) failed on '%value%'")});
		}
		return this._collectMessages(messages);
	};
	this.Date = function(){
		var messages = [];
		if( !/^\d{4}-\d{2}-\d{2}$/.test(this._value) ){
			messages.push({NOT_YYYY_MM_DD:this._formatMessage("'%value%' is not of the format YYYY-MM-DD")});
		}
		if( !this._value.strToDate() ){
			messages.push({INVALID:this._formatMessage("'%value%' does not appear to be a valid date")});
		}
		return this._collectMessages(messages);
	};
	this.DateBetween = function(min,max,inclusive){
		var messages = [];
		if (inclusive) {
			if (min.strToDate() > this._value.strToDate() || this._value.strToDate() > max.strToDate()) {
				messages.push({NOT_BETWEEN:this._formatMessage("'%value%' is not between %0% and %1%, inclusively", [min, max])});
			}
		} else {
			if (min.strToDate() >= this._value.strToDate() || this._value.strToDate() >= max.strToDate()) {
				messages.push({NOT_BETWEEN_STRICT:this._formatMessage("'%value%' is not strictly between %0% and %1%", [min, max])});
			}
		}
		return this._collectMessages(messages);
	};		
	this.Digits = function(){
		var messages = [];
		if( this._empty() ) {
			messages.push({STRING_EMPTY:this._formatMessage("'%name%' is an empty string")});
		}
		if( !/^[0-9]+$/i.test(this._value) ){
			messages.push({NOT_DIGITS:this._formatMessage("'%name%' has not only digit characters")});
		}
		return this._collectMessages(messages);
	};
	this.EmailAddress = function(){
		var messages = [];
		if( this._value.indexOf('@') === -1 || !/^([a-z0-9]+[\.\-_]?)+@([a-z0-9]+[\.\-_]?)+\.([a-z]{2,4})$/i.test(this._value) ){
			messages.push({INVALID:this._formatMessage("'%value%' is not a valid email address in the basic format local-part@hostname")});
		}
		return this._collectMessages(messages);
	};
	this.Identical = function(token){
		var messages = [];
		if( !token || token === '' ){
			messages.push({MISSING_TOKEN:"No token was provided to match against"});
		}
		if( String(this._value) !== String(token) ){
			messages.push({NOT_SAME:"Tokens do not match"});
		}
		return this._collectMessages(messages);
	};
	this.NotEmpty = function(){
		var messages = [];
		if( this._empty() ){
			messages.push({IS_EMPTY:"Value is empty, but a non-empty value is required"});
		}
		return this._collectMessages(messages);
	};
	this.NotIdentical = function(token){
		var messages = [];
		if( !token || token === '' ){
			messages.push({MISSING_TOKEN:"No token was provided to match against"});
		}
		if( String(this._value) === String(token) ){
			messages.push({IS_SAME:this._formatMessage("Value is identical to %0%",[token])});
		}
		return this._collectMessages(messages);
	};
	this.Regex = function(expression,pattern){
		var messages = [];
		if (!expression.test(this._value)) {
			messages.push({
				NOT_MATCH: this._formatMessage("'%value%' does not match against pattern '%0%'", [(pattern) ? pattern : expression])
			});
		}
		return this._collectMessages(messages);
	};
	this.Required = function(){
		var messages = [];
		if (this._empty()) {
			messages.push({IS_EMPTY:"Value is empty, but a non-empty value is required"});
		}
		if (this.getInput().attr('type') == 'checkbox'){
			if (!this.getInput().is(':checked')) {
				messages.push({NOT_CHECKED:this._formatMessage("'%name%' is required but is not checked")});
			}
		}
		return this._collectMessages(messages);
	};
	this.StringLength = function(min,max){
		var messages = [];
		if( this._value.length < min ){
			messages.push({TOO_SHORT:this._formatMessage("'%value%' is less than %0% characters long",[min])});
		}
		if( this._value.length > max ){
			messages.push({TOO_LONG:this._formatMessage("'%value%' is greater than %0% characters long",[max])});
		}
		return this._collectMessages(messages);
	};
};
(function($){			
jQuery.fn.getProperties = function(index){
	return ($(this).size() > 0) ? {
		object: $(this),
		value: $(this).val(),
		type: $(this).attr('type'),
		name: index
	} : {
		object: index,
		value: null,
		type: 'group',
		name: index
	};					
};			
jQuery.fn.validate = function(settings) {
	$form = $(this);
	$form.filters = settings.filters || [];
	$form.rules = settings.rules || [];
	$form.groups = settings.groups || [];
	$form.userMessages = settings.messages || [];
	$form.breakChain = settings.options.breakChain || true;
	$form.errorClass = settings.options.labelErrorClass || 'error';
	$form.preAction = settings.options.preAction || function(){return false;};
	$form.postAction = settings.options.postAction || false;

	$form._display = function(data){
		if (!data) {
			return false;
		}
		var msg = '';
		for(index in data.label){
			var title = data.label[index].attr('title');
			msg += "\n"+((title)?title:data.label[index].text()).toUpperCase()+"\n";
			msg += data.message[index]+"\n";
		}
		alert(msg);
	};
	$form.decorator = (typeof settings.display === 'function') ? settings.display : $form._display;
	$form.resetMessages = function() {
		$form.messages = {field:[],label:[],message:[],repeated:[],index:[]};
	};
	$form.setMessages = function(field) {
		if (field.getMessages().length > 0) {
			$form.messages.repeated.push(($.inArray(field.getName(true),$form.messages.index)!=-1)?true:false);
			$form.messages.index.push(field.getName(true));
			$form.messages.field.push(field.getInput());
			$form.messages.label.push(field.getLabel());
			var userMessages = $form.userMessages[field.getName(true)];

			switch(typeof userMessages){
				case 'string':
					$form.messages.message.push([userMessages+"\n"]);
					break;
				case 'object':
					userMessage = (typeof userMessages[field.getValidator()] === 'string')
						? [userMessages[field.getValidator()]+"\n"]
						: [_o(field.getMessages($form.breakChain)[0])+"\n"];
					$form.messages.message.push(userMessage);
					break;
				default:
					$form.messages.message.push(_o(field.getMessages($form.breakChain)[0])+"\n");
					break;
			}
		}
	};
	$form.getField = function(search){
		switch(search.substr(0,1)){
			case '.':
			case '#':
				var field = $(search,$form);
				break;
			case '*':
				var field = $('input:text,textarea',$form);
				break;
			default:
				//var field = $('*[name='+search+']',$form);
				var field = $(document.getElementsByName(search),$form);
				break;
		}
		if (typeof field.get(0) == 'undefined' || field.get(0).length === 0) {
			return false;
		}
		if(field.is(':radio')){
			field = field.filter(':checked'); // Get only checked element from
		}
		return field.filter(function(index){
			return ($(this).parents(":hidden").size() === 0 && $(this).is(':visible') ) || $(this).hasClass('__VH__');
		});
	};
	$form.runFilters = function(){
		for(index in $form.filters){
			$input = $form.getField(index);
			if ($input) {
				$input.each(function(){
					var clean = new filter($(this).getProperties(index));
					var rule = $form.filters[index];
					if (typeof rule === 'object') {
						for (i = 0, j = rule.length; i < j; i++) {
							clean.run(rule[i]);
						}
					}
					else {
						clean.run(rule);
					}
				});
			}
		}
	};
	$form.getGroup = function(index){
		if ($form.groups[index]) {
			$('.__VH__',$form).remove();
			var checksum = 0;
			var values = [];
			for (i = 0; i < $form.groups[index].fields.length; i++) {
				var $field = $form.getField($form.groups[index].fields[i]);
				if ($field) {
					$field.each(function(j){
						if (!values[j]) {
							values[j] = [];
						}
						values[j][i] = $(this).val();
					});
				}
				else {
					checksum++;
				}
			}
			if (checksum === 0) {
				for(i=0;i<values.length;i++){
					$($('<input type="hidden"></input>')
						.val(values[i].join($form.groups[index].glue))
						.addClass('__VH__')
						.addClass('__VP__'+index)
					).insertAfter($field.get(i));
				}
				return $('.__VP__'+index,$form);
			}
			else {
				return false;
			}
		} else {
			return false;
		}
	};
	$form.runValidation = function(){
		$form.resetMessages();
		for (index in $form.rules) {
			$input = $form.getField(index);
			$group = $form.getGroup(index);
			if ($input || $group) {
				if($group){
					$input = $group;
				}
				$input.each(function(){
					var test = new validator($(this).getProperties(index));
					var rule = $form.rules[index];
					if (typeof rule === 'object') {
						for (i = 0, j = rule.length; i < j; i++) {
							test.run(rule[i], i);
							if (test.getMessages().length > 0) {
								break;
							}
						}
					}
					else {
						test.run(rule, 0);
					}
					$form.setMessages(test);
				});
			}
		}
	};
	$form.isValid = function(){
		$form.preAction();
		$form.runFilters();
		$form.runValidation();
		return ($form.messages.index.length > 0) ? false : true;
	};
	$form.submit(function(){
		$('label,input,select,textarea').removeClass($form.errorClass).removeData('error');
		if ($form.isValid()) {
			$form.decorator(false);
			return (typeof $form.postAction === 'function') ? $form.postAction() : true;
		} else {
			$form.decorator($form.messages);
			return false;
		}
	}).keydown(function(e){ 
		return (e.keyCode==13) ? false : true;
	});
	return $form;
};
})(jQuery);

