function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }

function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }

function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }

function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

import { extend } from '../../../helpers/object';
import { CellCoords, CellRange } from './../../../3rdparty/walkontable/src';
import { arrayEach } from './../../../helpers/array';
/**
 * Class responsible for all of the Autofill-related operations on merged cells.
 *
 * @class AutofillCalculations
 * @plugin MergeCells
 * @util
 */

var AutofillCalculations =
/*#__PURE__*/
function () {
  function AutofillCalculations(plugin) {
    _classCallCheck(this, AutofillCalculations);

    /**
     * Reference to the Merge Cells plugin.
     *
     * @type {MergeCells}
     */
    this.plugin = plugin;
    /**
     * Reference to the MergedCellsCollection class instance.
     *
     * @type {MergedCellsCollection}
     */

    this.mergedCellsCollection = this.plugin.mergedCellsCollection;
    /**
     * Cache of the currently processed autofill data.
     *
     * @private
     * @type {Object}
     */

    this.currentFillData = null;
  }
  /**
   * Correct the provided selection area, so it's not selecting only a part of a merged cell.
   *
   * @param {Array} selectionArea
   */


  _createClass(AutofillCalculations, [{
    key: "correctSelectionAreaSize",
    value: function correctSelectionAreaSize(selectionArea) {
      if (selectionArea[0] === selectionArea[2] && selectionArea[1] === selectionArea[3]) {
        var mergedCell = this.mergedCellsCollection.get(selectionArea[0], selectionArea[1]);

        if (mergedCell) {
          selectionArea[2] = selectionArea[0] + mergedCell.rowspan - 1;
          selectionArea[3] = selectionArea[1] + mergedCell.colspan - 1;
        }
      }
    }
    /**
     * Get the direction of the autofill process.
     *
     * @param {Array} selectionArea The selection area.
     * @param {Array} finalArea The final area (base + drag).
     * @return {String} `up`, `down`, `left` or `right`.
     */

  }, {
    key: "getDirection",
    value: function getDirection(selectionArea, finalArea) {
      var direction = null;

      if (finalArea[0] === selectionArea[0] && finalArea[1] === selectionArea[1] && finalArea[3] === selectionArea[3]) {
        direction = 'down';
      } else if (finalArea[2] === selectionArea[2] && finalArea[1] === selectionArea[1] && finalArea[3] === selectionArea[3]) {
        direction = 'up';
      } else if (finalArea[1] === selectionArea[1] && finalArea[2] === selectionArea[2]) {
        direction = 'right';
      } else {
        direction = 'left';
      }

      return direction;
    }
    /**
     * Snap the drag area to the farthest merged cell, so it won't clip any of the merged cells.
     *
     * @param {Array} baseArea The base selected area.
     * @param {Array} dragArea The drag area.
     * @param {String} dragDirection The autofill drag direction.
     * @param {Array} foundMergedCells MergeCellCoords found in the base selection area.
     * @return {Array} The new drag area
     */

  }, {
    key: "snapDragArea",
    value: function snapDragArea(baseArea, dragArea, dragDirection, foundMergedCells) {
      var newDragArea = dragArea.slice(0);
      var fillSize = this.getAutofillSize(baseArea, dragArea, dragDirection);

      var _baseArea = _slicedToArray(baseArea, 4),
          baseAreaStartRow = _baseArea[0],
          baseAreaStartColumn = _baseArea[1],
          baseAreaEndRow = _baseArea[2],
          baseAreaEndColumn = _baseArea[3];

      var verticalDirection = ['up', 'down'].indexOf(dragDirection) > -1;
      var fullCycle = verticalDirection ? baseAreaEndRow - baseAreaStartRow + 1 : baseAreaEndColumn - baseAreaStartColumn + 1;
      var fulls = Math.floor(fillSize / fullCycle) * fullCycle;
      var partials = fillSize - fulls;
      var farthestCollection = this.getFarthestCollection(baseArea, dragArea, dragDirection, foundMergedCells);

      if (farthestCollection) {
        if (dragDirection === 'down') {
          var fill = farthestCollection.row + farthestCollection.rowspan - baseAreaStartRow - partials;
          var newLimit = newDragArea[2] + fill;

          if (newLimit >= this.plugin.hot.countRows()) {
            newDragArea[2] -= partials;
          } else {
            newDragArea[2] += partials ? fill : 0;
          }
        } else if (dragDirection === 'right') {
          var _fill = farthestCollection.col + farthestCollection.colspan - baseAreaStartColumn - partials;

          var _newLimit = newDragArea[3] + _fill;

          if (_newLimit >= this.plugin.hot.countCols()) {
            newDragArea[3] -= partials;
          } else {
            newDragArea[3] += partials ? _fill : 0;
          }
        } else if (dragDirection === 'up') {
          var _fill2 = baseAreaEndRow - partials - farthestCollection.row + 1;

          var _newLimit2 = newDragArea[0] + _fill2;

          if (_newLimit2 < 0) {
            newDragArea[0] += partials;
          } else {
            newDragArea[0] -= partials ? _fill2 : 0;
          }
        } else if (dragDirection === 'left') {
          var _fill3 = baseAreaEndColumn - partials - farthestCollection.col + 1;

          var _newLimit3 = newDragArea[1] + _fill3;

          if (_newLimit3 < 0) {
            newDragArea[1] += partials;
          } else {
            newDragArea[1] -= partials ? _fill3 : 0;
          }
        }
      }

      this.updateCurrentFillCache({
        baseArea: baseArea,
        dragDirection: dragDirection,
        foundMergedCells: foundMergedCells,
        fillSize: fillSize,
        dragArea: newDragArea,
        cycleLength: fullCycle
      });
      return newDragArea;
    }
    /**
     * Update the current fill cache with the provided object.
     *
     * @private
     * @param {Object} updateObject
     */

  }, {
    key: "updateCurrentFillCache",
    value: function updateCurrentFillCache(updateObject) {
      if (!this.currentFillData) {
        this.currentFillData = {};
      }

      extend(this.currentFillData, updateObject);
    }
    /**
     * Get the "length" of the drag area.
     *
     * @private
     * @param {Array} baseArea The base selection area.
     * @param {Array} dragArea The drag area (containing the base area).
     * @param {String} direction The drag direction.
     * @return {Number|null} The "length" (height or width, depending on the direction) of the drag.
     */

  }, {
    key: "getAutofillSize",
    value: function getAutofillSize(baseArea, dragArea, direction) {
      var _baseArea2 = _slicedToArray(baseArea, 4),
          baseAreaStartRow = _baseArea2[0],
          baseAreaStartColumn = _baseArea2[1],
          baseAreaEndRow = _baseArea2[2],
          baseAreaEndColumn = _baseArea2[3];

      var _dragArea = _slicedToArray(dragArea, 4),
          dragAreaStartRow = _dragArea[0],
          dragAreaStartColumn = _dragArea[1],
          dragAreaEndRow = _dragArea[2],
          dragAreaEndColumn = _dragArea[3];

      switch (direction) {
        case 'up':
          return baseAreaStartRow - dragAreaStartRow;

        case 'down':
          return dragAreaEndRow - baseAreaEndRow;

        case 'left':
          return baseAreaStartColumn - dragAreaStartColumn;

        case 'right':
          return dragAreaEndColumn - baseAreaEndColumn;

        default:
          return null;
      }
    }
    /**
     * Trim the default drag area (containing the selection area) to the drag-only area.
     *
     * @private
     * @param {Array} baseArea The base selection area.
     * @param {Array} dragArea The base selection area extended by the drag area.
     * @param {String} direction Drag direction.
     * @return {Array|null} Array representing the drag area coordinates.
     */

  }, {
    key: "getDragArea",
    value: function getDragArea(baseArea, dragArea, direction) {
      var _baseArea3 = _slicedToArray(baseArea, 4),
          baseAreaStartRow = _baseArea3[0],
          baseAreaStartColumn = _baseArea3[1],
          baseAreaEndRow = _baseArea3[2],
          baseAreaEndColumn = _baseArea3[3];

      var _dragArea2 = _slicedToArray(dragArea, 4),
          dragAreaStartRow = _dragArea2[0],
          dragAreaStartColumn = _dragArea2[1],
          dragAreaEndRow = _dragArea2[2],
          dragAreaEndColumn = _dragArea2[3];

      switch (direction) {
        case 'up':
          return [dragAreaStartRow, dragAreaStartColumn, baseAreaStartRow - 1, baseAreaEndColumn];

        case 'down':
          return [baseAreaEndRow + 1, baseAreaStartColumn, dragAreaEndRow, baseAreaEndColumn];

        case 'left':
          return [dragAreaStartRow, dragAreaStartColumn, baseAreaEndRow, baseAreaStartColumn - 1];

        case 'right':
          return [baseAreaStartRow, baseAreaEndColumn + 1, dragAreaEndRow, dragAreaEndColumn];

        default:
          return null;
      }
    }
    /**
     * Get the to-be-farthest merged cell in the newly filled area.
     *
     * @private
     * @param {Array} baseArea The base selection area.
     * @param {Array} dragArea The drag area (containing the base area).
     * @param {String} direction The drag direction.
     * @param {Array} mergedCellArray Array of the merged cells found in the base area.
     * @return {MergedCellCoords|null}
     */

  }, {
    key: "getFarthestCollection",
    value: function getFarthestCollection(baseArea, dragArea, direction, mergedCellArray) {
      var _baseArea4 = _slicedToArray(baseArea, 4),
          baseAreaStartRow = _baseArea4[0],
          baseAreaStartColumn = _baseArea4[1],
          baseAreaEndRow = _baseArea4[2],
          baseAreaEndColumn = _baseArea4[3];

      var verticalDirection = ['up', 'down'].indexOf(direction) > -1;
      var baseEnd = verticalDirection ? baseAreaEndRow : baseAreaEndColumn;
      var baseStart = verticalDirection ? baseAreaStartRow : baseAreaStartColumn;
      var fillSize = this.getAutofillSize(baseArea, dragArea, direction);
      var fullCycle = verticalDirection ? baseAreaEndRow - baseAreaStartRow + 1 : baseAreaEndColumn - baseAreaStartColumn + 1;
      var fulls = Math.floor(fillSize / fullCycle) * fullCycle;
      var partials = fillSize - fulls;
      var inclusionFunctionName = null;
      var farthestCollection = null;
      var endOfDragRecreationIndex = null;

      switch (direction) {
        case 'up':
          inclusionFunctionName = 'includesVertically';
          endOfDragRecreationIndex = baseEnd - partials + 1;
          break;

        case 'left':
          inclusionFunctionName = 'includesHorizontally';
          endOfDragRecreationIndex = baseEnd - partials + 1;
          break;

        case 'down':
          inclusionFunctionName = 'includesVertically';
          endOfDragRecreationIndex = baseStart + partials - 1;
          break;

        case 'right':
          inclusionFunctionName = 'includesHorizontally';
          endOfDragRecreationIndex = baseStart + partials - 1;
          break;

        default:
      }

      arrayEach(mergedCellArray, function (currentCollection) {
        if (currentCollection[inclusionFunctionName](endOfDragRecreationIndex) && currentCollection.isFarther(farthestCollection, direction)) {
          farthestCollection = currentCollection;
        }
      });
      return farthestCollection;
    }
    /**
     * Recreate the merged cells after the autofill process.
     *
     * @param {Array} changes Changes made.
     */

  }, {
    key: "recreateAfterDataPopulation",
    value: function recreateAfterDataPopulation(changes) {
      if (!this.currentFillData) {
        return;
      }

      var fillRange = this.getRangeFromChanges(changes);
      var foundMergedCells = this.currentFillData.foundMergedCells;
      var dragDirection = this.currentFillData.dragDirection;

      var inBounds = function inBounds(current, offset) {
        switch (dragDirection) {
          case 'up':
            return current.row - offset >= fillRange.from.row;

          case 'down':
            return current.row + current.rowspan - 1 + offset <= fillRange.to.row;

          case 'left':
            return current.col - offset >= fillRange.from.column;

          case 'right':
            return current.col + current.colspan - 1 + offset <= fillRange.to.column;

          default:
            return null;
        }
      };

      var fillOffset = 0;
      var current = null;
      var multiplier = 1;

      do {
        for (var j = 0; j < foundMergedCells.length; j += 1) {
          current = foundMergedCells[j];
          fillOffset = multiplier * this.currentFillData.cycleLength;

          if (inBounds(current, fillOffset)) {
            switch (dragDirection) {
              case 'up':
                this.plugin.mergedCellsCollection.add({
                  row: current.row - fillOffset,
                  rowspan: current.rowspan,
                  col: current.col,
                  colspan: current.colspan
                });
                break;

              case 'down':
                this.plugin.mergedCellsCollection.add({
                  row: current.row + fillOffset,
                  rowspan: current.rowspan,
                  col: current.col,
                  colspan: current.colspan
                });
                break;

              case 'left':
                this.plugin.mergedCellsCollection.add({
                  row: current.row,
                  rowspan: current.rowspan,
                  col: current.col - fillOffset,
                  colspan: current.colspan
                });
                break;

              case 'right':
                this.plugin.mergedCellsCollection.add({
                  row: current.row,
                  rowspan: current.rowspan,
                  col: current.col + fillOffset,
                  colspan: current.colspan
                });
                break;

              default:
            }
          }

          if (j === foundMergedCells.length - 1) {
            multiplier += 1;
          }
        }
      } while (inBounds(current, fillOffset));

      this.currentFillData = null;
      this.plugin.hot.render();
    }
    /**
     * Get the drag range from the changes made.
     *
     * @private
     * @param {Array} changes The changes made.
     * @returns {Object} Object with `from` and `to` properties, both containing `row` and `column` keys.
     */

  }, {
    key: "getRangeFromChanges",
    value: function getRangeFromChanges(changes) {
      var _this = this;

      var rows = {
        min: null,
        max: null
      };
      var columns = {
        min: null,
        max: null
      };
      arrayEach(changes, function (change) {
        var rowIndex = change[0];

        var columnIndex = _this.plugin.hot.propToCol(change[1]);

        if (rows.min === null || rowIndex < rows.min) {
          rows.min = rowIndex;
        }

        if (rows.max === null || rowIndex > rows.max) {
          rows.max = rowIndex;
        }

        if (columns.min === null || columnIndex < columns.min) {
          columns.min = columnIndex;
        }

        if (columns.max === null || columnIndex > columns.max) {
          columns.max = columnIndex;
        }
      });
      return {
        from: {
          row: rows.min,
          column: columns.min
        },
        to: {
          row: rows.max,
          column: columns.max
        }
      };
    }
    /**
     * Check if the drag area contains any merged cells.
     *
     * @param {Array} baseArea The base selection area.
     * @param {Array} fullArea The base area extended by the drag area.
     * @param {String} direction Drag direction.
     * @returns {Boolean}
     */

  }, {
    key: "dragAreaOverlapsCollections",
    value: function dragAreaOverlapsCollections(baseArea, fullArea, direction) {
      var dragArea = this.getDragArea(baseArea, fullArea, direction);

      var _dragArea3 = _slicedToArray(dragArea, 4),
          dragAreaStartRow = _dragArea3[0],
          dragAreaStartColumn = _dragArea3[1],
          dragAreaEndRow = _dragArea3[2],
          dragAreaEndColumn = _dragArea3[3];

      var topLeft = new CellCoords(dragAreaStartRow, dragAreaStartColumn);
      var bottomRight = new CellCoords(dragAreaEndRow, dragAreaEndColumn);
      var dragRange = new CellRange(topLeft, topLeft, bottomRight);
      return !!this.mergedCellsCollection.getWithinRange(dragRange, true);
    }
  }]);

  return AutofillCalculations;
}();

export default AutofillCalculations;