define(['jquery', 'lodash', 'taoTests/runner/plugin', 'taoQtiTest/runner/plugins/controls/timer/strategy/strategyHandler', 'taoQtiTest/runner/plugins/controls/timer/component/timerbox', 'taoQtiTest/runner/plugins/controls/timer/timers', 'taoQtiTest/runner/helpers/isReviewPanelEnabled', 'taoQtiTest/runner/helpers/stats', 'handlebars', 'lib/handlebars/helpers'], function ($$1, _, pluginFactory, getStrategyHandler, timerboxFactory, timersFactory, isReviewPanelEnabled, statsHelper, Handlebars, Helpers0) { 'use strict';

    $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    pluginFactory = pluginFactory && Object.prototype.hasOwnProperty.call(pluginFactory, 'default') ? pluginFactory['default'] : pluginFactory;
    getStrategyHandler = getStrategyHandler && Object.prototype.hasOwnProperty.call(getStrategyHandler, 'default') ? getStrategyHandler['default'] : getStrategyHandler;
    timerboxFactory = timerboxFactory && Object.prototype.hasOwnProperty.call(timerboxFactory, 'default') ? timerboxFactory['default'] : timerboxFactory;
    timersFactory = timersFactory && Object.prototype.hasOwnProperty.call(timersFactory, 'default') ? timersFactory['default'] : timersFactory;
    isReviewPanelEnabled = isReviewPanelEnabled && Object.prototype.hasOwnProperty.call(isReviewPanelEnabled, 'default') ? isReviewPanelEnabled['default'] : isReviewPanelEnabled;
    statsHelper = statsHelper && Object.prototype.hasOwnProperty.call(statsHelper, 'default') ? statsHelper['default'] : statsHelper;
    Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
    Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;

    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);  


      return "<div aria-live=\"polite\" class=\"visible-hidden\"></div>\n";
      });
    function screenreaderNotificationTpl(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) 2018-2019 (original work) Open Assessment Technologies SA ;
     */

    // timeout after which screenreader notifcation should be cleaned up
    const screenreaderNotificationTimeout = 20000;

    /**
     * Creates the plugin
     */
    var plugin = pluginFactory({
      name: 'timer',
      /**
       * Install step, add behavior before the lifecycle
       */
      install() {
        const testRunner = this.getTestRunner();

        /**
         * Load the timers, from the given timeConstraints and reading the current value in the store
         * @param {store} timeStore - where the values are read
         * @param {Object} config - the current config, especially for the warnings
         * @returns {Promise<Object[]>} the list of timers for the current context
         */
        this.loadTimers = function loadTimers(timeStore, config) {
          const testContext = testRunner.getTestContext();
          const testPart = testRunner.getCurrentPart();
          const isLinear = testPart && testPart.isLinear;
          const timeConstraints = testContext.timeConstraints;
          const timers = timersFactory(timeConstraints, isLinear, config);
          return Promise.all(_.map(timers, function (timer) {
            return timeStore.getItem(`consumed_${timer.id}`).then(function (savedConsumedTime) {
              if (_.isNumber(savedConsumedTime) && savedConsumedTime >= 0 && config.restoreTimerFromClient) {
                timer.remainingTime = timer.originalTime + timer.extraTime.total - savedConsumedTime;
              }
            });
          })).then(function () {
            return timers;
          });
        };

        /**
         * Save consumed time values into the store
         * @param {store} timeStore - where the values are saved
         * @param {Object[]} timers - the timers to save
         * @returns {Promise} resolves once saved
         */
        this.saveTimers = function saveTimers(timeStore, timers) {
          return Promise.all(_.map(timers, function (timer) {
            return timeStore.setItem(`consumed_${timer.id}`, timer.originalTime + timer.extraTime.total - timer.remainingTime);
          }));
        };

        //define the "timer" store as "volatile" (removed on browser change).
        testRunner.getTestStore().setVolatile(this.getName());
      },
      /**
       * Initializes the plugin (called during runner's init)
       *
       * @returns {Promise}
       */
      init: function init() {
        const self = this;
        const testRunner = this.getTestRunner();
        const testRunnerOptions = testRunner.getOptions();
        let screenreaderNotifcationTimeoutId;
        const stats = {};
        ['test', 'testPart', 'section', 'item'].forEach(scope => Object.assign(stats, {
          [scope]: statsHelper.getInstantStats(scope, testRunner)
        }));

        /**
         * Plugin config,
         */
        const config = Object.assign({
          /**
           * An option to control is the warnings are contextual or global
           */
          contextualWarnings: false,
          /**
           * The list of configured warnings
           */
          warnings: testRunnerOptions.timerWarning || {},
          /**
           * The list of configured warnings for screenreaders
           */
          warningsForScreenreader: testRunnerOptions.timerWarningForScreenreader || {},
          /**
           * The guided navigation option
           */
          guidedNavigation: testRunnerOptions.guidedNavigation,
          /**
           * Restore timer from client.
           */
          restoreTimerFromClient: testRunnerOptions.timer && testRunnerOptions.timer.restoreTimerFromClient,
          /**
           * Questions stats
           */
          questionsStats: stats
        }, this.getConfig());

        /**
         * Set up the strategy handler
         */
        const strategyHandler = getStrategyHandler(testRunner);

        /**
         * dispatch errors to the test runner
         * @param {Error} err - to dispatch
         */
        const handleError = err => {
          testRunner.trigger('error', err);
        };
        function loadSavedTimers(timeStore) {
          const testContext = testRunner.getTestContext();
          //update the timers before each item
          if (self.timerbox && testContext.timeConstraints) {
            return self.loadTimers(timeStore, config).then(function (timers) {
              return self.timerbox.update(timers);
            }).catch(handleError);
          }
        }
        return new Promise(function (resolve) {
          //load the plugin store
          return testRunner.getPluginStore(self.getName()).then(function (timeStore) {
            testRunner.before('renderitem', function () {
              return loadSavedTimers(timeStore);
            }).before('enableitem', function () {
              if (config.restoreTimerFromClient) {
                return loadSavedTimers(timeStore);
              }
            }).on('tick', function (elapsed) {
              if (self.timerbox) {
                const timers = self.timerbox.getTimers();
                const updatedTimers = Object.keys(timers).reduce((acc, timerName) => {
                  const statsScope = statsHelper.getInstantStats(timers[timerName].scope, testRunner);
                  const unansweredQuestions = statsScope && statsScope.questions - statsScope.answered;
                  acc[timerName] = Object.assign({}, timers[timerName], {
                    remainingTime: timers[timerName].remainingTime - elapsed,
                    unansweredQuestions
                  });
                  return acc;
                }, {});
                self.timerbox.update(updatedTimers).catch(handleError);
              }
            }).after('renderitem', function () {
              if (self.timerbox) {
                $$1(self.timerbox.getElement()).find('.timer-wrapper').attr('aria-hidden', isReviewPanelEnabled(testRunner));
                self.timerbox.start();
              }
              self.$screenreaderWarningContainer.text('');
            }).after('enableitem', function () {
              if (self.timerbox && config.restoreTimerFromClient) {
                //this will "resume" the countdowns if timers have client mode
                self.timerbox.start();
              }
            }).on('move skip', function () {
              if (self.timerbox) {
                //this will "pause" the countdowns
                self.timerbox.stop();
              }
            }).on('disableitem', function () {
              if (self.timerbox && config.restoreTimerFromClient) {
                //this will "pause" the countdowns if timers have client mode
                self.timerbox.stop();
              }
            });
            timeStore.getItem('zen-mode').then(function (startZen) {
              //set up the timerbox
              self.timerbox = timerboxFactory({
                ariaHidden: isReviewPanelEnabled(testRunner),
                zenMode: {
                  enabled: true,
                  startHidden: !!startZen
                },
                displayWarning: config.contextualWarnings
              }).on('change', _.throttle(function () {
                //update the store with the current timer values
                self.saveTimers(timeStore, this.getTimers());
              }, 1000)).on('timeradd', function (timer) {
                strategyHandler.setUp(timer).catch(handleError);
              }).on('timerremove', function (timer) {
                strategyHandler.tearDown(timer).catch(handleError);
              }).on('timerstart', function (timer) {
                strategyHandler.start(timer).catch(handleError);
              }).on('timerstop', function (timer) {
                strategyHandler.stop(timer).catch(handleError);
              }).on('timerend', function (timer) {
                strategyHandler.complete(timer).catch(handleError);
              }).on('timerchange', function (action, timer) {
                //backward compatible events
                self.trigger(`${action}timer`, timer.qtiClassName, timer);
              }).on('zenchange', function (isZen) {
                timeStore.setItem('zen-mode', !!isZen);
              }).on('init', resolve).on('error', handleError);

              // share this timer values to use in other components
              self.timerbox.spread(testRunner, 'timertick');
              if (!config.contextualWarnings) {
                self.timerbox.on('warn', function (message, level) {
                  if (level && message) {
                    testRunner.trigger(level, message);
                  }
                });

                // debounce used to prevent multiple invoking at the same time
                self.timerbox.on('warnscreenreader', _.debounce((message, remainingTime, scope) => {
                  const statsScope = statsHelper.getInstantStats(scope, testRunner);
                  const unansweredQuestions = statsScope && statsScope.questions - statsScope.answered;
                  if (screenreaderNotifcationTimeoutId) {
                    clearTimeout(screenreaderNotifcationTimeoutId);
                  }
                  self.$screenreaderWarningContainer.text(message(remainingTime, unansweredQuestions));
                  screenreaderNotifcationTimeoutId = setTimeout(() => self.$screenreaderWarningContainer.text(''), screenreaderNotificationTimeout);
                }, 1000, {
                  'leading': true,
                  'trailing': false
                }));
              }
            }).catch(handleError);
          });
        });
      },
      /**
       * Called during the runner's render phase
       */
      render: function render() {
        const $container = this.getAreaBroker().getControlArea();
        this.$screenreaderWarningContainer = $$1(screenreaderNotificationTpl());
        this.timerbox.render($container);
        $container.append(this.$screenreaderWarningContainer);
      },
      /**
       * Called during the runner's destroy phase
       */
      destroy: function destroy() {
        if (this.timerbox) {
          this.timerbox.stop().destroy();
        }
      },
      /**
       * Shows the timers
       */
      show: function show() {
        if (this.timerbox) {
          this.timerbox.show();
        }
      },
      /**
       * Hides the timers
       */
      hide: function hide() {
        if (this.timerbox) {
          this.timerbox.hide();
        }
      }
    });

    return plugin;

});
