(function($) {

  /*
  Validation Singleton
  */
  var Validation = function() {
      var rules = {
          email : {
             check: function(value, title) {
                 if(title) this.msg = "Enter a valid "+title+".";
                 if(value)
                     return testPattern(value,/^([a-zA-Z0-9_\.\-])+@(([a-zA-Z0-9\-])+\.)+([a-zA-Z]{2,4})+$/);
                 return true;
             },
             msg : "Enter a valid email address."
          },
          url : {
             check : function(value, title) {
                 if(title) this.msg = "Enter a valid "+title+".";
                 if(value)
                     return testPattern(value,/^https?:\/\/(.+\.)+.{2,4}([?\/].*)?$/);
                 return true;
             },
             msg : "Enter a valid URL."
          },
          int_plus : {
             check : function(value, title) {
                 if(title) this.msg = title+" field needs an integer number. (value > 0)";
                 if(value)
                     return testPattern(value,/^[0-9]+$/);
                 return true;
             },
             msg : "Enter a valid integer number. (value > 0)"
          },
          int_all : {
             check : function(value, title) {
                 if(title) this.msg = title+" field needs an integer number.";
                 if(value)
                     return testPattern(value,/^(-|\+)?[0-9]+$/);
                 return true;
             },
             msg : "Enter a valid integer number."
          },
          decimal_plus : {
             check : function(value, title) {
                 if(title) this.msg = title+" field needs an decimal number. (value > 0)";
                 if(value)
                     return (!isNaN(value) && value > 0);
                 return true;
             },
             msg : "Enter a valid decimal number. (value > 0)"
          },
          decimal_all : {
             check : function(value, title) {
                 if(title) this.msg = title+" field needs an decimal number.";
                 if(value) {
                     return (!isNaN(value));
                 }
                 return true;
             },
             msg : "Enter a valid decimal number."
          },
          checked_min : {
             minchk:1,
             check : function(frm, chk, minchk, title) {
                 this.minchk = parseInt(minchk) ? parseInt(minchk) : 1;
                 this.msg    = "Select at least "+this.minchk+" "+(title ? title : ("checkbox"+(this.minchk>1?'es':'')))+".";
                 var checked = $('#'+frm+' input[name='+chk+']:checked').length;
                 if(checked < this.minchk)
                     return false;
                 else
                     return true;
             },
             msg : "Select at least 1 checkbox."
          },
          required : {
             check: function(value, title) {
                 if(title) this.msg = title + " field is required.";
                 
                 if(value)
                     return true;
                 else
                     return false;
             },
             msg : "This field is required."
          }
      }
      var testPattern = function(value, regExp) {
          return regExp.test(value);
      }
      return {
          addRule : function(name, rule) {
              rules[name] = rule;
          },
          getRule : function(name) {
              return rules[name];
          }
      }
  }

  /*
  Form factory
  */
  var Form = function(form) {
      this.id = form.attr('id');
      this.jqval_doalert = typeof($(form)[0]['jqval_doalert'])=='undefined' ? 0  : parseInt($(form)[0]['jqval_doalert'].value);
      this.jqval_msglist = typeof($(form)[0]['jqval_msglist'])=='undefined' ? 0  : parseInt($(form)[0]['jqval_msglist'].value);

      this.jqval_msgbxon = typeof($(form)[0]['jqval_msgbxon'])=='undefined' ? 0  : parseInt($(form)[0]['jqval_msgbxon'].value);
      this.jqval_msgbxid = typeof($(form)[0]['jqval_msgbxid'])=='undefined' ? '' : $(form)[0]['jqval_msgbxid'].value;
      this.jqval_msginit = typeof($(form)[0]['jqval_msginit'])=='undefined' ? '' : $(form)[0]['jqval_msginit'].value;
                           
      this.jqval_bgcolor = typeof($(form)[0]['jqval_bgcolor'])=='undefined' ? 0  : parseInt($(form)[0]['jqval_bgcolor'].value);
      this.jqval_bg_norm = typeof($(form)[0]['jqval_bg_norm'])=='undefined' ? '#FFFFFF' : $(form)[0]['jqval_bg_norm'].value;
      this.jqval_bg_errr = typeof($(form)[0]['jqval_bg_errr'])=='undefined' ? '#FFFFCC' : $(form)[0]['jqval_bg_errr'].value;

      var oform = this;
      var fields = [];
      form.find("input[validation], textarea[validation], select[validation]").each(function() {
          fields.push(new Field(oform, this));
      });
      this.fields  = fields;
  }
  Form.prototype = {
      validate : function() {
          for(field in this.fields) {
              this.fields[field].validate();
          }
      },
      isValid : function() {
          for(field in this.fields) {
              if(!this.fields[field].valid) {
                  this.fields[field].field.focus();

              	  if(this.jqval_msgbxon)
              	    $("#"+this.jqval_msgbxid).html(this.fields[field].message);
                  if(this.jqval_doalert)
                    alert(this.fields[field].message);

                  return false;
              } else {
              	  if(this.jqval_msgbxon)
              	    $("#"+this.jqval_msgbxid).html(this.jqval_msginit);
              }
          }
          return true;
      }
  }

  /*
  Field factory
  */
  var Field = function(oform, field) {
      this.oform   = oform;
      this.field   = $(field);
      this.type    = this.field.attr('type');
      this.name    = this.field.attr('name');
      this.chklist = this.field.attr("validation").split(" ");
      this.valid   = false;
      this.attach("change");
      this.message = '';
      this.title   = typeof($(field).attr('alt')!='undefined') ? $(field).attr('alt') : '';
      this.container = '';
      this.fieldbox  = '';
      this.eventinit = false;

      if(this.oform.jqval_msglist) {
          //alert(this.name+', '+this.field.next().attr('for')+','+this.field.next().attr('class'));
          if(this.field.next().attr('class')=='jqval_msgbox') 
              this.fieldbox  = this.field.next();

          var oparent = this.field.parent();
          var ctr = 0;
          while(oparent.attr('tagName') != 'BODY') {
              ctr++;
              switch(oparent.attr('class')) {
                 case 'jqval_container': if(!this.container) this.container = oparent; break;
                 case 'jqval_fieldbox' : if(!this.fieldbox)  this.fieldbox  = oparent; break;
              }
              if(ctr > 5 || this.container)
                  break;
              else
                  oparent = oparent.parent();
          }
          if(!this.fieldbox) this.fieldbox = this.field;
      }
      if(this.type=='checkbox') {
          if(!this.eventinit) {
              var obj = this;
              $('#'+this.oform.id+' input[name='+this.name+']').click(function(e){
                  obj.validate();
              });
              this.eventinit = true;
          }
      }
  }
  Field.prototype = {
      attach : function(event) {
          var obj = this;
          if(event == "change") {
              obj.field.bind("change",function() {
                  return obj.validate();
              });
          }
          if(event == "keyup") {
              obj.field.bind("keyup",function(e) {
                  return obj.validate();
              });
          }
      },
      validate : function() {
          var obj = this,
              field = obj.field,
              errorClass = "jqval_errorlist",
              errorlist = $(document.createElement("ul")).addClass(errorClass),
              errors = [];

          if(this.oform.jqval_msglist) {
              this.fieldbox.next("."+errorClass).remove();
          }
          obj.message = '';
          for (var item in this.chklist) {
              try {
                  var rule, isval;
                  if(this.type == 'checkbox') {
                      if((/^checked_min(.*)?$/).test(this.chklist[item])) {
                      	  var ary = this.chklist[item].split('=')
                      	  rule = $.Validation.getRule('checked_min');
                      	  isval = rule.check(this.oform.id, this.name, ary[1], this.title);
                      }
                  } else {
                  	  rule = $.Validation.getRule(this.chklist[item]);
                  	  isval = rule.check(field.val(), this.title);
                  }
                  if(!isval) {
                      errors.push(rule.msg);
                  }
              } catch(e) {
                  alert('ATTENTION DEVELOPERS : The rule [ '+this.chklist[item]+' ] does NOT exist!!!      ');
                  errors.push('The rule [ '+this.chklist[item]+' ] does NOT exist!!!');
              }
          }
          if(errors.length) {
              if(this.type!='checkbox') {
                  obj.field.unbind("keyup")
                  obj.attach("keyup");
              }
              if(this.oform.jqval_msglist) {
              	  this.fieldbox.after(errorlist.empty());
              }
              obj.message = '';
              for(error in errors) {
                if(this.oform.jqval_msglist)
                  errorlist.append("<li>"+ errors[error] +"</li>");
                obj.message += errors[error] + "\n";
              }
              obj.valid = false;

              if(this.oform.jqval_msglist && this.container)
                  $(this.container).attr("class", "jqval_container_err");
              if(this.oform.jqval_bgcolor) {
                  if(this.type=='checkbox') {
                      $('#'+this.oform.id+' input[name='+this.name+']').css('background-color', this.oform.jqval_bg_errr);
                  } else {
                      $(field).css('background-color', this.oform.jqval_bg_errr);
                  }
              }
          }
          else {
              if(this.oform.jqval_msglist) {
                  errorlist.remove();
                  if(this.container)
                      $(this.container).attr("class", "jqval_container");
              }
         	    if(this.oform.jqval_bgcolor) {
                  if(this.type=='checkbox') {
                      $('#'+this.oform.id+' input[name='+this.name+']').css('background-color', this.oform.jqval_bg_norm);
                  } else {
                      $(field).css('background-color', this.oform.jqval_bg_norm);
                  }
              }

              obj.valid = true;
              obj.message = '';
          }
      }
  }

  /*
  Validation extends jQuery prototype
  */
  $.extend($.fn, {
      validation : function(callback) {
          var validator = new Form($(this));
          $.data($(this)[0], 'validator', validator);
          $(this).bind("submit", function(e) {
                   e.preventDefault();
              validator.validate();
              if(validator.isValid()) {
                  if(typeof(callback)==='function') {
        	          callback.call(this);
                    e.preventDefault();
        	        }
              }
              else {
                  e.preventDefault();
              }
          });
      },
      validate : function() {
          var validator = $.data($(this)[0], 'validator');
          validator.validate();
          return validator.isValid();
      }
  });
  $.Validation = new Validation();
})(jQuery);
