define(['jquery', 'lodash', 'i18n', 'handlebars', 'lib/handlebars/helpers', 'helpers', 'ui/feedback'], function ($$1, _, __, Handlebars, Helpers0, helpers, feedback) { 'use strict';

    $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
    Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
    Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
    helpers = helpers && Object.prototype.hasOwnProperty.call(helpers, 'default') ? helpers['default'] : helpers;
    feedback = feedback && Object.prototype.hasOwnProperty.call(feedback, 'default') ? feedback['default'] : feedback;

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;


      buffer += "<div class=\"feedback-";
      if (helper = helpers.level) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.level); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">\n<span class=\"icon-lock ";
      if (helper = helpers.level) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.level); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "-color ";
      if (helper = helpers.level) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.level); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\"></span>\n    ";
      if (helper = helpers.msg) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.msg); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\n    <div class=\"txt-rgt button-container\">\n        <button class=\"btn btn-info small check-in\"><span class=\"icon-unlock info\"></span>"
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Check-in", options) : helperMissing.call(depth0, "__", "Check-in", options)))
        + "</button>\n        <button class=\"btn btn-info small release\"><span class=\"icon-result-nok info\"></span>"
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Release", options) : helperMissing.call(depth0, "__", "Release", options)))
        + "</button>\n    </div>\n\n    <div class=\"message-container button-container\" style=\"display: none;\">\n            <label>\n                "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Message", options) : helperMissing.call(depth0, "__", "Message", options)))
        + "\n            </label>\n            <input type=\"text\" name=\"message\" class=\"message\">\n            <button class=\"btn-info small commit\">"
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Commit", options) : helperMissing.call(depth0, "__", "Commit", options)))
        + "</button>\n    </div>\n</div>\n";
      return buffer;
      });
    function tpl(data, options, asString) {
      var html = Template(data, options);
      return (asString || true) ? html : $(html);
    }

    /**
     * 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;
     *
     *
     */

    //keep a reference to alive lock
    var currents = [];

    //contains the reference to the main lock box. We expect other containers to be only edge cases.
    var $lockBox;

    //lock levels are divided into 2 categories
    var categories = {
      hasLock: 'info',
      locked: 'error'
    };

    //lock's states
    var states = {
      created: 'created',
      displayed: 'displayed',
      closed: 'closed'
    };

    //the default options
    var defaultOptions = {
      msg: __('This resource is locked'),
      releaseUrl: helpers._url('release', 'Lock', 'tao'),
      commitUrl: helpers._url('commitResource', 'History', 'taoRevision')
    };

    /**
     * Object delegation. This enables us to separate the instance from Api.
     * An instance can call methods from the API like it was it, so each object will not contain the function definition.
     * @private
     * @param {Object} receiver - the object that receive the methods
     * @param {Object} provider - it provides the methods to the receiver
     * @returns {Object} the receiver augmented by the provider's methods.
     */
    function delegate(receiver, provider) {
      _(provider).functions().forEach(function delegateMethod(methodName) {
        receiver[methodName] = function applyDelegated() {
          return provider[methodName].apply(receiver, arguments);
        };
      });
      return receiver;
    }

    /**
     * It provides the lock behavior
     * @typedef lockApi
     *
     *
     * @param {Object} [options] - the plugin options
     * @param {String} [options.uri =  ''] - The uri of the selected resource
     * @param {String} [options.releaseUrl =  ''] - The url to call to release the lock
     * @param {String} [options.commitUrl =  ''] - The url to call to commit the resource
     */
    const lockApi = {
      level: null,
      category: null,
      /**
       * generate the lock with the right options
       *
       * @example lock().message();
       * @param {String} [category] - the category of the lock (hasLock or locked)
       * @param {String} [msg] - the message to display
       * @param {Object} [options] - the plugin options
       * @fires create.lock
       * @returns {lockApi}
       */
      message: function message(category, msg, options) {
        if (!category || !_.includes(_.keys(categories), category)) {
          category = 'hasLock';
        }
        this.setState(states.created);
        this.category = category;
        this.level = _.result(categories, this.category);
        this.options = _.defaults(options || {}, defaultOptions);
        this.content = tpl({
          level: this.level,
          msg: msg
        });
        this._trigger('create');
        return this;
      },
      /**
       * generate the lock with the right options and open it
       *
       * @example lock().hasLock();
       * @param {String} [msg] - the message to display
       * @param {Object} [options] - the plugin options
       * @returns {lockApi}
       */
      hasLock: function hasLock(msg, options) {
        return this.message('hasLock', msg, options).open();
      },
      /**
       * generate the lock with the right options and open it
       *
       * @example lock().locked();
       * @param {String} [msg] - the message to display
       * @param {Object} [options] - the plugin options
       * @returns {lockApi}
       */
      locked: function locked(msg, options) {
        return this.message('locked', msg, options).open();
      },
      /**
       * open the lock
       * @example lock().message().open();
       * @fires open.lock
       * @returns {lockApi}
       */
      open: function open() {
        this._trigger('open');

        // display me
        this.display();
        return this;
      },
      /**
       * close the lock
       * @example lock().close();
       * @fires close.lock
       */
      close: function close() {
        if (this.isInState(states.displayed)) {
          this.setState(states.closed);
          $$1('#' + this.id).remove();
          this._trigger('close');

          //clean up ref
          _.remove(currents, {
            _state: states.closed
          });
        }
      },
      /**
       * display the lock
       * @example lock().display();
       * @fires display.lock
       * @returns {lockApi}
       */
      display: function display() {
        var self = this;
        if (self.content) {
          self.setState(states.displayed);
          $$1(self.content).attr('id', self.id).appendTo(self._container);
          self._trigger('display');
          if (typeof this.options.uri === 'undefined') {
            $$1('.release', self._container).hide();
            $$1('.check-in', self._container).hide();
          } else {
            $$1('.release', self._container).on('click', function () {
              self.release();
            });
            $$1('.check-in', self._container).on('click', function () {
              self.commit();
            });
          }
        }
        return self;
      },
      /**
       * call the url to release the lock
       * @example lock().release();
       * @fires released.lock
       * @fires failed.lock
       * @returns {lockApi}
       */
      release: function release() {
        var self = this;
        if (self.options.releaseUrl !== '') {
          $$1.ajax({
            url: self.options.releaseUrl,
            type: 'POST',
            data: {
              uri: self.options.uri
            },
            dataType: 'json',
            success: function (response) {
              if (response.success) {
                self._trigger('released', response);
              } else {
                self._trigger('failed', response);
              }
            },
            error: function () {
              self._trigger('failed');
            }
          });
        } else {
          self._trigger('failed');
        }
        return this;
      },
      /**
       * ask a message and call the url to commit the resource
       * @example lock().commit();
       * @fires commit.lock
       * @returns {lockApi}
       */
      commit: function commit() {
        var self = this;
        if (self.options.commitUrl !== '') {
          $$1('.message-container', self._container).slideToggle();
          $$1('.commit', self._container).off('click').on('click', function () {
            var message = $$1('.message', self._container).val();
            if (message !== '') {
              $$1.ajax({
                url: self.options.commitUrl,
                type: 'POST',
                data: {
                  id: self.options.uri,
                  message: message
                },
                dataType: 'json',
                success: function (response) {
                  if (response.success) {
                    self._trigger('committed', response);
                  } else {
                    self._trigger('failed', response);
                  }
                },
                error: function () {
                  self._trigger('failed');
                }
              });
            } else {
              self._trigger('failed', {
                message: __('Please give a message to your commit')
              });
            }
          });
        } else {
          self._trigger('failed');
        }
        return this;
      },
      /**
       * Default behaviour
       */
      register: function () {
        var msg = this._container.data('msg') || defaultOptions.msg;
        var id = this._container.data('id');
        return this.message('hasLock', msg, {
          uri: id,
          released: function (response) {
            feedback().success(response.message);
            this.close();
          },
          committed: function (response) {
            feedback().success(response.commitMessage);
            this.close();
          },
          failed: function (response) {
            if (typeof response !== 'undefined' && typeof response.message !== 'undefined') {
              feedback().error(response.message);
            } else {
              feedback().error('Unknown Error');
            }
          }
        }).open();
      },
      /**
       * trigger the event and the callback if exists
       * @param {String} [eventName] - the name of the event, use the caller name if not set
       */
      _trigger: function _trigger(eventName, data) {
        //trigger the related event
        this._container.trigger(eventName + '.lock', [this]);

        //run the callback if set in options
        if (_.isFunction(this.options[eventName])) {
          this.options[eventName].call(this, data);
        }
      }
    };

    /**
     * Contains the current state of the lock and accessors
     * @typedef lockState
     */
    const lockState = {
      //the current state
      _state: null,
      /**
       * Check if the current state is one of the given values
       * @param {String|Array} verify - the statue to check
       * @returns {Boolean} true if the object is in the state to verify
       */
      isInState: function isInState(verify) {
        if (_.isString(verify)) {
          verify = [verify];
        }
        return _.includes(verify, this._state);
      },
      /**
       * Change the current state
       * @param {String} state - the new state
       * @throws {Error} if we try to set an invalid state
       */
      setState: function setState(state) {
        if (!_.includes(states, state)) {
          throw new Error('Unkown state ' + state);
        }
        this._state = state;
      }
    };

    /**
     * Enables you to create a new lock.
     * example lock().error("content");
     * @exports ui/lock
     * @param {jQUeryElement} [$container] - only to specify another container
     * @returns {Object} the lock object
     * @throws {Error} if the container isn't found
     */
    const lockFactory = function lockFactory($container) {
      var _container;
      if (!$container) {
        $lockBox = $$1('#lock-box');
      }
      _container = $container || $lockBox;
      if (!_container || !_container.length) {
        throw new Error('The lock needs to belong to an existing container');
      }

      //if there is already a lock component in this container close it and open a new one
      _.forEach(currents, function (lockRef) {
        if (lockRef !== null && lockRef._container.get(0) === _container.get(0)) {
          lockRef.close();
        }
      });
      //mixin the new object with the state object
      const lk = _.extend({
        id: 'lock-' + (currents.length + 1),
        _container: _container
      }, lockState);
      currents.push(lk);

      //delegate the api calls to the new instance
      return delegate(lk, lockApi);
    };

    return lockFactory;

});
