define(['jquery', 'lodash', 'ui/formValidator/highlighters/highlighter', 'ui/validator'], function ($, _, highlighterFactory, validator) { 'use strict';

    $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $;
    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    highlighterFactory = highlighterFactory && Object.prototype.hasOwnProperty.call(highlighterFactory, 'default') ? highlighterFactory['default'] : highlighterFactory;

    /**
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * as published by the Free Software Foundation; under version 2
     * of the License (non-upgradable).
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
     *
     * Copyright (c) 2015 (original work) Open Assessment Technologies SA;
     *
     */
    var defaultOptions = {
      highlighter: {
        type: 'message',
        errorClass: 'error'
      },
      container: $(document),
      selector: '[data-validate]',
      validateOnInit: false,
      events: ['change', 'blur']
    };

    /**
     * @param {Object} options
     * @param {jQuery} [options.container] - container which contains elements to validate
     * @param {Object} [options.selector = '[data-validate]'] - selector to find elements to validate
     * @param {string|Array} [options.events = ['change', 'blur']] - the default event that triggers the validation
     * @param {Object} [options.highlighter - error filed highlighter options {@see ui/formValidator/highlighters/highlighter}
     * @param {Object} [options.validateOnInit = false] - whether form should be validated after plugin initialization
     */
    var formValidatorFactory = function formValidatorFactory(options) {
      var state = {
          valid: true,
          errors: []
        },
        highlighter,
        $toValidate,
        validator;
      validator = {
        init: function init() {
          var self = this;
          self.options = _.defaults(options || {}, defaultOptions);
          $toValidate = getFieldsToValidate();
          $toValidate.validator({
            event: self.options.events,
            validated: function (valid, report) {
              afterFieldValidate($(this), valid, report);
            }
          });
          if (options.validateOnInit) {
            self.validate();
          }
          return self;
        },
        /**
         * Validate form.
         * @returns {boolean} - whether form is valid
         */
        validate: function validate() {
          $toValidate = getFieldsToValidate();
          state = {
            valid: true,
            errors: []
          };
          $toValidate.validator('validate', function (valid, report) {
            afterFieldValidate($(this), valid, report);
          });
          return state.valid;
        },
        /**
         * Get form state in following format:
         * <pre>
         *  {
         *    valid : false,
         *
         *  }
         * </pre>
         * @returns {object}
         */
        getState: function getState() {
          return state;
        },
        /**
         * Destroy validator.
         */
        destroy: function destroy() {
          var $fields = getFieldsToValidate();
          $fields.each(function () {
            if (highlighter) {
              highlighter.destroy($(this));
            }
          });
        },
        /**
         * Get container which contains fields to validate
         * @returns {jQuery}
         */
        getContainer: function getContainer() {
          var self = this,
            $container;
          if (self.options.container && self.options.container.length) {
            $container = self.options.container;
          } else {
            $container = $(document);
          }
          return $container;
        }
      };

      /**
       * Callback will be called after field validation.
       * @private
       * @param {jQuery} $field - validated field
       * @param {boolean} valid - whether field is valid
       * @param {array} report - list of reports {@see ui/validator/Report}
       */
      function afterFieldValidate($field, valid, report) {
        var firstErrorData;
        state.valid = state.valid && valid;
        if (!valid) {
          firstErrorData = _.map(_.filter(report, {
            type: 'failure'
          }), 'data')[0];
          highlightField($field, false, firstErrorData.message);
          state.errors.push($.extend(true, {
            field: $field
          }, firstErrorData));
        } else {
          highlightField($field, true);
        }
      }

      /**
       * Add or remove error class and error message
       * @private
       * @param {jQuery} $field - element to be highlighted
       * @param {boolean} success - whether input is valid or not.
       * @param {string} [message]
       */
      function highlightField($field, success, message) {
        highlighter = getHighlighter();
        if (success) {
          highlighter.unhighlight($field);
        } else {
          highlighter.highlight($field, message);
        }
      }

      /**
       * Get highlighter helper
       * @private
       * @return {object} - highlighter {@see ui/formValidator/highlighters/highlighter}
       */
      function getHighlighter() {
        if (_.isUndefined(highlighter)) {
          highlighter = highlighterFactory(validator.options.highlighter);
        }
        return highlighter;
      }

      /**
       * Get fields to validate
       * @private
       * @returns {jQuery}
       */
      function getFieldsToValidate() {
        var $container;
        if (_.isUndefined($toValidate)) {
          $container = validator.getContainer();
          $toValidate = $container.find(validator.options.selector);
        }
        return $toValidate;
      }
      return validator.init();
    };

    return formValidatorFactory;

});
