import "core-js/modules/es.symbol";
import "core-js/modules/es.symbol.description";
import "core-js/modules/es.symbol.iterator";
import "core-js/modules/es.array.filter";
import "core-js/modules/es.array.index-of";
import "core-js/modules/es.array.iterator";
import "core-js/modules/es.array.slice";
import "core-js/modules/es.array.sort";
import "core-js/modules/es.array.splice";
import "core-js/modules/es.map";
import "core-js/modules/es.object.to-string";
import "core-js/modules/es.regexp.exec";
import "core-js/modules/es.string.iterator";
import "core-js/modules/es.string.split";
import "core-js/modules/web.dom-collections.iterator";
import "core-js/modules/web.timers";

function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

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 SheetClip from './../lib/SheetClip/SheetClip';
import { cellMethodLookupFactory } from './helpers/data';
import { columnFactory } from './helpers/setting';
import { createObjectPropListener, deepClone, deepExtend, deepObjectSize, duckSchema, hasOwnProperty, isObject, objectEach } from './helpers/object';
import { extendArray, to2dArray } from './helpers/array';
import Interval from './utils/interval';
import { rangeEach } from './helpers/number';
var copyableLookup = cellMethodLookupFactory('copyable', false);
/**
 * Utility class that gets and saves data from/to the data source using mapping of columns numbers to object property names
 * @todo refactor arguments of methods getRange, getText to be numbers (not objects)
 * @todo remove priv, GridSettings from object constructor
 *
 * @util
 * @class DataMap
 * @private
 */

var DataMap =
/*#__PURE__*/
function () {
  _createClass(DataMap, null, [{
    key: "DESTINATION_RENDERER",

    /**
     * @type {Number}
     */
    get: function get() {
      return 1;
    }
    /**
     * @type {Number}
     */

  }, {
    key: "DESTINATION_CLIPBOARD_GENERATOR",
    get: function get() {
      return 2;
    }
    /**
     * @param {Object} instance Instance of Handsontable
     * @param {*} priv
     * @param {GridSettings} GridSettings Grid settings
     */

  }]);

  function DataMap(instance, priv, GridSettings) {
    var _this = this;

    _classCallCheck(this, DataMap);

    /**
     * Instance of {@link Handsontable}
     *
     * @private
     * @type {Handsontable}
     */
    this.instance = instance;
    /**
     * Private settings object.
     *
     * @private
     * @type {Object}
     */

    this.priv = priv;
    /**
     * Instance of {@link GridSettings}
     *
     * @private
     * @type {GridSettings}
     */

    this.GridSettings = GridSettings;
    /**
     * Reference to the original dataset.
     *
     * @type {*}
     */

    this.dataSource = this.instance.getSettings().data;
    /**
     * Cached rows number.
     *
     * @type {Number}
     */

    this.cachedLength = null;
    /**
     * Flag determines if the cache should be used.
     *
     * @type {Boolean}
     */

    this.skipCache = false;
    /**
     * Cached sourceData rows number.
     *
     * @type {Number}
     */

    this.latestSourceRowsCount = 0;
    /**
     * Generated schema based on the first row from the source data.
     *
     * @type {Object}
     */

    this.duckSchema = this.dataSource && this.dataSource[0] ? duckSchema(this.dataSource[0]) : {};
    /**
     * Cached array of properties to columns.
     *
     * @type {Array}
     */

    this.colToPropCache = void 0;
    /**
     * Cached map of properties to columns.
     *
     * @type {Map}
     */

    this.propToColCache = void 0;
    this.createMap();
    /**
     * Instance of {@link Interval}
     *
     * @type {Interval}
     */

    this.interval = Interval.create(function () {
      return _this.clearLengthCache();
    }, '15fps');
    this.instance.addHook('skipLengthCache', function (delay) {
      return _this.onSkipLengthCache(delay);
    });
    this.onSkipLengthCache(500);
  }
  /**
   * Generates cache for property to and from column addressation.
   */


  _createClass(DataMap, [{
    key: "createMap",
    value: function createMap() {
      var schema = this.getSchema();
      var i;

      if (typeof schema === 'undefined') {
        throw new Error('trying to create `columns` definition but you didn\'t provide `schema` nor `data`');
      }

      this.colToPropCache = [];
      this.propToColCache = new Map();
      var columns = this.instance.getSettings().columns;

      if (columns) {
        var maxCols = this.instance.getSettings().maxCols;
        var columnsLen = Math.min(maxCols, columns.length);
        var filteredIndex = 0;
        var columnsAsFunc = false;
        var schemaLen = deepObjectSize(schema);

        if (typeof columns === 'function') {
          columnsLen = schemaLen > 0 ? schemaLen : this.instance.countSourceCols();
          columnsAsFunc = true;
        }

        for (i = 0; i < columnsLen; i++) {
          var column = columnsAsFunc ? columns(i) : columns[i];

          if (isObject(column)) {
            if (typeof column.data !== 'undefined') {
              var index = columnsAsFunc ? filteredIndex : i;
              this.colToPropCache[index] = column.data;
              this.propToColCache.set(column.data, index);
            }

            filteredIndex += 1;
          }
        }
      } else {
        this.recursiveDuckColumns(schema);
      }
    }
    /**
     * Generates columns' translation cache.
     *
     * @param {Object} schema
     * @param {Number} lastCol
     * @param {Number} parent
     * @returns {Number}
     */

  }, {
    key: "recursiveDuckColumns",
    value: function recursiveDuckColumns(schema, lastCol, parent) {
      var _this2 = this;

      var lastColumn = lastCol;
      var propertyParent = parent;
      var prop;

      if (typeof lastColumn === 'undefined') {
        lastColumn = 0;
        propertyParent = '';
      }

      if (_typeof(schema) === 'object' && !Array.isArray(schema)) {
        objectEach(schema, function (value, key) {
          if (value === null) {
            prop = propertyParent + key;

            _this2.colToPropCache.push(prop);

            _this2.propToColCache.set(prop, lastColumn);

            lastColumn += 1;
          } else {
            lastColumn = _this2.recursiveDuckColumns(value, lastColumn, "".concat(key, "."));
          }
        });
      }

      return lastColumn;
    }
    /**
     * Returns property name that corresponds with the given column index.
     *
     * @param {Number} col Visual column index.
     * @returns {Number} Physical column index.
     */

  }, {
    key: "colToProp",
    value: function colToProp(col) {
      var physicalColumn = this.instance.toPhysicalColumn(col);

      if (!isNaN(physicalColumn) && this.colToPropCache && typeof this.colToPropCache[physicalColumn] !== 'undefined') {
        return this.colToPropCache[physicalColumn];
      }

      return physicalColumn;
    }
    /**
     * Translates property into visual column index.
     *
     * @param {Object} prop
     * @fires Hooks#modifyCol
     * @returns {Number}
     */

  }, {
    key: "propToCol",
    value: function propToCol(prop) {
      var col;

      if (typeof this.propToColCache.get(prop) === 'undefined') {
        col = prop;
      } else {
        col = this.propToColCache.get(prop);
      }

      col = this.instance.toVisualColumn(col);
      return col;
    }
    /**
     * Returns data's schema.
     *
     * @returns {Object}
     */

  }, {
    key: "getSchema",
    value: function getSchema() {
      var schema = this.instance.getSettings().dataSchema;

      if (schema) {
        if (typeof schema === 'function') {
          return schema();
        }

        return schema;
      }

      return this.duckSchema;
    }
    /**
     * Creates row at the bottom of the data array.
     *
     * @param {Number} [index] Physical index of the row before which the new row will be inserted.
     * @param {Number} [amount=1] An amount of rows to add.
     * @param {String} [source] Source of method call.
     * @fires Hooks#afterCreateRow
     * @returns {Number} Returns number of created rows.
     */

  }, {
    key: "createRow",
    value: function createRow(index) {
      var _this3 = this;

      var amount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
      var source = arguments.length > 2 ? arguments[2] : undefined;
      var numberOfCreatedRows = 0;
      var rowIndex = index;

      if (typeof rowIndex !== 'number' || rowIndex >= this.instance.countSourceRows()) {
        rowIndex = this.instance.countSourceRows();
      }

      var continueProcess = this.instance.runHooks('beforeCreateRow', rowIndex, amount, source);

      if (continueProcess === false) {
        return 0;
      }

      var maxRows = this.instance.getSettings().maxRows;
      var columnCount = this.instance.countCols();

      var _loop = function _loop() {
        var row = null;

        if (_this3.instance.dataType === 'array') {
          if (_this3.instance.getSettings().dataSchema) {
            // Clone template array
            row = deepClone(_this3.getSchema());
          } else {
            row = [];
            /* eslint-disable no-loop-func */

            rangeEach(columnCount - 1, function () {
              return row.push(null);
            });
          }
        } else if (_this3.instance.dataType === 'function') {
          row = _this3.instance.getSettings().dataSchema(rowIndex);
        } else {
          row = {};
          deepExtend(row, _this3.getSchema());
        }

        if (rowIndex === _this3.instance.countSourceRows()) {
          _this3.dataSource.push(row);
        } else {
          _this3.spliceData(rowIndex, 0, row);
        }

        numberOfCreatedRows += 1;
      };

      while (numberOfCreatedRows < amount && this.instance.countSourceRows() < maxRows) {
        _loop();
      }

      this.instance.runHooks('afterCreateRow', rowIndex, numberOfCreatedRows, source);
      this.instance.forceFullRender = true; // used when data was changed

      return numberOfCreatedRows;
    }
    /**
     * Creates column at the right of the data array.
     *
     * @param {Number} [index] Visual index of the column before which the new column will be inserted
     * @param {Number} [amount=1] An amount of columns to add.
     * @param {String} [source] Source of method call.
     * @fires Hooks#afterCreateCol
     * @returns {Number} Returns number of created columns
     */

  }, {
    key: "createCol",
    value: function createCol(index) {
      var amount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
      var source = arguments.length > 2 ? arguments[2] : undefined;

      if (!this.instance.isColumnModificationAllowed()) {
        throw new Error('Cannot create new column. When data source in an object, ' + 'you can only have as much columns as defined in first data row, data schema or in the \'columns\' setting.' + 'If you want to be able to add new columns, you have to use array datasource.');
      }

      var rlen = this.instance.countSourceRows();
      var data = this.dataSource;
      var countColumns = this.instance.countCols();
      var columnIndex = typeof index !== 'number' || index >= countColumns ? countColumns : index;
      var numberOfCreatedCols = 0;
      var currentIndex;
      var continueProcess = this.instance.runHooks('beforeCreateCol', columnIndex, amount, source);

      if (continueProcess === false) {
        return 0;
      }

      currentIndex = columnIndex;
      var maxCols = this.instance.getSettings().maxCols;

      while (numberOfCreatedCols < amount && this.instance.countCols() < maxCols) {
        var _constructor = columnFactory(this.GridSettings, this.priv.columnsSettingConflicts);

        if (typeof columnIndex !== 'number' || columnIndex >= this.instance.countCols()) {
          if (rlen > 0) {
            for (var r = 0; r < rlen; r++) {
              if (typeof data[r] === 'undefined') {
                data[r] = [];
              }

              data[r].push(null);
            }
          } else {
            data.push([null]);
          } // Add new column constructor


          this.priv.columnSettings.push(_constructor);
        } else {
          for (var row = 0; row < rlen; row++) {
            data[row].splice(currentIndex, 0, null);
          } // Add new column constructor at given index


          this.priv.columnSettings.splice(currentIndex, 0, _constructor);
        }

        numberOfCreatedCols += 1;
        currentIndex += 1;
      }

      this.instance.runHooks('afterCreateCol', columnIndex, numberOfCreatedCols, source);
      this.instance.forceFullRender = true; // used when data was changed

      return numberOfCreatedCols;
    }
    /**
     * Removes row from the data array.
     *
     * @param {Number} [index] Visual index of the row to be removed. If not provided, the last row will be removed
     * @param {Number} [amount=1] Amount of the rows to be removed. If not provided, one row will be removed
     * @param {String} [source] Source of method call.
     * @fires Hooks#beforeRemoveRow
     * @fires Hooks#afterRemoveRow
     */

  }, {
    key: "removeRow",
    value: function removeRow(index) {
      var amount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
      var source = arguments.length > 2 ? arguments[2] : undefined;
      var rowIndex = typeof index !== 'number' ? -amount : index;
      var rowsAmount = this.instance.runHooks('modifyRemovedAmount', amount, rowIndex);
      var sourceRowsLength = this.instance.countSourceRows();
      rowIndex = (sourceRowsLength + rowIndex) % sourceRowsLength;
      var logicRows = this.visualRowsToPhysical(rowIndex, rowsAmount);
      var actionWasNotCancelled = this.instance.runHooks('beforeRemoveRow', rowIndex, rowsAmount, logicRows, source);

      if (actionWasNotCancelled === false) {
        return;
      }

      var data = this.dataSource;
      var newData = this.filterData(rowIndex, rowsAmount);

      if (newData) {
        data.length = 0;
        Array.prototype.push.apply(data, newData);
      }

      this.instance.runHooks('afterRemoveRow', rowIndex, rowsAmount, logicRows, source);
      this.instance.forceFullRender = true; // used when data was changed
    }
    /**
     * Removes column from the data array.
     *
     * @param {Number} [index] Visual index of the column to be removed. If not provided, the last column will be removed
     * @param {Number} [amount=1] Amount of the columns to be removed. If not provided, one column will be removed
     * @param {String} [source] Source of method call.
     * @fires Hooks#beforeRemoveCol
     * @fires Hooks#afterRemoveCol
     */

  }, {
    key: "removeCol",
    value: function removeCol(index) {
      var amount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
      var source = arguments.length > 2 ? arguments[2] : undefined;

      if (this.instance.dataType === 'object' || this.instance.getSettings().columns) {
        throw new Error('cannot remove column with object data source or columns option specified');
      }

      var columnIndex = typeof index !== 'number' ? -amount : index;
      columnIndex = (this.instance.countCols() + columnIndex) % this.instance.countCols();
      var logicColumns = this.visualColumnsToPhysical(columnIndex, amount);
      var descendingLogicColumns = logicColumns.slice(0).sort(function (a, b) {
        return b - a;
      });
      var actionWasNotCancelled = this.instance.runHooks('beforeRemoveCol', columnIndex, amount, logicColumns, source);

      if (actionWasNotCancelled === false) {
        return;
      }

      var isTableUniform = true;
      var removedColumnsCount = descendingLogicColumns.length;
      var data = this.dataSource;

      for (var c = 0; c < removedColumnsCount; c++) {
        if (isTableUniform && logicColumns[0] !== logicColumns[c] - c) {
          isTableUniform = false;
        }
      }

      if (isTableUniform) {
        for (var r = 0, rlen = this.instance.countSourceRows(); r < rlen; r++) {
          data[r].splice(logicColumns[0], amount);
        }
      } else {
        for (var _r = 0, _rlen = this.instance.countSourceRows(); _r < _rlen; _r++) {
          for (var _c = 0; _c < removedColumnsCount; _c++) {
            data[_r].splice(descendingLogicColumns[_c], 1);
          }
        }

        for (var _c2 = 0; _c2 < removedColumnsCount; _c2++) {
          this.priv.columnSettings.splice(logicColumns[_c2], 1);
        }
      }

      this.instance.runHooks('afterRemoveCol', columnIndex, amount, logicColumns, source);
      this.instance.forceFullRender = true; // used when data was changed
    }
    /**
     * Add/Removes data from the column.
     *
     * @param {Number} col Physical index of column in which do you want to do splice
     * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end
     * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed
     * @param {Array} [elements]
     * @returns {Array} Returns removed portion of columns
     */

  }, {
    key: "spliceCol",
    value: function spliceCol(col, index, amount) {
      var colData = this.instance.getDataAtCol(col);
      var removed = colData.slice(index, index + amount);
      var after = colData.slice(index + amount);

      for (var _len = arguments.length, elements = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
        elements[_key - 3] = arguments[_key];
      }

      extendArray(elements, after);
      var i = 0;

      while (i < amount) {
        elements.push(null); // add null in place of removed elements

        i += 1;
      }

      to2dArray(elements);
      this.instance.populateFromArray(index, col, elements, null, null, 'spliceCol');
      return removed;
    }
    /**
     * Add/Removes data from the row.
     *
     * @param {Number} row Physical index of row in which do you want to do splice
     * @param {Number} index Index at which to start changing the array. If negative, will begin that many elements from the end.
     * @param {Number} amount An integer indicating the number of old array elements to remove. If amount is 0, no elements are removed.
     * @param {Array} [elements]
     * @returns {Array} Returns removed portion of rows
     */

  }, {
    key: "spliceRow",
    value: function spliceRow(row, index, amount) {
      var rowData = this.instance.getSourceDataAtRow(row);
      var removed = rowData.slice(index, index + amount);
      var after = rowData.slice(index + amount);

      for (var _len2 = arguments.length, elements = new Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) {
        elements[_key2 - 3] = arguments[_key2];
      }

      extendArray(elements, after);
      var i = 0;

      while (i < amount) {
        elements.push(null); // add null in place of removed elements

        i += 1;
      }

      this.instance.populateFromArray(row, index, [elements], null, null, 'spliceRow');
      return removed;
    }
    /**
     * Add/remove row(s) to/from the data source.
     *
     * @param {Number} index Physical index of the element to remove.
     * @param {Number} amount Number of rows to add/remove.
     * @param {Object} element Row to add.
     */

  }, {
    key: "spliceData",
    value: function spliceData(index, amount, element) {
      var continueSplicing = this.instance.runHooks('beforeDataSplice', index, amount, element);

      if (continueSplicing !== false) {
        this.dataSource.splice(index, amount, element);
      }
    }
    /**
     * Filter unwanted data elements from the data source.
     *
     * @param {Number} index Visual index of the element to remove.
     * @param {Number} amount Number of rows to add/remove.
     * @returns {Array}
     */

  }, {
    key: "filterData",
    value: function filterData(index, amount) {
      var physicalRows = this.visualRowsToPhysical(index, amount);
      var continueSplicing = this.instance.runHooks('beforeDataFilter', index, amount, physicalRows);

      if (continueSplicing !== false) {
        var newData = this.dataSource.filter(function (row, rowIndex) {
          return physicalRows.indexOf(rowIndex) === -1;
        });
        return newData;
      }
    }
    /**
     * Returns single value from the data array.
     *
     * @param {Number} row Visual row index.
     * @param {Number} prop
     * @returns {*}
     */

  }, {
    key: "get",
    value: function get(row, prop) {
      var physicalRow = this.instance.toPhysicalRow(row);
      var dataRow = this.dataSource[physicalRow]; // TODO: To remove, use 'modifyData' hook instead (see below)

      var modifiedRowData = this.instance.runHooks('modifyRowData', physicalRow);
      dataRow = isNaN(modifiedRowData) ? modifiedRowData : dataRow; //

      var value = null; // try to get value under property `prop` (includes dot)

      if (dataRow && dataRow.hasOwnProperty && hasOwnProperty(dataRow, prop)) {
        value = dataRow[prop];
      } else if (typeof prop === 'string' && prop.indexOf('.') > -1) {
        var sliced = prop.split('.');
        var out = dataRow;

        if (!out) {
          return null;
        }

        for (var i = 0, ilen = sliced.length; i < ilen; i++) {
          out = out[sliced[i]];

          if (typeof out === 'undefined') {
            return null;
          }
        }

        value = out;
      } else if (typeof prop === 'function') {
        /**
         *  allows for interacting with complex structures, for example
         *  d3/jQuery getter/setter properties:
         *
         *    {columns: [{
         *      data: function(row, value){
         *        if(arguments.length === 1){
         *          return row.property();
         *        }
         *        row.property(value);
         *      }
         *    }]}
         */
        value = prop(this.dataSource.slice(physicalRow, physicalRow + 1)[0]);
      }

      if (this.instance.hasHook('modifyData')) {
        var valueHolder = createObjectPropListener(value);
        this.instance.runHooks('modifyData', physicalRow, this.propToCol(prop), valueHolder, 'get');

        if (valueHolder.isTouched()) {
          value = valueHolder.value;
        }
      }

      return value;
    }
    /**
     * Returns single value from the data array (intended for clipboard copy to an external application).
     *
     * @param {Number} row Physical row index.
     * @param {Number} prop
     * @returns {String}
     */

  }, {
    key: "getCopyable",
    value: function getCopyable(row, prop) {
      if (copyableLookup.call(this.instance, row, this.propToCol(prop))) {
        return this.get(row, prop);
      }

      return '';
    }
    /**
     * Saves single value to the data array.
     *
     * @param {Number} row Visual row index.
     * @param {Number} prop
     * @param {String} value
     * @param {String} [source] Source of hook runner.
     */

  }, {
    key: "set",
    value: function set(row, prop, value, source) {
      var physicalRow = this.instance.runHooks('modifyRow', row, source || 'datamapGet');
      var newValue = value;
      var dataRow = this.dataSource[physicalRow]; // TODO: To remove, use 'modifyData' hook instead (see below)

      var modifiedRowData = this.instance.runHooks('modifyRowData', physicalRow);
      dataRow = isNaN(modifiedRowData) ? modifiedRowData : dataRow; //

      if (this.instance.hasHook('modifyData')) {
        var valueHolder = createObjectPropListener(newValue);
        this.instance.runHooks('modifyData', physicalRow, this.propToCol(prop), valueHolder, 'set');

        if (valueHolder.isTouched()) {
          newValue = valueHolder.value;
        }
      } // try to set value under property `prop` (includes dot)


      if (dataRow && dataRow.hasOwnProperty && hasOwnProperty(dataRow, prop)) {
        dataRow[prop] = newValue;
      } else if (typeof prop === 'string' && prop.indexOf('.') > -1) {
        var sliced = prop.split('.');
        var out = dataRow;
        var i = 0;
        var ilen;

        for (i = 0, ilen = sliced.length - 1; i < ilen; i++) {
          if (typeof out[sliced[i]] === 'undefined') {
            out[sliced[i]] = {};
          }

          out = out[sliced[i]];
        }

        out[sliced[i]] = newValue;
      } else if (typeof prop === 'function') {
        /* see the `function` handler in `get` */
        prop(this.dataSource.slice(physicalRow, physicalRow + 1)[0], newValue);
      } else {
        dataRow[prop] = newValue;
      }
    }
    /**
     * This ridiculous piece of code maps rows Id that are present in table data to those displayed for user.
     * The trick is, the physical row id (stored in settings.data) is not necessary the same
     * as the visual (displayed) row id (e.g. when sorting is applied).
     *
     * @param {Number} index Visual row index.
     * @param {Number} amount
     * @fires Hooks#modifyRow
     * @returns {Number}
     */

  }, {
    key: "visualRowsToPhysical",
    value: function visualRowsToPhysical(index, amount) {
      var totalRows = this.instance.countSourceRows();
      var logicRows = [];
      var physicRow = (totalRows + index) % totalRows;
      var rowsToRemove = amount;
      var row;

      while (physicRow < totalRows && rowsToRemove) {
        row = this.instance.toPhysicalRow(physicRow);
        logicRows.push(row);
        rowsToRemove -= 1;
        physicRow += 1;
      }

      return logicRows;
    }
    /**
     *
     * @param index Visual column index.
     * @param amount
     * @returns {Array}
     */

  }, {
    key: "visualColumnsToPhysical",
    value: function visualColumnsToPhysical(index, amount) {
      var totalCols = this.instance.countCols();
      var visualCols = [];
      var physicalCol = (totalCols + index) % totalCols;
      var colsToRemove = amount;

      while (physicalCol < totalCols && colsToRemove) {
        var col = this.instance.toPhysicalColumn(physicalCol);
        visualCols.push(col);
        colsToRemove -= 1;
        physicalCol += 1;
      }

      return visualCols;
    }
    /**
     * Clears the data array.
     */

  }, {
    key: "clear",
    value: function clear() {
      for (var r = 0; r < this.instance.countSourceRows(); r++) {
        for (var c = 0; c < this.instance.countCols(); c++) {
          this.set(r, this.colToProp(c), '');
        }
      }
    }
    /**
     * Clear cached data length.
     */

  }, {
    key: "clearLengthCache",
    value: function clearLengthCache() {
      this.cachedLength = null;
    }
    /**
     * Get data length.
     *
     * @returns {Number}
     */

  }, {
    key: "getLength",
    value: function getLength() {
      var _this4 = this;

      var maxRowsFromSettings = this.instance.getSettings().maxRows;
      var maxRows;

      if (maxRowsFromSettings < 0 || maxRowsFromSettings === 0) {
        maxRows = 0;
      } else {
        maxRows = maxRowsFromSettings || Infinity;
      }

      var length = this.instance.countSourceRows();

      if (this.instance.hasHook('modifyRow')) {
        var reValidate = this.skipCache;
        this.interval.start();

        if (length !== this.latestSourceRowsCount) {
          reValidate = true;
        }

        this.latestSourceRowsCount = length;

        if (this.cachedLength === null || reValidate) {
          rangeEach(length - 1, function (row) {
            var physicalRow = _this4.instance.toPhysicalRow(row);

            if (physicalRow === null) {
              length -= 1;
            }
          });
          this.cachedLength = length;
        } else {
          length = this.cachedLength;
        }
      } else {
        this.interval.stop();
      }

      return Math.min(length, maxRows);
    }
    /**
     * Returns the data array.
     *
     * @returns {Array}
     */

  }, {
    key: "getAll",
    value: function getAll() {
      var start = {
        row: 0,
        col: 0
      };
      var end = {
        row: Math.max(this.instance.countSourceRows() - 1, 0),
        col: Math.max(this.instance.countCols() - 1, 0)
      };

      if (start.row - end.row === 0 && !this.instance.countSourceRows()) {
        return [];
      }

      return this.getRange(start, end, DataMap.DESTINATION_RENDERER);
    }
    /**
     * Returns data range as array.
     *
     * @param {Object} [start] Start selection position. Visual indexes.
     * @param {Object} [end] End selection position. Visual indexes.
     * @param {Number} destination Destination of datamap.get
     * @returns {Array}
     */

  }, {
    key: "getRange",
    value: function getRange(start, end, destination) {
      var output = [];
      var r;
      var c;
      var row;
      var maxRows = this.instance.getSettings().maxRows;
      var maxCols = this.instance.getSettings().maxCols;

      if (maxRows === 0 || maxCols === 0) {
        return [];
      }

      var getFn = destination === DataMap.DESTINATION_CLIPBOARD_GENERATOR ? this.getCopyable : this.get;
      var rlen = Math.min(Math.max(maxRows - 1, 0), Math.max(start.row, end.row));
      var clen = Math.min(Math.max(maxCols - 1, 0), Math.max(start.col, end.col));

      for (r = Math.min(start.row, end.row); r <= rlen; r++) {
        row = [];
        var physicalRow = this.instance.toPhysicalRow(r);

        for (c = Math.min(start.col, end.col); c <= clen; c++) {
          if (physicalRow === null) {
            break;
          }

          row.push(getFn.call(this, r, this.colToProp(c)));
        }

        if (physicalRow !== null) {
          output.push(row);
        }
      }

      return output;
    }
    /**
     * Return data as text (tab separated columns).
     *
     * @param {Object} [start] Start selection position. Visual indexes.
     * @param {Object} [end] End selection position. Visual indexes.
     * @returns {String}
     */

  }, {
    key: "getText",
    value: function getText(start, end) {
      return SheetClip.stringify(this.getRange(start, end, DataMap.DESTINATION_RENDERER));
    }
    /**
     * Return data as copyable text (tab separated columns intended for clipboard copy to an external application).
     *
     * @param {Object} [start] Start selection position. Visual indexes.
     * @param {Object} [end] End selection position. Visual indexes.
     * @returns {String}
     */

  }, {
    key: "getCopyableText",
    value: function getCopyableText(start, end) {
      return SheetClip.stringify(this.getRange(start, end, DataMap.DESTINATION_CLIPBOARD_GENERATOR));
    }
    /**
     * `skipLengthCache` callback.
     *
     * @private
     * @param {Number} delay Time of the delay in milliseconds.
     */

  }, {
    key: "onSkipLengthCache",
    value: function onSkipLengthCache(delay) {
      var _this5 = this;

      this.skipCache = true;
      setTimeout(function () {
        _this5.skipCache = false;
      }, delay);
    }
    /**
     * Destroy instance.
     */

  }, {
    key: "destroy",
    value: function destroy() {
      this.interval.stop();
      this.interval = null;
      this.instance = null;
      this.priv = null;
      this.GridSettings = null;
      this.dataSource = null;
      this.cachedLength = null;
      this.duckSchema = null;
      this.colToPropCache.length = 0;
      this.propToColCache.clear();
      this.propToColCache = void 0;
    }
  }]);

  return DataMap;
}();

export default DataMap;