(function($) {
  
  $.smartform = {
    defaults: {
      error_msg     : null,
      append_errors : false,
      append_token  : '<br />'
    }
  }

  $.fn.extend({
    smartform: function(config) 
    {
      var form = this;
      var config = $.extend({}, $.smartform.defaults, config);
      var elements = [];

      if (config.error_msg)
      {
        var current_msg = [];

        config.error_msg = $(config.error_msg).get(0);
        config.error_msg.Set = setMessage;
      }

      $('label', form).each(function()
      {
        var id = $(this).attr('for');
        var o = $('#'+id).get(0);
        if (!o) return;

        o.label = this;
        elements.push(o);

        if (!config.error_msg)
        {
          o.error_msg = $('#SFError_' + o.name).get(0);
          if (!o.error_msg)
            o.error_msg = $('#SFError_' + o.name.replace(/\[\]/, '')).get(0);

          if (!o.error_msg)
            o.error_msg = $('#SFError_' + o.name.replace(/\[\]/, '')).get(0);
          
          if (!o.error_msg)
          {
            o.error_msg = document.createElement('span');
            o.parentNode.insertBefore(o.error_msg, this);
            o.parentNode.insertBefore(document.createTextNode(' '), this);
          }

          o.error_msg.Set = setMessage;

          o.error_msg.field = o;
          o.error_msg.className = 'SFError';
          $(o.error_msg).hide();
        }

        o.def = config.definitions[o.name];
        if (!o.def)
          o.def = config.definitions[o.name.replace(/\[\]/, '')];

        if (o.def && o.def.bMandatory && $(this).html().indexOf('*') == -1)
          $(this).append('*');

        o.clearHint = function() {
            if (this.def && this.def.sHint && jQuery(this).val() == this.def.sHint) {
                jQuery(this).val('');
            }
        }

        if (o.def && o.def.sHint) {
            if (jQuery(o).val().length == 0 && o.def.sHint.length > 0) {
                jQuery(o).val(o.def.sHint);
            }

            jQuery(o).focus(function() 
            { 
                if (jQuery(this).val().length > 0 && this.def.sHint.length > 0 && jQuery(this).val() != this.def.sHint)
                    return;

                jQuery(this).addClass('focused').val('');
            });

            jQuery(o).blur(function() 
            { 
                if (jQuery(this).val().length > 0)
                    return;

                jQuery(this).removeClass('focused').val(this.def.sHint);
            });

        }

        o.mark = function()
        {
          $(this).addClass('error');
          $(this.label).addClass('error');
          $(this.error_msg).show();
        }

        o.unmark = function()
        {
          $(this).removeClass('error');
          $(this.label).removeClass('error');
          $(this.error_msg).hide();
        }

        $(o).focus(function()
        {
          $(this).addClass('active');
          $(this.label).addClass('active');
        });

        $(o).blur(function()
        {
          $(this).removeClass('active');
          $(this.label).removeClass('active');
        });
      });

      $(form).submit(function(e)
      {
        if (!validate()) 
        {
          e.preventDefault();
          return;
        }

        $(config.error_msg).hide();

        if (config.ajax)
        {
          e.preventDefault();
          submitByAjax();
          return;
        }
      });

      if (config.data)
      {
        restoreValues(config.data);
      }

      if (config.result && !config.result.success)
      {
        for (f in config.result.errors)
        {
          handleError({ error: config.result.errors[f], field: f});
        }
      }

      function validate()
      {
        try
        {
          $(elements).each(function()
          {
            validateField(this);
          });
        }
        catch (e)
        {
          handleError(e);
          return false;
        }

        return true;
      }

      function validateField(f)
      {
        f.unmark();
        f.clearHint();
        switch (f.type)
        {
          case 'text':
          case 'textarea':
          case 'hidden':
          case 'password':
            var vals = [ f.value ];
            break;

          case 'select-one':
          case 'select-multiple':
            var vals = [];
            $.each(f.options,function(index,element)
            {
              if (element.selected)
              {            	
                vals.push(element.value == '0' || element.value == '-' ? '' : element.value);
              }
            });

            if (!vals.length) vals.push('');
            break;

          case 'checkbox':
          case 'radio':
            var vals = [];
            $(elements).each(function()
            {
              if (this.name == f.name && f.checked)
              {
                vals.push(f.value);
              }
            });

            if (!vals.length) vals.push('');
            break;

          default:
            return true;
        }

        $(vals).each(function()
        {
          if (!f.def) return;

          if (f.def.condition && !validators.matchCondition(f.def.condition)) return;

          var val = $.trim(this);
          var is_num = f.def.sFormat && f.def.sFormat == 'bNumeric';

          if (f.def.bMandatory && validators.empty(val, is_num))
          {
            throw { error: 'mandatory', field: f, value: this };
          }
          else if (!f.def.bMandatory && validators.empty(val, is_num))
          {
            return;
          }

          if (f.def.sLength && !validators.len(val, f.def.sLength))
            throw { error: 'length', field: f, value: this };

          if (!validators.empty(val, is_num) && f.def.sFormat && !validators[f.def.sFormat](val))
            throw { error: 'format', field: f, value: this };
        });
      }

      var validators = {};
      validators.matchCondition = function(cond)
      {
        var tmp = cond.split(':');
        var f = $(tmp[0]).get(0);

        if (!f) return true;

        switch (tmp[1])
        {
          case 'checked':
            return f.checked;
        }

        return true;
      }

      validators.empty = function(val, is_num)
      {
        return (val.length == 0 || is_num && val == 0 || val == '-');
      }

      validators.len = function(val, len)
      {
        if (len.indexOf('-') > -1)
        {
          var l = len.split('-');
          var min = l[0] ? parseInt(l[0]) : 0;
          var max = l[1] ? parseInt(l[1]) : 1;

          if (max > 0 && max < min) return false;
          if (val.length < min) return false;
          if (max > 0 && val.length > max) return false;

          return true;
        }

        if (val.length == len) return true;

        return false;
      }

      validators.bAlphanumeric = function(val)
      {
        return val.match(/^[\sa-zöäüß0-9\.\-]+$/gi) ? true : false;
      }

      validators.bNumeric = function(val)
      {
        return val.match(/^[0-9]+$/g) ? true : false;
      }

      validators.bAlphabetical = function(val)
      {
        return val.match(/^[a-zöäüß]+$/gi) ? true : false;
      }

      validators.bEmail = function(val)
      {
        return val.match(/^[a-z0-9_\-\.]+@[a-z0-9_\-\.]+\.[a-z]{2,6}$/gi) ? true : false;
      }

      validators.bUrl = function(val)
      {
        return val.match(/^((?:http|https):\/\/[a-z0-9\/\?=_#&%~-]+(\.[a-z0-9\/\?=_#&%~-]+)+)|(www(\.[a-z0-9\/\?=_#&%~-]+){2,})$/gi) ? true : false;
      }

      validators.bGermanDate = function(val)
      {
        return val.match(/^[\d]{2}\.[\d]{2}\.[\d]{4}$/gi) ? true : false;
      }

      function submitByAjax()
      {
        $.post(config.ajax, $(form).serialize(), function(data)
        {
          if (data.success == true)
          {
            if (config.onSuccess)
            {
              config.onSuccess(data);
            }
            return;
          }

          $($(form).get(0).elements).each(function() 
          { 
            if (this.unmark) this.unmark();
          });

          $(data.errors).each(function() 
          {
            handleError(this);
          });

          if (data.errors.ERROR_CODE && config.error_msg && config.errors[data.errors.ERROR_CODE])
          {
            config.error_msg.Set(config.errors[data.errors.ERROR_CODE]);
          }

          if (config.onError)
          {
            config.onError(data);
          }
        }, 'json');
      }

      function restoreValues(values)
      {
        $(elements).each(function()
        {
          if (!values[this.name]) return;

          switch (this.type)
          {
            case 'text':
            case 'hidden':
              $(this).val(values[this.name]);
              break;

            case 'checkbox':
            case 'radio':
              this.checked = this.value == values[this.name];
              break;

            case 'select-one':
            case 'select-multiple':
              var f = this;
              $(this.options).each(function()
              {
                if (this.value == values[f.name])
                {
                  this.selected = true;
                }
              });
              break;
          }
        });
      }

      function setMessage(msg)
      {
        if (!config.append_errors)
        {
          $(this).html(msg);
          return;
        }

        if (current_msg.indexOf(msg) < 0)
        {
          current_msg.push(msg);
          (this.tagName.toLowerCase() == 'ul' || this.tagName.toLowerCase() == 'ol')
             ? $(this).append('<li>' + msg + '</li>')
             : $(this).append(msg + config.append_token);
        }
      }

      function handleError(e)
      {
        var f = typeof e.field != 'object' ? $('input[name='+e.field+']', form).get(0) : e.field;
            f.mark();

        var errors = config.errors[f.name];
        if (!errors)
          errors = config.errors[f.name.replace(/\[\]/, '')];

        if (errors && errors[e.error])
          msg = errors[e.error];

        else if (errors && errors['default'])
          msg = errors['default'];

        else if (config.errors['_global_'])
          msg = config.errors['_global_'];

        else return;

        config.error_msg ? config.error_msg.Set(msg) : f.error_msg.Set(msg);
      }

      return this;
    }
  });

})(jQuery);

