(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module unless amdModuleId is set
    define(["jquery"], function (a0) {
      return (factory(a0));
    });
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory(require("jquery"));
  } else {
    factory(jQuery);
  }
}(this, function (jQuery) {

/** File generated by Grunt -- do not modify
 *  JQUERY-FORM-VALIDATOR
 *
 *  @version 2.2.201
 *  @website http://formvalidator.net/
 *  @author Victor Jonsson, http://victorjonsson.se
 *  @license MIT
 */
/**
 * Deprecated functions and attributes
 * @todo: Remove in release of 3.0
 */
(function ($, undefined) {

  'use strict';

  /**
   * @deprecated
   * @param language
   * @param conf
   */
  $.fn.validateForm = function (language, conf) {
    $.formUtils.warn('Use of deprecated function $.validateForm, use $.isValid instead');
    return this.isValid(language, conf, true);
  };

  $(window).on('validatorsLoaded formValidationSetup', function(evt, $form, config) {
    if( !$form ) {
      $form = $('form');
    }

    addSupportForCustomErrorMessageCallback(config);
    addSupportForElementReferenceInPositionParam(config);
    addSupportForValidationDependingOnCheckedInput($form);
  });


  function addSupportForCustomErrorMessageCallback(config) {
    if (config &&
        config.errorMessagePosition === 'custom' &&
        typeof config.errorMessageCustom === 'function') {

      $.formUtils.warn('Use of deprecated function errorMessageCustom, use config.submitErrorMessageCallback instead');

      config.submitErrorMessageCallback = function($form, errorMessages) {
        config.errorMessageCustom(
            $form,
            config.language.errorTitle,
            errorMessages,
            config
        );
      };
    }
  }

  function addSupportForElementReferenceInPositionParam(config) {
    if (config.errorMessagePosition && typeof config.errorMessagePosition === 'object') {
      $.formUtils.warn('Deprecated use of config parameter errorMessagePosition, use config.submitErrorMessageCallback instead');
      var $errorMessageContainer = config.errorMessagePosition;
      config.errorMessagePosition = 'top';
      config.submitErrorMessageCallback = function() {
        return $errorMessageContainer;
      };
    }
  }

  function addSupportForValidationDependingOnCheckedInput($form) {
    var $inputsDependingOnCheckedInputs = $form.find('[data-validation-if-checked]');
    if ($inputsDependingOnCheckedInputs.length) {
      $.formUtils.warn(
        'Detected use of attribute "data-validation-if-checked" which is '+
        'deprecated. Use "data-validation-depends-on" provided by module "logic"'
      );
    }

    $inputsDependingOnCheckedInputs
      .on('beforeValidation', function() {

        var $elem = $(this),
          nameOfDependingInput = $elem.valAttr('if-checked');

        // Set the boolean telling us that the validation depends
        // on another input being checked
        var $dependingInput = $('input[name="' + nameOfDependingInput + '"]', $form),
          dependingInputIsChecked = $dependingInput.is(':checked'),
          valueOfDependingInput = ($.formUtils.getValue($dependingInput) || '').toString(),
          requiredValueOfDependingInput = $elem.valAttr('if-checked-value');

        if (!dependingInputIsChecked || !(
              !requiredValueOfDependingInput ||
              requiredValueOfDependingInput === valueOfDependingInput
          )) {
          $elem.valAttr('skipped', true);
        }

      });
    }

})(jQuery);

/**
 * Utility methods used for displaying error messages (attached to $.formUtils)
 */
(function ($) {

  'use strict';

  var dialogs = {

    resolveErrorMessage: function($elem, validator, validatorName, conf, language) {
      var errorMsgAttr = conf.validationErrorMsgAttribute + '-' + validatorName.replace('validate_', ''),
        validationErrorMsg = $elem.attr(errorMsgAttr);

      if (!validationErrorMsg) {
        validationErrorMsg = $elem.attr(conf.validationErrorMsgAttribute);
        if (!validationErrorMsg) {
          if (typeof validator.errorMessageKey !== 'function') {
            validationErrorMsg = language[validator.errorMessageKey];
          }
          else {
            validationErrorMsg = language[validator.errorMessageKey(conf)];
          }
          if (!validationErrorMsg) {
            validationErrorMsg = validator.errorMessage;
          }
        }
      }
      return validationErrorMsg;
    },
    getParentContainer: function ($elem) {
      if ($elem.valAttr('error-msg-container')) {
        return $($elem.valAttr('error-msg-container'));
      } else {
        var $parent = $elem.parent();
        if (!$parent.hasClass('form-group') && !$parent.closest('form').hasClass('form-horizontal')) {
          var $formGroup = $parent.closest('.form-group');
          if ($formGroup.length) {
            return $formGroup.eq(0);
          }
        }
        return $parent;
      }
    },
    applyInputErrorStyling: function ($input, conf) {
      $input
        .addClass(conf.errorElementClass)
        .removeClass('valid');

      this.getParentContainer($input)
        .addClass(conf.inputParentClassOnError)
        .removeClass(conf.inputParentClassOnSuccess);

      if (conf.borderColorOnError !== '') {
        $input.css('border-color', conf.borderColorOnError);
      }
    },
    applyInputSuccessStyling: function($input, conf) {
      $input.addClass('valid');
      this.getParentContainer($input)
        .addClass(conf.inputParentClassOnSuccess);
    },
    removeInputStylingAndMessage: function($input, conf) {

      // Reset input css
      $input
        .removeClass('valid')
        .removeClass(conf.errorElementClass)
        .css('border-color', '');

      var $parentContainer = dialogs.getParentContainer($input);

      // Reset parent css
      $parentContainer
        .removeClass(conf.inputParentClassOnError)
        .removeClass(conf.inputParentClassOnSuccess);

      // Remove possible error message
      if (typeof conf.inlineErrorMessageCallback === 'function') {
        var $errorMessage = conf.inlineErrorMessageCallback($input, conf);
        if ($errorMessage) {
          $errorMessage.html('');
        }
      } else {
        $parentContainer
          .find('.' + conf.errorMessageClass)
          .remove();
      }

    },
    removeAllMessagesAndStyling: function($form, conf) {

      // Remove error messages in top of form
      if (typeof conf.submitErrorMessageCallback === 'function') {
        var $errorMessagesInTopOfForm = conf.submitErrorMessageCallback($form, conf);
        if ($errorMessagesInTopOfForm) {
          $errorMessagesInTopOfForm.html('');
        }
      } else {
        $form.find('.' + conf.errorMessageClass + '.alert').remove();
      }

      // Remove input css/messages
      $form.find('.' + conf.errorElementClass + ',.valid').each(function() {
        dialogs.removeInputStylingAndMessage($(this), conf);
      });
    },
    setInlineMessage: function ($input, errorMsg, conf) {

      this.applyInputErrorStyling($input, conf);

      var custom = document.getElementById($input.attr('name') + '_err_msg'),
        $messageContainer = false,
        setErrorMessage = function ($elem) {
          $.formUtils.$win.trigger('validationErrorDisplay', [$input, $elem]);
          $elem.html(errorMsg);
        },
        addErrorToMessageContainer = function() {
          var $found = false;
          $messageContainer.find('.' + conf.errorMessageClass).each(function () {
            if (this.inputReferer === $input[0]) {
              $found = $(this);
              return false;
            }
          });
          console.log($found);
          if ($found) {
            if (!errorMsg) {
              $found.remove();
            } else {
              setErrorMessage($found);
            }
          } else if(errorMsg !== '') {
            $message = $('<div class="' + conf.errorMessageClass + ' alert"></div>');
            setErrorMessage($message);
            $message[0].inputReferer = $input[0];
            $messageContainer.prepend($message);
          }
        },
        $message;

      if (custom) {
        // Todo: remove in 3.0
        $.formUtils.warn('Using deprecated element reference ' + custom.id);
        $messageContainer = $(custom);
        addErrorToMessageContainer();
      } else if (typeof conf.inlineErrorMessageCallback === 'function') {
        $messageContainer = conf.inlineErrorMessageCallback($input, conf);
        if (!$messageContainer) {
          // Error display taken care of by inlineErrorMessageCallback
          return;
        }
        addErrorToMessageContainer();
      } else {
        var $parent = this.getParentContainer($input);
        $message = $parent.find('.' + conf.errorMessageClass + '.help-block');
        if ($message.length === 0) {
          $message = $('<span></span>').addClass('help-block').addClass(conf.errorMessageClass);
          $message.appendTo($parent);
        }
        setErrorMessage($message);
      }
    },
    setMessageInTopOfForm: function ($form, errorMessages, conf, lang) {
      var view = '<div class="{errorMessageClass} alert alert-danger">'+
                    '<strong>{errorTitle}</strong>'+
                    '<ul>{fields}</ul>'+
                '</div>',
          $container = false;

      if (typeof conf.submitErrorMessageCallback === 'function') {
        $container = conf.submitErrorMessageCallback($form, errorMessages, conf);
        console.log($container);
        if (!$container) {
          // message display taken care of by callback
          return;
        }
      }

      var viewParams = {
            errorTitle: lang.errorTitle,
            fields: '',
            errorMessageClass: conf.errorMessageClass
          };

      $.each(errorMessages, function (i, msg) {
        viewParams.fields += '<li>'+msg+'</li>';
      });

      $.each(viewParams, function(param, value) {
        view = view.replace('{'+param+'}', value);
      });

      if ($container) {
        $container.html(view);
      } else {
        $form.children().eq(0).before($(view));
      }
    }
  };

  $.formUtils = $.extend($.formUtils || {}, {
    dialogs: dialogs
  });

})(jQuery);

/**
 * File declaring all methods if this plugin which is applied to $.fn.
 */
(function($, window) {

  'use strict';

  var _helpers = 0;


  /**
   * Assigns validateInputOnBlur function to elements blur event
   *
   * @param {Object} language Optional, will override $.formUtils.LANG
   * @param {Object} conf Optional, will override the default settings
   * @return {jQuery}
   */
  $.fn.validateOnBlur = function (language, conf) {
    this.find('*[data-validation]')
      .bind('blur.validation', function () {
        $(this).validateInputOnBlur(language, conf, true, 'blur');
      });
    if (conf.validateCheckboxRadioOnClick) {
      // bind click event to validate on click for radio & checkboxes for nice UX
      this.find('input[type=checkbox][data-validation],input[type=radio][data-validation]')
        .bind('click.validation', function () {
          $(this).validateInputOnBlur(language, conf, true, 'click');
        });
    }

    return this;
  };

  /*
   * Assigns validateInputOnBlur function to elements custom event
   * @param {Object} language Optional, will override $.formUtils.LANG
   * @param {Object} settings Optional, will override the default settings
   * * @return {jQuery}
   */
  $.fn.validateOnEvent = function (language, config) {
    var $elements = this[0].nodeName === 'FORM' ? this.find('*[data-validation-event]') : this;
    $elements
      .each(function () {
        var $el = $(this),
          etype = $el.valAttr('event');
        if (etype) {
          $el
            .unbind(etype + '.validation')
            .bind(etype + '.validation', function (evt) {
              if( (evt || {}).keyCode !== 9 ) {
                $(this).validateInputOnBlur(language, config, true, etype);
              }
            });
        }
      });
    return this;
  };

  /**
   * fade in help message when input gains focus
   * fade out when input loses focus
   * <input data-help="The info that I want to display for the user when input is focused" ... />
   *
   * @param {String} attrName - Optional, default is data-help
   * @return {jQuery}
   */
  $.fn.showHelpOnFocus = function (attrName) {
    if (!attrName) {
      attrName = 'data-validation-help';
    }

    // Remove previously added event listeners
    this.find('.has-help-txt')
      .valAttr('has-keyup-event', false)
      .removeClass('has-help-txt');

    // Add help text listeners
    this.find('textarea,input').each(function () {
      var $elem = $(this),
        className = 'jquery_form_help_' + (++_helpers),
        help = $elem.attr(attrName);

      if (help) {
        $elem
          .addClass('has-help-txt')
          .unbind('focus.help')
          .bind('focus.help', function () {
            var $help = $elem.parent().find('.' + className);
            if ($help.length === 0) {
              $help = $('<span />')
                .addClass(className)
                .addClass('help')
                .addClass('help-block') // twitter bs
                .text(help)
                .hide();

              $elem.after($help);
            }
            $help.fadeIn();
          })
          .unbind('blur.help')
          .bind('blur.help', function () {
            $(this)
              .parent()
              .find('.' + className)
              .fadeOut('slow');
          });
      }
    });

    return this;
  };

  /**
   * @param {Function} cb
   * @param {Object} [conf]
   * @param {Object} [lang]
   */
  $.fn.validate = function(cb, conf, lang) {
    var language = $.extend({}, $.formUtils.LANG, lang || {});
    this.each(function() {
      var $elem = $(this),
        formDefaultConfig = $elem.closest('form').get(0).validationConfig || {};

      $elem.one('validation', function(evt, isValid) {
        if ( typeof cb === 'function' ) {
          cb(isValid, this, evt);
        }
      });

      $elem.validateInputOnBlur(
        language,
        $.extend({}, formDefaultConfig, conf || {}),
        true
      );
    });
  };

  /**
   * Tells whether or not validation of this input will have to postpone the form submit ()
   * @returns {Boolean}
   */
  $.fn.willPostponeValidation = function() {
    return (this.valAttr('suggestion-nr') ||
      this.valAttr('postpone') ||
      this.hasClass('hasDatepicker')) &&
      !window.postponedValidation;
  };

  /**
   * Validate single input when it loses focus
   * shows error message in a span element
   * that is appended to the parent element
   *
   * @param {Object} [language] Optional, will override $.formUtils.LANG
   * @param {Object} [conf] Optional, will override the default settings
   * @param {Boolean} attachKeyupEvent Optional
   * @param {String} eventType
   * @return {jQuery}
   */
  $.fn.validateInputOnBlur = function (language, conf, attachKeyupEvent, eventType) {

    $.formUtils.eventType = eventType;

    if ( this.willPostponeValidation() ) {
      // This validation has to be postponed
      var _self = this,
        postponeTime = this.valAttr('postpone') || 200;

      window.postponedValidation = function () {
        _self.validateInputOnBlur(language, conf, attachKeyupEvent, eventType);
        window.postponedValidation = false;
      };

      setTimeout(function () {
        if (window.postponedValidation) {
          window.postponedValidation();
        }
      }, postponeTime);

      return this;
    }

    language = $.extend({}, $.formUtils.LANG, language || {});
    $.formUtils.dialogs.removeInputStylingAndMessage(this, conf);

    var $elem = this,
      $form = $elem.closest('form'),
      result = $.formUtils.validateInput(
        $elem,
        language,
        conf,
        $form,
        eventType
      );

    if (attachKeyupEvent) {
      $elem.unbind('keyup.validation');
    }

    if (result.shouldChangeDisplay) {
      if (result.isValid) {
        $.formUtils.dialogs.applyInputSuccessStyling($elem, conf);
      } else {
        $.formUtils.dialogs.setInlineMessage($elem, result.errorMsg, conf);
      }
    }

    if (!result.isValid && attachKeyupEvent) {
      $elem.bind('keyup.validation', function (evt) {
        if( evt.keyCode !== 9 ) {
          $(this).validateInputOnBlur(language, conf, false, 'keyup');
        }
      });
    }

    return this;
  };

  /**
   * Short hand for fetching/adding/removing element attributes
   * prefixed with 'data-validation-'
   *
   * @param {String} name
   * @param {String|Boolean} [val]
   * @return {String|undefined|jQuery}
   * @protected
   */
  $.fn.valAttr = function (name, val) {
    if (val === undefined) {
      return this.attr('data-validation-' + name);
    } else if (val === false || val === null) {
      return this.removeAttr('data-validation-' + name);
    } else {
      name = ((name.length > 0) ? '-' + name : '');
      return this.attr('data-validation' + name, val);
    }
  };

  /**
   * Function that validates all inputs in active form
   *
   * @param {Object} [language]
   * @param {Object} [conf]
   * @param {Boolean} [displayError] Defaults to true
   */
  $.fn.isValid = function (language, conf, displayError) {

    if ($.formUtils.isLoadingModules) {
      var $self = this;
      setTimeout(function () {
        $self.isValid(language, conf, displayError);
      }, 200);
      return null;
    }

    conf = $.extend({}, $.formUtils.defaultConfig(), conf || {});
    language = $.extend({}, $.formUtils.LANG, language || {});
    displayError = displayError !== false;

    if ($.formUtils.errorDisplayPreventedWhenHalted) {
      // isValid() was called programmatically with argument displayError set
      // to false when the validation was halted by any of the validators
      delete $.formUtils.errorDisplayPreventedWhenHalted;
      displayError = false;
    }

    $.formUtils.isValidatingEntireForm = true;
    $.formUtils.haltValidation = false;

    /**
     * Adds message to error message stack if not already in the message stack
     *
     * @param {String} mess
     * @para {jQuery} $elem
     */
    var addErrorMessage = function (mess, $elem) {
        if ($.inArray(mess, errorMessages) < 0) {
          errorMessages.push(mess);
        }
        errorInputs.push($elem);
        $elem.attr('current-error', mess);
        if (displayError) {
          $.formUtils.dialogs.applyInputErrorStyling($elem, conf);
        }
      },

      /** Holds inputs (of type checkox or radio) already validated, to prevent recheck of mulitple checkboxes & radios */
      checkedInputs = [],

      /** Error messages for this validation */
      errorMessages = [],

      /** Input elements which value was not valid */
      errorInputs = [],

      /** Form instance */
      $form = this,

      /**
       * Tells whether or not to validate element with this name and of this type
       *
       * @param {String} name
       * @param {String} type
       * @return {Boolean}
       */
      ignoreInput = function (name, type) {
        if (type === 'submit' || type === 'button' || type === 'reset') {
          return true;
        }
        return $.inArray(name, conf.ignore || []) > -1;
      };

    // Reset style and remove error class
    if (displayError) {
      $.formUtils.dialogs.removeAllMessagesAndStyling($form, conf);
    }

    // Validate element values
    $form.find('input,textarea,select').filter(':not([type="submit"],[type="button"])').each(function () {
      var $elem = $(this),
        elementType = $elem.attr('type'),
        isCheckboxOrRadioBtn = elementType === 'radio' || elementType === 'checkbox',
        elementName = $elem.attr('name');

      if (!ignoreInput(elementName, elementType) && (!isCheckboxOrRadioBtn || $.inArray(elementName, checkedInputs) < 0)) {

        if (isCheckboxOrRadioBtn) {
          checkedInputs.push(elementName);
        }

        var result = $.formUtils.validateInput(
          $elem,
          language,
          conf,
          $form,
          'submit'
        );

        if (result.shouldChangeDisplay) {
          if (!result.isValid) {
            addErrorMessage(result.errorMsg, $elem);
          } else if (result.isValid) {
            $elem.valAttr('current-error', false);
            $.formUtils.dialogs.applyInputSuccessStyling($elem, conf);
          }
        }
      }

    });

    // Run validation callback
    if (typeof conf.onValidate === 'function') {
      var errors = conf.onValidate($form);
      if ($.isArray(errors)) {
        $.each(errors, function (i, err) {
          addErrorMessage(err.message, err.element);
        });
      }
      else if (errors && errors.element && errors.message) {
        addErrorMessage(errors.message, errors.element);
      }
    }

    // Reset form validation flag
    $.formUtils.isValidatingEntireForm = false;

    // Validation failed
    if (!$.formUtils.haltValidation && errorInputs.length > 0) {

      if (displayError) {

        if (conf.errorMessagePosition === 'top') {
          $.formUtils.dialogs.setMessageInTopOfForm($form, errorMessages, conf, language);
        } else {
          $.each(errorInputs, function (i, $input) {
            $.formUtils.dialogs.setInlineMessage($input, $input.attr('current-error'), conf);
          });
        }
        if (conf.scrollToTopOnError) {
          $.formUtils.$win.scrollTop($form.offset().top - 20);
        }

      }

      return false;
    }

    if (!displayError && $.formUtils.haltValidation) {
      $.formUtils.errorDisplayPreventedWhenHalted = true;
    }

    return !$.formUtils.haltValidation;
  };

  /**
   * Plugin for displaying input length restriction
   */
  $.fn.restrictLength = function (maxLengthElement) {
    new $.formUtils.lengthRestriction(this, maxLengthElement);
    return this;
  };

  /**
   * Add suggestion dropdown to inputs having data-suggestions with a comma
   * separated string with suggestions
   * @param {Array} [settings]
   * @returns {jQuery}
   */
  $.fn.addSuggestions = function (settings) {
    var sugs = false;
    this.find('input').each(function () {
      var $field = $(this);

      sugs = $.split($field.attr('data-suggestions'));

      if (sugs.length > 0 && !$field.hasClass('has-suggestions')) {
        $.formUtils.suggest($field, sugs, settings);
        $field.addClass('has-suggestions');
      }
    });
    return this;
  };


})(jQuery, window);

/**
 * Utility methods used for handling loading of modules (attached to $.formUtils)
 */
(function($) {

  'use strict';

  $.formUtils = $.extend($.formUtils || {}, {

    /**
     * @var {Boolean}
     */
    isLoadingModules: false,

    /**
     * @var {Object}
     */
    loadedModules: {},

    /**
     * @example
     *  $.formUtils.loadModules('date, security.dev');
     *
     * Will load the scripts date.js and security.dev.js from the
     * directory where this script resides. If you want to load
     * the modules from another directory you can use the
     * path argument.
     *
     * The script will be cached by the browser unless the module
     * name ends with .dev
     *
     * @param {String} modules - Comma separated string with module file names (no directory nor file extension)
     * @param {String} [path] - Optional, path where the module files is located if their not in the same directory as the core modules
     * @param {function} [callback] - Optional, whether or not to fire event 'load' when modules finished loading
     */
    loadModules: function (modules, path, callback) {

      if ($.formUtils.isLoadingModules) {
        setTimeout(function () {
          $.formUtils.loadModules(modules, path, callback);
        }, 10);
        return;
      }

      var hasLoadedAnyModule = false,
        loadModuleScripts = function (modules, path) {

          var moduleList = $.split(modules),
            numModules = moduleList.length,
            moduleLoadedCallback = function () {
              numModules--;
              if (numModules === 0) {
                $.formUtils.isLoadingModules = false;
                if (callback && hasLoadedAnyModule) {
                  if( typeof callback === 'function' ) {
                    callback();
                  }
                }
              }
            };


          if (numModules > 0) {
            $.formUtils.isLoadingModules = true;
          }

          var cacheSuffix = '?_=' + ( new Date().getTime() ),
            appendToElement = document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0];

          $.each(moduleList, function (i, modName) {
            modName = $.trim(modName);
            if (modName.length === 0) {
              moduleLoadedCallback();
            }
            else {
              var scriptUrl = path + modName + (modName.slice(-3) === '.js' ? '' : '.js'),
                script = document.createElement('SCRIPT');

              if (scriptUrl in $.formUtils.loadedModules) {
                // already loaded
                moduleLoadedCallback();
              }
              else {

                // Remember that this script is loaded
                $.formUtils.loadedModules[scriptUrl] = 1;
                hasLoadedAnyModule = true;

                // Load the script
                script.type = 'text/javascript';
                script.onload = moduleLoadedCallback;
                script.src = scriptUrl + ( scriptUrl.slice(-7) === '.dev.js' ? cacheSuffix : '' );
                script.onerror = function() {
                  $.formUtils.warn('Unable to load form validation module '+scriptUrl);
                };
                script.onreadystatechange = function () {
                  // IE 7 fix
                  if (this.readyState === 'complete' || this.readyState === 'loaded') {
                    moduleLoadedCallback();
                    // Handle memory leak in IE
                    this.onload = null;
                    this.onreadystatechange = null;
                  }
                };
                appendToElement.appendChild(script);
              }
            }
          });
        };

      if (path) {
        loadModuleScripts(modules, path);
      } else {
        var findScriptPathAndLoadModules = function () {
          var foundPath = false;
          $('script[src*="form-validator"]').each(function () {
            foundPath = this.src.substr(0, this.src.lastIndexOf('/')) + '/';
            if (foundPath === '/') {
              foundPath = '';
            }
            return false;
          });

          if (foundPath !== false) {
            loadModuleScripts(modules, foundPath);
            return true;
          }
          return false;
        };

        if (!findScriptPathAndLoadModules()) {
          $(findScriptPathAndLoadModules);
        }
      }
    }

  });

})(jQuery);

/**
 * Setup function for the plugin
 */
(function ($) {

  'use strict';


  /**
   * A bit smarter split function
   * delimiter can be space, comma, dash or pipe
   * @param {String} val
   * @param {Function|String} [callback]
   * @returns {Array|void}
   */
  $.split = function (val, callback) {
    if (typeof callback !== 'function') {
      // return array
      if (!val) {
        return [];
      }
      var values = [];
      $.each(val.split(callback ? callback : /[,|\-\s]\s*/g),
        function (i, str) {
          str = $.trim(str);
          if (str.length) {
            values.push(str);
          }
        }
      );
      return values;
    } else if (val) {
      // exec callback func on each
      $.each(val.split(/[,|\-\s]\s*/g),
        function (i, str) {
          str = $.trim(str);
          if (str.length) {
            return callback(str, i);
          }
        }
      );
    }
  };

  /**
   * Short hand function that makes the validation setup require less code
   * @param conf
   */
  $.validate = function (conf) {

    var defaultConf = $.extend($.formUtils.defaultConfig(), {
      form: 'form',
      validateOnEvent: false,
      validateOnBlur: true,
      validateCheckboxRadioOnClick: true,
      showHelpOnFocus: true,
      addSuggestions: true,
      modules: '',
      onModulesLoaded: null,
      language: false,
      onSuccess: false,
      onError: false,
      onElementValidate: false
    });

    conf = $.extend(defaultConf, conf || {});

    if( conf.lang && conf.lang !== 'en' ) {
      var langModule = 'lang/'+conf.lang+'.js';
      conf.modules += conf.modules.length ? ','+langModule : langModule;
    }

    // Add validation to forms
    $(conf.form).each(function (i, form) {

      // Make a reference to the config for this form
      form.validationConfig = conf;

      // Trigger jQuery event that we're about to setup validation
      var $form = $(form);
      // $.formUtils.$win.trigger('formValidationSetup', [$form, conf]);
      $form.trigger('formValidationSetup', [$form, conf]);

      // Remove classes and event handlers that might have been
      // added by a previous call to $.validate
      $form.find('.has-help-txt')
          .unbind('focus.validation')
          .unbind('blur.validation');

      $form
        .removeClass('has-validation-callback')
        .unbind('submit.validation')
        .unbind('reset.validation')
        .find('input[data-validation],textarea[data-validation]')
          .unbind('blur.validation');

      // Validate when submitted
      $form.bind('submit.validation', function () {

        var $form = $(this);

        if ($.formUtils.haltValidation) {
          // pressing several times on submit button while validation is halted
          return false;
        }

        if ($.formUtils.isLoadingModules) {
          setTimeout(function () {
            $form.trigger('submit.validation');
          }, 200);
          return false;
        }

        var valid = $form.isValid(conf.language, conf);

        if ($.formUtils.haltValidation) {
          // Validation got halted by one of the validators
          return false;
        } else {
          if (valid && typeof conf.onSuccess === 'function') {
            var callbackResponse = conf.onSuccess($form);
            if (callbackResponse === false) {
              return false;
            }
          } else if (!valid && typeof conf.onError === 'function') {
            conf.onError($form);
            return false;
          } else {
            return valid;
          }
        }
      })
      .bind('reset.validation', function () {
        $.formUtils.dialogs.removeAllMessagesAndStyling($form, conf);
      })
      .addClass('has-validation-callback');

      if (conf.showHelpOnFocus) {
        $form.showHelpOnFocus();
      }
      if (conf.addSuggestions) {
        $form.addSuggestions();
      }
      if (conf.validateOnBlur) {
        $form.validateOnBlur(conf.language, conf);
        $form.bind('html5ValidationAttrsFound', function () {
          $form.validateOnBlur(conf.language, conf);
        });
      }
      if (conf.validateOnEvent) {
        $form.validateOnEvent(conf.language, conf);
      }
    });

    if (conf.modules !== '') {
      $.formUtils.loadModules(conf.modules, false, function() {
        if (typeof conf.onModulesLoaded === 'function') {
          conf.onModulesLoaded();
        }
        var $form = typeof conf.form === 'string' ? $(conf.form) : conf.form;
        $.formUtils.$win.trigger('validatorsLoaded', [$form, conf]);
      });
    }
  };

})(jQuery);

/**
 * Utility methods and properties attached to $.formUtils
 */
(function($, window) {

  'use strict';

  var $win = $(window);

  $.formUtils = $.extend($.formUtils || {}, {

    $win: $win,

    /**
     * Default config for $(...).isValid();
     */
    defaultConfig: function () {
      return {
        ignore: [], // Names of inputs not to be validated even though `validationRuleAttribute` containing the validation rules tells us to
        errorElementClass: 'error', // Class that will be put on elements which value is invalid
        borderColorOnError: '#b94a48', // Border color of elements which value is invalid, empty string to not change border color
        errorMessageClass: 'form-error', // class name of div containing error messages when validation fails
        validationRuleAttribute: 'data-validation', // name of the attribute holding the validation rules
        validationErrorMsgAttribute: 'data-validation-error-msg', // define custom err msg inline with element
        errorMessagePosition: 'element', // Can be either "top" or "element" or "custom"
        errorMessageTemplate: {
          container: '<div class="{errorMessageClass} alert alert-danger">{messages}</div>',
          messages: '<strong>{errorTitle}</strong><ul>{fields}</ul>',
          field: '<li>{msg}</li>'
        },
        scrollToTopOnError: true,
        dateFormat: 'yyyy-mm-dd',
        addValidClassOnAll: false, // whether or not to apply class="valid" even if the input wasn't validated
        decimalSeparator: '.',
        inputParentClassOnError: 'has-error', // twitter-bootstrap default class name
        inputParentClassOnSuccess: 'has-success', // twitter-bootstrap default class name
        validateHiddenInputs: false, // whether or not hidden inputs should be validated
        inlineErrorMessageCallback: false,
        submitErrorMessageCallback: false
      };
    },

    /**
     * Available validators
     */
    validators: {},

    /**
     * Events triggered by form validator
     */
    _events: {load: [], valid: [], invalid: []},

    /**
     * Setting this property to true during validation will
     * stop further validation from taking place and form will
     * not be sent
     */
    haltValidation: false,

    /**
     * This variable will be true $.fn.isValid() is called
     * and false when $.fn.validateOnBlur is called
     */
    isValidatingEntireForm: false,

    /**
     * Function for adding a validator
     * @param {Object} validator
     */
    addValidator: function (validator) {
      // prefix with "validate_" for backward compatibility reasons
      var name = validator.name.indexOf('validate_') === 0 ? validator.name : 'validate_' + validator.name;
      if (validator.validateOnKeyUp === undefined) {
        validator.validateOnKeyUp = true;
      }
      this.validators[name] = validator;
    },

    /**
     * Warn user via the console if available
     */
    warn: function(msg) {
      if( 'console' in window ) {
        if( typeof window.console.warn === 'function' ) {
          window.console.warn(msg);
        } else if( typeof window.console.log === 'function' ) {
          window.console.log(msg);
        }
      } else {
        alert(msg);
      }
    },

    /**
     * Same as input $.fn.val() but also supporting input of typ radio or checkbox
     * @example
     *
     *  $.formUtils.getValue('.myRadioButtons', $('#some-form'));
     *  $.formUtils.getValue($('#some-form').find('.check-boxes'));
     *
     * @param query
     * @param $parent
     * @returns {String|Boolean}
     */
    getValue: function(query, $parent) {
      var $inputs = $parent ? $parent.find(query) : query;
      if ($inputs.length > 0 ) {
        var type = $inputs.eq(0).attr('type');
        if (type === 'radio' || type === 'checkbox') {
          return $inputs.filter(':checked').val();
        } else {
          return $inputs.val();
        }
      }
      return false;
    },

    /**
     * Validate the value of given element according to the validation rules
     * found in the attribute data-validation. Will return an object representing
     * a validation result, having the props shouldChangeDisplay, isValid and errorMsg
     * @param {jQuery} $elem
     * @param {Object} language ($.formUtils.LANG)
     * @param {Object} conf
     * @param {jQuery} $form
     * @param {String} [eventContext]
     * @return {Object}
     */
    validateInput: function ($elem, language, conf, $form, eventContext) {

      conf = conf || $.formUtils.defaultConfig();
      language = language || $.formUtils.LANG;

      var value = this.getValue($elem);

      $elem
        .valAttr('skipped', false)
        .one('beforeValidation', function() {
          // Skip input because its hidden or disabled
          // Doing this in a callback makes it possible for others to prevent the default
          // behaviour by binding to the same event and call evt.stopImmediatePropagation()
          if ($elem.attr('disabled') || (!$elem.is(':visible') && !conf.validateHiddenInputs)) {
            $elem.valAttr('skipped', 1);
          }
        })
        .trigger('beforeValidation', [value, conf, language]);

      var inputIsOptional = $elem.valAttr('optional') === 'true',
          skipBecauseItsEmpty = !value && inputIsOptional,
          validationRules = $elem.attr(conf.validationRuleAttribute),
          isValid = true,
          errorMsg = '',
          result = {isValid: true, shouldChangeDisplay:true, errorMsg:''};

      // For input type="number", browsers attempt to parse the entered value into a number.
      // If the input is not numeric, browsers handle the situation differently:
      // Chrome 48 simply disallows non-numeric input; FF 44 clears out the input box on blur;
      // Safari 5 parses the entered string to find a leading number.
      // If the input fails browser validation, the browser sets the input value equal to an empty string.
      // Therefore, we cannot distinguish (apart from hacks) between an empty input type="text" and one with a
      // value that can't be parsed by the browser.

      if (!validationRules || skipBecauseItsEmpty || $elem.valAttr('skipped')) {
        result.shouldChangeDisplay = conf.addValidClassOnAll;
        return result;
      }

      // Filter out specified characters
      var ignore = $elem.valAttr('ignore');
      if (ignore) {
        $.each(ignore.split(''), function(i, char) {
          value = value.replace(new RegExp('\\'+char), '');
        });
      }

      $.split(validationRules, function (rule) {

        if (rule.indexOf('validate_') !== 0) {
          rule = 'validate_' + rule;
        }

        var validator = $.formUtils.validators[rule];

        if (validator) {

          // special change of element for checkbox_group rule
          if (rule === 'validate_checkbox_group') {
            // set element to first in group, so error msg attr doesn't need to be set on all elements in group
            $elem = $form.find('[name="' + $elem.attr('name') + '"]:eq(0)');
          }

          if (eventContext !== 'keyup' || validator.validateOnKeyUp) {
            // A validator can prevent itself from getting triggered on keyup
            isValid = validator.validatorFunction(value, $elem, conf, language, $form);
          }

          if (!isValid) {
            errorMsg = $.formUtils.dialogs.resolveErrorMessage($elem, validator, rule, conf, language);
            return false; // break iteration
          }

        } else {

          // todo: Add some validator lookup function and tell immediately which module is missing
          throw new Error('Using undefined validator "' + rule +
            '". Maybe you have forgotten to load the module that "' + rule +'" belongs to?');

        }

      }, ' ');


      if (isValid === false) {
        $elem.trigger('validation', false);
        result.errorMsg = errorMsg;
        result.isValid = false;
        result.shouldChangeDisplay = true;
      } else if (isValid === null) {
        // A validatorFunction returning null means that it's not able to validate
        // the input at this time. Most probably some async stuff need to gets finished
        // first and then the validator will re-trigger the validation.
        result.shouldChangeDisplay = false;
      } else {
        $elem.trigger('validation', true);
        result.shouldChangeDisplay = true;
      }

      // Run element validation callback
      if (typeof conf.onElementValidate === 'function' && errorMsg !== null) {
        conf.onElementValidate(result.isValid, $elem, $form, errorMsg);
      }

      $elem.trigger('afterValidation', [result, eventContext]);

      return result;
    },

    /**
     * Is it a correct date according to given dateFormat. Will return false if not, otherwise
     * an array 0=>year 1=>month 2=>day
     *
     * @param {String} val
     * @param {String} dateFormat
     * @param {Boolean} [addMissingLeadingZeros]
     * @return {Array}|{Boolean}
     */
    parseDate: function (val, dateFormat, addMissingLeadingZeros) {
      var divider = dateFormat.replace(/[a-zA-Z]/gi, '').substring(0, 1),
        regexp = '^',
        formatParts = dateFormat.split(divider || null),
        matches, day, month, year;

      $.each(formatParts, function (i, part) {
        regexp += (i > 0 ? '\\' + divider : '') + '(\\d{' + part.length + '})';
      });

      regexp += '$';

      if (addMissingLeadingZeros) {
        var newValueParts = [];
        $.each(val.split(divider), function(i, part) {
          if(part.length === 1) {
            part = '0'+part;
          }
          newValueParts.push(part);
        });
        val = newValueParts.join(divider);
      }

      matches = val.match(new RegExp(regexp));
      if (matches === null) {
        return false;
      }

      var findDateUnit = function (unit, formatParts, matches) {
        for (var i = 0; i < formatParts.length; i++) {
          if (formatParts[i].substring(0, 1) === unit) {
            return $.formUtils.parseDateInt(matches[i + 1]);
          }
        }
        return -1;
      };

      month = findDateUnit('m', formatParts, matches);
      day = findDateUnit('d', formatParts, matches);
      year = findDateUnit('y', formatParts, matches);

      if ((month === 2 && day > 28 && (year % 4 !== 0 || year % 100 === 0 && year % 400 !== 0)) ||
        (month === 2 && day > 29 && (year % 4 === 0 || year % 100 !== 0 && year % 400 === 0)) ||
        month > 12 || month === 0) {
        return false;
      }
      if ((this.isShortMonth(month) && day > 30) || (!this.isShortMonth(month) && day > 31) || day === 0) {
        return false;
      }

      return [year, month, day];
    },

    /**
     * skum fix. är talet 05 eller lägre ger parseInt rätt int annars får man 0 när man kör parseInt?
     *
     * @param {String} val
     * @return {Number}
     */
    parseDateInt: function (val) {
      if (val.indexOf('0') === 0) {
        val = val.replace('0', '');
      }
      return parseInt(val, 10);
    },

    /**
     * Has month only 30 days?
     *
     * @param {Number} m
     * @return {Boolean}
     */
    isShortMonth: function (m) {
      return (m % 2 === 0 && m < 7) || (m % 2 !== 0 && m > 7);
    },

    /**
     * Restrict input length
     *
     * @param {jQuery} $inputElement Jquery Html object
     * @param {jQuery} $maxLengthElement jQuery Html Object
     * @return void
     */
    lengthRestriction: function ($inputElement, $maxLengthElement) {
      // read maxChars from counter display initial text value
      var maxChars = parseInt($maxLengthElement.text(), 10),
        charsLeft = 0,

      // internal function does the counting and sets display value
        countCharacters = function () {
          var numChars = $inputElement.val().length;
          if (numChars > maxChars) {
            // get current scroll bar position
            var currScrollTopPos = $inputElement.scrollTop();
            // trim value to max length
            $inputElement.val($inputElement.val().substring(0, maxChars));
            $inputElement.scrollTop(currScrollTopPos);
          }
          charsLeft = maxChars - numChars;
          if (charsLeft < 0) {
            charsLeft = 0;
          }

          // set counter text
          $maxLengthElement.text(charsLeft);
        };

      // bind events to this element
      // setTimeout is needed, cut or paste fires before val is available
      $($inputElement).bind('keydown keyup keypress focus blur', countCharacters)
        .bind('cut paste', function () {
          setTimeout(countCharacters, 100);
        });

      // count chars on pageload, if there are prefilled input-values
      $(document).bind('ready', countCharacters);
    },

    /**
     * Test numeric against allowed range
     *
     * @param $value int
     * @param $rangeAllowed str; (1-2, min1, max2, 10)
     * @return array
     */
    numericRangeCheck: function (value, rangeAllowed) {
      // split by dash
      var range = $.split(rangeAllowed),
      // min or max
        minmax = parseInt(rangeAllowed.substr(3), 10);

      if( range.length === 1 && rangeAllowed.indexOf('min') === -1 && rangeAllowed.indexOf('max') === -1 ) {
        range = [rangeAllowed, rangeAllowed]; // only a number, checking agains an exact number of characters
      }

      // range ?
      if (range.length === 2 && (value < parseInt(range[0], 10) || value > parseInt(range[1], 10) )) {
        return [ 'out', range[0], range[1] ];
      } // value is out of range
      else if (rangeAllowed.indexOf('min') === 0 && (value < minmax )) // min
      {
        return ['min', minmax];
      } // value is below min
      else if (rangeAllowed.indexOf('max') === 0 && (value > minmax )) // max
      {
        return ['max', minmax];
      } // value is above max
      // since no other returns executed, value is in allowed range
      return [ 'ok' ];
    },


    _numSuggestionElements: 0,
    _selectedSuggestion: null,
    _previousTypedVal: null,

    /**
     * Utility function that can be used to create plugins that gives
     * suggestions when inputs is typed into
     * @param {jQuery} $elem
     * @param {Array} suggestions
     * @param {Object} settings - Optional
     * @return {jQuery}
     */
    suggest: function ($elem, suggestions, settings) {
      var conf = {
          css: {
            maxHeight: '150px',
            background: '#FFF',
            lineHeight: '150%',
            textDecoration: 'underline',
            overflowX: 'hidden',
            overflowY: 'auto',
            border: '#CCC solid 1px',
            borderTop: 'none',
            cursor: 'pointer'
          },
          activeSuggestionCSS: {
            background: '#E9E9E9'
          }
        },
        setSuggsetionPosition = function ($suggestionContainer, $input) {
          var offset = $input.offset();
          $suggestionContainer.css({
            width: $input.outerWidth(),
            left: offset.left + 'px',
            top: (offset.top + $input.outerHeight()) + 'px'
          });
        };

      if (settings) {
        $.extend(conf, settings);
      }

      conf.css.position = 'absolute';
      conf.css['z-index'] = 9999;
      $elem.attr('autocomplete', 'off');

      if (this._numSuggestionElements === 0) {
        // Re-position suggestion container if window size changes
        $win.bind('resize', function () {
          $('.jquery-form-suggestions').each(function () {
            var $container = $(this),
              suggestID = $container.attr('data-suggest-container');
            setSuggsetionPosition($container, $('.suggestions-' + suggestID).eq(0));
          });
        });
      }

      this._numSuggestionElements++;

      var onSelectSuggestion = function ($el) {
        var suggestionId = $el.valAttr('suggestion-nr');
        $.formUtils._selectedSuggestion = null;
        $.formUtils._previousTypedVal = null;
        $('.jquery-form-suggestion-' + suggestionId).fadeOut('fast');
      };

      $elem
        .data('suggestions', suggestions)
        .valAttr('suggestion-nr', this._numSuggestionElements)
        .unbind('focus.suggest')
        .bind('focus.suggest', function () {
          $(this).trigger('keyup');
          $.formUtils._selectedSuggestion = null;
        })
        .unbind('keyup.suggest')
        .bind('keyup.suggest', function () {
          var $input = $(this),
            foundSuggestions = [],
            val = $.trim($input.val()).toLocaleLowerCase();

          if (val === $.formUtils._previousTypedVal) {
            return;
          }
          else {
            $.formUtils._previousTypedVal = val;
          }

          var hasTypedSuggestion = false,
            suggestionId = $input.valAttr('suggestion-nr'),
            $suggestionContainer = $('.jquery-form-suggestion-' + suggestionId);

          $suggestionContainer.scrollTop(0);

          // Find the right suggestions
          if (val !== '') {
            var findPartial = val.length > 2;
            $.each($input.data('suggestions'), function (i, suggestion) {
              var lowerCaseVal = suggestion.toLocaleLowerCase();
              if (lowerCaseVal === val) {
                foundSuggestions.push('<strong>' + suggestion + '</strong>');
                hasTypedSuggestion = true;
                return false;
              } else if (lowerCaseVal.indexOf(val) === 0 || (findPartial && lowerCaseVal.indexOf(val) > -1)) {
                foundSuggestions.push(suggestion.replace(new RegExp(val, 'gi'), '<strong>$&</strong>'));
              }
            });
          }

          // Hide suggestion container
          if (hasTypedSuggestion || (foundSuggestions.length === 0 && $suggestionContainer.length > 0)) {
            $suggestionContainer.hide();
          }

          // Create suggestion container if not already exists
          else if (foundSuggestions.length > 0 && $suggestionContainer.length === 0) {
            $suggestionContainer = $('<div></div>').css(conf.css).appendTo('body');
            $elem.addClass('suggestions-' + suggestionId);
            $suggestionContainer
              .attr('data-suggest-container', suggestionId)
              .addClass('jquery-form-suggestions')
              .addClass('jquery-form-suggestion-' + suggestionId);
          }

          // Show hidden container
          else if (foundSuggestions.length > 0 && !$suggestionContainer.is(':visible')) {
            $suggestionContainer.show();
          }

          // add suggestions
          if (foundSuggestions.length > 0 && val.length !== foundSuggestions[0].length) {

            // put container in place every time, just in case
            setSuggsetionPosition($suggestionContainer, $input);

            // Add suggestions HTML to container
            $suggestionContainer.html('');
            $.each(foundSuggestions, function (i, text) {
              $('<div></div>')
                .append(text)
                .css({
                  overflow: 'hidden',
                  textOverflow: 'ellipsis',
                  whiteSpace: 'nowrap',
                  padding: '5px'
                })
                .addClass('form-suggest-element')
                .appendTo($suggestionContainer)
                .click(function () {
                  $input.focus();
                  $input.val($(this).text());
                  $input.trigger('change');
                  onSelectSuggestion($input);
                });
            });
          }
        })
        .unbind('keydown.validation')
        .bind('keydown.validation', function (e) {
          var code = (e.keyCode ? e.keyCode : e.which),
            suggestionId,
            $suggestionContainer,
            $input = $(this);

          if (code === 13 && $.formUtils._selectedSuggestion !== null) {
            suggestionId = $input.valAttr('suggestion-nr');
            $suggestionContainer = $('.jquery-form-suggestion-' + suggestionId);
            if ($suggestionContainer.length > 0) {
              var newText = $suggestionContainer.find('div').eq($.formUtils._selectedSuggestion).text();
              $input.val(newText);
              $input.trigger('change');
              onSelectSuggestion($input);
              e.preventDefault();
            }
          }
          else {
            suggestionId = $input.valAttr('suggestion-nr');
            $suggestionContainer = $('.jquery-form-suggestion-' + suggestionId);
            var $suggestions = $suggestionContainer.children();
            if ($suggestions.length > 0 && $.inArray(code, [38, 40]) > -1) {
              if (code === 38) { // key up
                if ($.formUtils._selectedSuggestion === null) {
                  $.formUtils._selectedSuggestion = $suggestions.length - 1;
                }
                else{
                  $.formUtils._selectedSuggestion--;
                }
                if ($.formUtils._selectedSuggestion < 0) {
                  $.formUtils._selectedSuggestion = $suggestions.length - 1;
                }
              }
              else if (code === 40) { // key down
                if ($.formUtils._selectedSuggestion === null) {
                  $.formUtils._selectedSuggestion = 0;
                }
                else {
                  $.formUtils._selectedSuggestion++;
                }
                if ($.formUtils._selectedSuggestion > ($suggestions.length - 1)) {
                  $.formUtils._selectedSuggestion = 0;
                }
              }

              // Scroll in suggestion window
              var containerInnerHeight = $suggestionContainer.innerHeight(),
                containerScrollTop = $suggestionContainer.scrollTop(),
                suggestionHeight = $suggestionContainer.children().eq(0).outerHeight(),
                activeSuggestionPosY = suggestionHeight * ($.formUtils._selectedSuggestion);

              if (activeSuggestionPosY < containerScrollTop || activeSuggestionPosY > (containerScrollTop + containerInnerHeight)) {
                $suggestionContainer.scrollTop(activeSuggestionPosY);
              }

              $suggestions
                .removeClass('active-suggestion')
                .css('background', 'none')
                .eq($.formUtils._selectedSuggestion)
                .addClass('active-suggestion')
                .css(conf.activeSuggestionCSS);

              e.preventDefault();
              return false;
            }
          }
        })
        .unbind('blur.suggest')
        .bind('blur.suggest', function () {
          onSelectSuggestion($(this));
        });

      return $elem;
    },

    /**
     * Error dialogs
     *
     * @var {Object}
     */
    LANG: {
      errorTitle: 'Form submission failed!',
      requiredField: 'This is a required field',
      requiredFields: 'You have not answered all required fields',
      badTime: 'You have not given a correct time',
      badEmail: 'You have not given a correct e-mail address',
      badTelephone: 'You have not given a correct phone number',
      badSecurityAnswer: 'You have not given a correct answer to the security question',
      badDate: 'You have not given a correct date',
      lengthBadStart: 'The input value must be between ',
      lengthBadEnd: ' characters',
      lengthTooLongStart: 'The input value is longer than ',
      lengthTooShortStart: 'The input value is shorter than ',
      notConfirmed: 'Input values could not be confirmed',
      badDomain: 'Incorrect domain value',
      badUrl: 'The input value is not a correct URL',
      badCustomVal: 'The input value is incorrect',
      andSpaces: ' and spaces ',
      badInt: 'The input value was not a correct number',
      badSecurityNumber: 'Your social security number was incorrect',
      badUKVatAnswer: 'Incorrect UK VAT Number',
      badUKNin: 'Incorrect UK NIN',
      badUKUtr: 'Incorrect UK UTR Number',
      badStrength: 'The password isn\'t strong enough',
      badNumberOfSelectedOptionsStart: 'You have to choose at least ',
      badNumberOfSelectedOptionsEnd: ' answers',
      badAlphaNumeric: 'The input value can only contain alphanumeric characters ',
      badAlphaNumericExtra: ' and ',
      wrongFileSize: 'The file you are trying to upload is too large (max %s)',
      wrongFileType: 'Only files of type %s is allowed',
      groupCheckedRangeStart: 'Please choose between ',
      groupCheckedTooFewStart: 'Please choose at least ',
      groupCheckedTooManyStart: 'Please choose a maximum of ',
      groupCheckedEnd: ' item(s)',
      badCreditCard: 'The credit card number is not correct',
      badCVV: 'The CVV number was not correct',
      wrongFileDim : 'Incorrect image dimensions,',
      imageTooTall : 'the image can not be taller than',
      imageTooWide : 'the image can not be wider than',
      imageTooSmall : 'the image was too small',
      min : 'min',
      max : 'max',
      imageRatioNotAccepted : 'Image ratio is not be accepted',
      badBrazilTelephoneAnswer: 'The phone number entered is invalid',
      badBrazilCEPAnswer: 'The CEP entered is invalid',
      badBrazilCPFAnswer: 'The CPF entered is invalid',
      badPlPesel: 'The PESEL entered is invalid',
      badPlNip: 'The NIP entered is invalid',
      badPlRegon: 'The REGON entered is invalid',
      badreCaptcha: 'Please confirm that you are not a bot'
    }
  });

})(jQuery, window);

/**
 * File declaring all default validators.
 */
(function($) {

  /*
   * Validate email
   */
  $.formUtils.addValidator({
    name: 'email',
    validatorFunction: function (email) {

      var emailParts = email.toLowerCase().split('@'),
        localPart = emailParts[0],
        domain = emailParts[1];

      if (localPart && domain) {

        if( localPart.indexOf('"') === 0 ) {
          var len = localPart.length;
          localPart = localPart.replace(/\"/g, '');
          if( localPart.length !== (len-2) ) {
            return false; // It was not allowed to have more than two apostrophes
          }
        }

        return $.formUtils.validators.validate_domain.validatorFunction(emailParts[1]) &&
          localPart.indexOf('.') !== 0 &&
          localPart.substring(localPart.length-1, localPart.length) !== '.' &&
          localPart.indexOf('..') === -1 &&
          !(/[^\w\+\.\-\#\-\_\~\!\$\&\'\(\)\*\+\,\;\=\:]/.test(localPart));
      }

      return false;
    },
    errorMessage: '',
    errorMessageKey: 'badEmail'
  });

  /*
   * Validate domain name
   */
  $.formUtils.addValidator({
    name: 'domain',
    validatorFunction: function (val) {
      return val.length > 0 &&
        val.length <= 253 && // Including sub domains
        !(/[^a-zA-Z0-9]/.test(val.slice(-2))) && !(/[^a-zA-Z0-9]/.test(val.substr(0, 1))) && !(/[^a-zA-Z0-9\.\-]/.test(val)) &&
        val.split('..').length === 1 &&
        val.split('.').length > 1;
    },
    errorMessage: '',
    errorMessageKey: 'badDomain'
  });

  /*
   * Validate required
   */
  $.formUtils.addValidator({
    name: 'required',
    validatorFunction: function (val, $el, config, language, $form) {
      switch ($el.attr('type')) {
        case 'checkbox':
          return $el.is(':checked');
        case 'radio':
          return $form.find('input[name="' + $el.attr('name') + '"]').filter(':checked').length > 0;
        default:
          return $.trim(val) !== '';
      }
    },
    errorMessage: '',
    errorMessageKey: function(config) {
      if (config.errorMessagePosition === 'top' || typeof config.errorMessagePosition === 'function') {
        return 'requiredFields';
      }
      else {
        return 'requiredField';
      }
    }
  });

  /*
   * Validate length range
   */
  $.formUtils.addValidator({
    name: 'length',
    validatorFunction: function (val, $el, conf, lang) {
      var lengthAllowed = $el.valAttr('length'),
        type = $el.attr('type');

      if (lengthAllowed === undefined) {
        alert('Please add attribute "data-validation-length" to ' + $el[0].nodeName + ' named ' + $el.attr('name'));
        return true;
      }

      // check if length is above min, below max or within range.
      var len = type === 'file' && $el.get(0).files !== undefined ? $el.get(0).files.length : val.length,
        lengthCheckResults = $.formUtils.numericRangeCheck(len, lengthAllowed),
        checkResult;

      switch (lengthCheckResults[0]) {   // outside of allowed range
        case 'out':
          this.errorMessage = lang.lengthBadStart + lengthAllowed + lang.lengthBadEnd;
          checkResult = false;
          break;
        // too short
        case 'min':
          this.errorMessage = lang.lengthTooShortStart + lengthCheckResults[1] + lang.lengthBadEnd;
          checkResult = false;
          break;
        // too long
        case 'max':
          this.errorMessage = lang.lengthTooLongStart + lengthCheckResults[1] + lang.lengthBadEnd;
          checkResult = false;
          break;
        // ok
        default:
          checkResult = true;
      }

      return checkResult;
    },
    errorMessage: '',
    errorMessageKey: ''
  });

  /*
   * Validate url
   */
  $.formUtils.addValidator({
    name: 'url',
    validatorFunction: function (url) {
      // written by Scott Gonzalez: http://projects.scottsplayground.com/iri/
      // - Victor Jonsson added support for arrays in the url ?arg[]=sdfsdf
      // - General improvements made by Stéphane Moureau <https://github.com/TraderStf>

      var urlFilter = /^(https?|ftp):\/\/((((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])(\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])(\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/(((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|\[|\]|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#(((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;
      if (urlFilter.test(url)) {
        var domain = url.split('://')[1],
          domainSlashPos = domain.indexOf('/');

        if (domainSlashPos > -1) {
          domain = domain.substr(0, domainSlashPos);
        }

        return $.formUtils.validators.validate_domain.validatorFunction(domain); // todo: add support for IP-addresses
      }
      return false;
    },
    errorMessage: '',
    errorMessageKey: 'badUrl'
  });

  /*
   * Validate number (floating or integer)
   */
  $.formUtils.addValidator({
    name: 'number',
    validatorFunction: function (val, $el, conf) {
      if (val !== '') {
        var allowing = $el.valAttr('allowing') || '',
          decimalSeparator = $el.valAttr('decimal-separator') || conf.decimalSeparator,
          allowsRange = false,
          begin, end,
          steps = $el.valAttr('step') || '',
          allowsSteps = false,
          sanitize = $el.attr('data-sanitize') || '',
          isFormattedWithNumeral = sanitize.match(/(^|[\s])numberFormat([\s]|$)/i);

        if (isFormattedWithNumeral) {
          if (!window.numeral) {
            throw new ReferenceError('The data-sanitize value numberFormat cannot be used without the numeral' +
              ' library. Please see Data Validation in http://www.formvalidator.net for more information.');
          }
          //Unformat input first, then convert back to String
          if (val.length) {
            val = String(numeral().unformat(val));
          }
        }

        if (allowing.indexOf('number') === -1) {
          allowing += ',number';
        }

        if (allowing.indexOf('negative') === -1 && val.indexOf('-') === 0) {
          return false;
        }

        if (allowing.indexOf('range') > -1) {
          begin = parseFloat(allowing.substring(allowing.indexOf('[') + 1, allowing.indexOf(';')));
          end = parseFloat(allowing.substring(allowing.indexOf(';') + 1, allowing.indexOf(']')));
          allowsRange = true;
        }

        if (steps !== '') {
          allowsSteps = true;
        }

        if (decimalSeparator === ',') {
          if (val.indexOf('.') > -1) {
            return false;
          }
          // Fix for checking range with floats using ,
          val = val.replace(',', '.');
        }
        if (val.replace(/[0-9-]/g, '') === '' && (!allowsRange || (val >= begin && val <= end)) && (!allowsSteps || (val % steps === 0))) {
          return true;
        }

        if (allowing.indexOf('float') > -1 && val.match(new RegExp('^([0-9-]+)\\.([0-9]+)$')) !== null && (!allowsRange || (val >= begin && val <= end)) && (!allowsSteps || (val % steps === 0))) {
          return true;
        }
      }
      return false;
    },
    errorMessage: '',
    errorMessageKey: 'badInt'
  });

  /*
   * Validate alpha numeric
   */
  $.formUtils.addValidator({
    name: 'alphanumeric',
    validatorFunction: function (val, $el, conf, language) {
      var patternStart = '^([a-zA-Z0-9',
        patternEnd = ']+)$',
        additionalChars = $el.valAttr('allowing'),
        pattern = '';

      if (additionalChars) {
        pattern = patternStart + additionalChars + patternEnd;
        var extra = additionalChars.replace(/\\/g, '');
        if (extra.indexOf(' ') > -1) {
          extra = extra.replace(' ', '');
          extra += language.andSpaces || $.formUtils.LANG.andSpaces;
        }
        this.errorMessage = language.badAlphaNumeric + language.badAlphaNumericExtra + extra;
      } else {
        pattern = patternStart + patternEnd;
        this.errorMessage = language.badAlphaNumeric;
      }

      return new RegExp(pattern).test(val);
    },
    errorMessage: '',
    errorMessageKey: ''
  });

  /*
   * Validate against regexp
   */
  $.formUtils.addValidator({
    name: 'custom',
    validatorFunction: function (val, $el) {
      var regexp = new RegExp($el.valAttr('regexp'));
      return regexp.test(val);
    },
    errorMessage: '',
    errorMessageKey: 'badCustomVal'
  });

  /*
   * Validate date
   */
  $.formUtils.addValidator({
    name: 'date',
    validatorFunction: function (date, $el, conf) {
      var dateFormat = $el.valAttr('format') || conf.dateFormat || 'yyyy-mm-dd',
        addMissingLeadingZeros = $el.valAttr('require-leading-zero') === 'false';
      return $.formUtils.parseDate(date, dateFormat, addMissingLeadingZeros) !== false;
    },
    errorMessage: '',
    errorMessageKey: 'badDate'
  });


  /*
   * Validate group of checkboxes, validate qty required is checked
   * written by Steve Wasiura : http://stevewasiura.waztech.com
   * element attrs
   *    data-validation="checkbox_group"
   *    data-validation-qty="1-2"  // min 1 max 2
   *    data-validation-error-msg="chose min 1, max of 2 checkboxes"
   */
  $.formUtils.addValidator({
    name: 'checkbox_group',
    validatorFunction: function (val, $el, conf, lang, $form) {
      // preset return var
      var isValid = true,
      // get name of element. since it is a checkbox group, all checkboxes will have same name
        elname = $el.attr('name'),
      // get checkboxes and count the checked ones
        $checkBoxes = $('input[type=checkbox][name^="' + elname + '"]', $form),
        checkedCount = $checkBoxes.filter(':checked').length,
      // get el attr that specs qty required / allowed
        qtyAllowed = $el.valAttr('qty');

      if (qtyAllowed === undefined) {
        var elementType = $el.get(0).nodeName;
        alert('Attribute "data-validation-qty" is missing from ' + elementType + ' named ' + $el.attr('name'));
      }

      // call Utility function to check if count is above min, below max, within range etc.
      var qtyCheckResults = $.formUtils.numericRangeCheck(checkedCount, qtyAllowed);

      // results will be array, [0]=result str, [1]=qty int
      switch (qtyCheckResults[0]) {
        // outside allowed range
        case 'out':
          this.errorMessage = lang.groupCheckedRangeStart + qtyAllowed + lang.groupCheckedEnd;
          isValid = false;
          break;
        // below min qty
        case 'min':
          this.errorMessage = lang.groupCheckedTooFewStart + qtyCheckResults[1] + lang.groupCheckedEnd;
          isValid = false;
          break;
        // above max qty
        case 'max':
          this.errorMessage = lang.groupCheckedTooManyStart + qtyCheckResults[1] + lang.groupCheckedEnd;
          isValid = false;
          break;
        // ok
        default:
          isValid = true;
      }

      if( !isValid ) {
        var _triggerOnBlur = function() {
          $checkBoxes.unbind('click', _triggerOnBlur);
          $checkBoxes.filter('*[data-validation]').validateInputOnBlur(lang, conf, false, 'blur');
        };
        $checkBoxes.bind('click', _triggerOnBlur);
      }

      return isValid;
    }
    //   errorMessage : '', // set above in switch statement
    //   errorMessageKey: '' // not used
  });

})(jQuery);


}));
