/**
 * Utility function needed by $form.setMessages
 * @param {Object} object
 */
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));
					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 = new Array(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]);
				}
				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.getDaysInMonth = function(){
				return [31,(this.isLeapYear() ? 29:28),31,30,31,30,31,31,30,31,30,31][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()));
			}
			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) {
				//t = this;
				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 = new Array();
//console.log(this._input);
				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( !/^([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._display = function(data){
					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].join("\n");
						msg += data.message[index]+"\n";
						//console.log(data.message[index]);
					}
					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.not(':hidden,:disabled');
					return field.filter(function(index){
						// Don't check fields which are not visible
						return (($(this).parents(":hidden").size() == 0 && $(this).is(':visible') ) || $(this).hasClass('__VH__')) && $(this).is(':enabled');
					});
					//return field;
				};

				$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 = new Array();
						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] = new Array();
									values[j][i] = $(this).val();
								});
							}
							else {
								checksum++;
							}
						}
						if (checksum == 0) {
							for(i=0;i<values.length;i++){
								// Create validation helper - hidden field with processed value
								$($('<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){
								//if there is a group defined for this rule, then make use group helper insted as input
								$input = $group;
							}
							$input.each(function(){
								var test = new validator($(this).getProperties(index));
								//TODO: type, value, name, label - should be passed here to de validator object.
								var rule = $form.rules[index]; //Array of rules or single rule to test
								if (typeof rule === 'object') {
									for (i = 0, j = rule.length; i < j; i++) {
										test.run(rule[i], i);
										// Break loop if the first validator has returned minimum one error;
										if (test.getMessages().length > 0)
											break;
									}
								}
								else {
									test.run(rule, 0);
								}
								//console.log(test.getMessages());
								$form.setMessages(test);

							});
						}
					}
				};

				$form.isValid = function(){
					$form.preAction();
					$form.runFilters();
					$form.runValidation();
					//return false;
					return ($form.messages.index.length > 0) ? false : true;
				};

				$form.submit(function(){
					$('label,input,select,textarea').removeClass($form.errorClass).removeData('error');
					if ($form.isValid()) {
						return true;
					} else {
//console.log($form.messages);
						$form.decorator($form.messages);
						return false;
					}
				}).keydown(function(e){ return (e.keyCode==13) ? false : true });

				return $form;
			};
})(jQuery);

