define(['jquery', 'lodash', 'util/typeCaster', 'taoTests/runner/plugin', 'taoQtiTest/runner/helpers/verticalWriting', 'css!taoQtiTest/runner/plugins/content/itemScrolling/css/scrolling.css'], function ($, _, typeCaster, pluginFactory, verticalWriting, scrolling_css) { 'use strict';

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

    /**
     * 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) 2020 (original work) Open Assessment Technologies SA;
     */
    const minimalAcceptableSizePx = 20;

    /**
     * Creates the loading bar plugin.
     * Displays a loading bar when a blocking task is running
     */
    var itemScrolling = pluginFactory({
      name: 'itemScrolling',
      /**
       * Initializes the plugin (called during runner's init)
       */
      init: function init() {
        const testRunner = this.getTestRunner();
        let $root;
        testRunner.on('renderitem', () => {
          $root = testRunner.getAreaBroker().getContainer();
          const isItemVerticalWriting = verticalWriting.getIsItemWritingModeVerticalRl();
          const $itemScrollContainer = getItemScrollContainer(isItemVerticalWriting);
          if ($itemScrollContainer.length) {
            this.itemResizeCallback = _.throttle(() => requestAnimationFrame(adaptBlockSize), 200);
            this.itemResizeObserver = new ResizeObserver(this.itemResizeCallback);
            this.itemResizeObserver.observe($itemScrollContainer.get(0));
          }
        }).on('unloaditem', () => {
          if (this.itemResizeObserver) {
            this.itemResizeObserver.disconnect();
          }
          if (this.itemResizeCallback && this.itemResizeCallback.cancel) {
            this.itemResizeCallback.cancel();
          }
        });
        function getItemScrollContainer(isItemVerticalWriting) {
          return isItemVerticalWriting ? $root.find('.qti-itemBody') : $root.find('.test-runner-sections > .content-wrapper');
        }
        function adaptBlockSize() {
          const isItemVerticalWriting = verticalWriting.getIsItemWritingModeVerticalRl();
          const $itemScrollContainer = getItemScrollContainer(isItemVerticalWriting);
          const $blockContainers = $itemScrollContainer.find('[data-scrolling="true"]');
          const innerItemSize = getItemRunnerBlockSize($itemScrollContainer, isItemVerticalWriting) - getQtiItemAndItemBodyPadding($itemScrollContainer, isItemVerticalWriting) - 2;
          const contentBlockSize = innerItemSize - getGridRowBlockMargin() - getExtraGridRowBlockSize(isItemVerticalWriting) - getSpaceAroundQtiContent($itemScrollContainer, isItemVerticalWriting);
          $blockContainers.each(function () {
            const $block = $(this);
            const isBlockVerticalWriting = verticalWriting.getIsWritingModeVerticalRl($block);
            const isDifferentWritingMode = isBlockVerticalWriting !== isItemVerticalWriting;
            const isScrollable = typeCaster.strToBool($block.attr('data-scrolling') || 'false');
            const selectedBlockSize = parseFloat($block.attr('data-scrolling-height')) || 100;
            const containerParent = $block.parent().closest('[data-scrolling="true"]');
            const containerBlockSize = isItemVerticalWriting ? containerParent.width() : containerParent.height();
            const normalSizeProp = isItemVerticalWriting ? 'width' : 'height';
            const maxSizeProp = isItemVerticalWriting ? 'max-width' : 'max-height';
            const sizeProp = isDifferentWritingMode ? normalSizeProp : maxSizeProp;
            if ($block.length && isScrollable) {
              $block.data('scrollable', true);
              const cssObj = {};
              if (containerParent.length > 0) {
                cssObj[sizeProp] = `${containerBlockSize * (selectedBlockSize * 0.01)}px`;
              } else {
                const maxSize = contentBlockSize * (selectedBlockSize * 0.01);
                if (maxSize > minimalAcceptableSizePx) {
                  cssObj[sizeProp] = `${maxSize}px`;
                } else {
                  // contentBlockSize could turn out to be negative or very small because of
                  //  'getExtraGridRowBlockSize' [other grid-row's content is unexpectedly long] or 'getSpaceAroundQtiContent';
                  // then we show block with natural size (no scrollbars);
                  // but for block with different writing-mode need to define *some* size.
                  if (isBlockVerticalWriting !== isItemVerticalWriting) {
                    cssObj[sizeProp] = `${innerItemSize / 2}px`;
                  }
                }
              }
              $block.css(cssObj);
            }
          });
        }
        function getItemRunnerBlockSize($itemScrollContainer, isItemVerticalWriting) {
          const rect = $itemScrollContainer.get(0).getBoundingClientRect();
          return isItemVerticalWriting ? rect.width : rect.height;
        }

        // if layout is: text (scroll-block) on top/bottom of the question (interaction), then:
        //   ensure whole item fits on the screen (fit interaction, and let scroll-block fill remaining space;
        //   if there are several scroll-blocks, split this remaining space between them based on their `data-scrolling-height )
        // notes:
        //  - should have been enabled by the special option? it may not always be a desirable behavior!
        //  - applies to `.grid-row`, but not to `.colrow`
        function getExtraGridRowBlockSize(isItemVerticalWriting) {
          var $gridRows = $root.find('.qti-itemBody > .grid-row'),
            extraBlockSize = 0;
          $gridRows.each(function () {
            var $gridRow = $(this),
              $blockContainer = $gridRow.find('[data-scrolling="true"]');
            if (!$blockContainer.length) {
              extraBlockSize += isItemVerticalWriting ? $gridRow.outerWidth(true) : $gridRow.outerHeight(true);
            }
          });
          return extraBlockSize;
        }

        // rubrick's block height
        function getSpaceAroundQtiContent($itemScrollContainer, isItemVerticalWriting) {
          if (isItemVerticalWriting) {
            return 0;
          }
          const itemScrollContainer = $itemScrollContainer.get(0);
          const $qtiContent = $root.find('#qti-content');
          if ($qtiContent.length && itemScrollContainer.contains($qtiContent.get(0))) {
            const qtiContentRect = $qtiContent.get(0).getBoundingClientRect();
            const itemRunnerContainerRect = itemScrollContainer.getBoundingClientRect();
            return qtiContentRect.top - itemRunnerContainerRect.top + itemScrollContainer.scrollTop;
          }
          return 0;
        }

        // all elems between item-scroll-container and itemBody, including itemBody
        function getQtiItemAndItemBodyPadding($itemScrollContainer, isItemVerticalWriting) {
          let padding = 0;
          const $itemBody = $root.find('.qti-itemBody');
          const propNames = isItemVerticalWriting ? ['padding-left', 'padding-right'] : ['padding-top', 'padding-bottom'];
          const parentsUntil = $itemScrollContainer.get(0).contains($itemBody.get(0)) ? $itemBody.parentsUntil($itemScrollContainer).toArray() : [];
          for (const el of [...parentsUntil, $itemBody.get(0)]) {
            if ($itemScrollContainer.get(0).contains(el)) {
              const compStyle = getComputedStyle(el);
              for (const propName of propNames) {
                padding += parseFloat(compStyle.getPropertyValue(propName) || 0);
              }
            }
          }
          return padding;
        }

        // a) dual-column: one row, 2 columns - one with full-height scrollable block, other with interaction
        // b) 'getExtraGridRowBlockSize' where text (scroll-block) on top/bottom of the question (interaction) should fit on screen
        function getGridRowBlockMargin() {
          let margin = 0;
          const $gridRows = $root.find('.qti-itemBody > .grid-row');
          $gridRows.each(function () {
            const $gridRow = $(this);
            const $blockContainer = $gridRow.find('[data-scrolling="true"]');
            if ($blockContainer.length) {
              // "getExtraGridRowBlockSize" includes height of rows *without* scroll containers;
              // here we include margins of rows *with* scroll containers
              const $col = $blockContainer.closest('[class*=" col-"], [class^="col-"]');
              const margins = [[$col, 'margin-block-start'], [$gridRow, 'margin-block-start'], [$col, 'margin-block-end'], [$gridRow, 'margin-block-end']];
              for (const [$el, prop] of margins) {
                margin += $el.length ? parseFloat(getComputedStyle($el.get(0)).getPropertyValue(prop) || 0) : 0;
              }
            }
          });
          return margin;
        }
      },
      destroy: function destroy() {
        if (this.itemResizeObserver) {
          this.itemResizeObserver.disconnect();
        }
        if (this.itemResizeCallback && this.itemResizeCallback.cancel) {
          this.itemResizeCallback.cancel();
        }
      }
    });

    return itemScrolling;

});
