import {getProperty} from './helpers/object';
import {arrayEach} from './helpers/array';
import {rangeEach} from './helpers/number';

/**
 * @class DataSource
 * @private
 */
class DataSource {
  constructor(hotInstance, dataSource = []) {
    /**
     * Instance of Handsontable.
     *
     * @type {Handsontable}
     */
    this.hot = hotInstance;
    /**
     * Data source
     *
     * @type {Array}
     */
    this.data = dataSource;
    /**
     * Type of data source.
     *
     * @type {String}
     * @default 'array'
     */
    this.dataType = 'array';

    this.colToProp = () => {};
    this.propToCol = () => {};
  }

  /**
   * Get all data.
   *
   * @param {Boolean} [toArray=false] If `true` return source data as an array of arrays even when source data was provided
   *                                  in another format.
   * @returns {Array}
   */
  getData(toArray = false) {
    let result =  this.data;

    if (toArray) {
      result = this.getByRange(
        {row: 0, col: 0},
        {row: Math.max(this.countRows() - 1, 0), col: Math.max(this.countColumns() - 1, 0)},
        true
      );
    }

    return result;
  }

  /**
   * Set new data source.
   *
   * @param data {Array}
   */
  setData(data) {
    this.data = data;
  }

  /**
   * Returns array of column values from the data source. `column` is the index of the row in the data source.
   *
   * @param {Number} column
   * @returns {Array}
   */
  getAtColumn(column) {
    let result = [];

    arrayEach(this.data, (row) => {
      let property = this.colToProp(column);

      if (typeof property === 'string') {
        row = getProperty(row, property);
      } else {
        row = row[property];
      }
      result.push(row);
    });

    return result;
  }

  /**
   * Returns a single row of the data (array or object, depending on what you have). `row` is the index of the row in the data source.
   *
   * @param {Number} row
   * @returns {Array|Object}
   */
  getAtRow(row) {
    return this.data[row];
  }

  /**
   * Returns a single value from the data.
   *
   * @param {Number} row Row index.
   * @param {Number} column Column index.
   * @returns {*}
   */
  getAtCell(row, column) {
    let result = null;

    if (this.data[row]) {
      let prop = this.colToProp(column);

      if (typeof prop === 'string') {
        result = getProperty(this.data[row], prop);
      } else {
        result = this.data[row][prop];
      }
    }

    return result;
  }

  /**
   * Returns source data by passed range.
   *
   * @param {Object} start Object with `row` and `col` keys.
   * @param {Object} end Object with `row` and `col` keys.
   * @param {Boolean} [toArray=false] If `true` return source data as an array of arrays even when source data was provided
   *                                  in another format.
   * @returns {Array}
   */
  getByRange(start, end, toArray = false) {
    let startRow = Math.min(start.row, end.row);
    let startCol = Math.min(start.col, end.col);
    let endRow = Math.max(start.row, end.row);
    let endCol = Math.max(start.col, end.col);
    let result = [];

    rangeEach(startRow, endRow, (currentRow) => {
      let row = this.getAtRow(currentRow);
      let newRow;

      if (this.dataType === 'array') {
        newRow = row.slice(startCol, endCol + 1);

      } else if (this.dataType === 'object') {
        newRow = toArray ? [] : {};

        rangeEach(startCol, endCol, (column) => {
          let prop = this.colToProp(column);

          if (toArray) {
            newRow.push(row[prop]);
          } else {
            newRow[prop] = row[prop];
          }
        });
      }

      result.push(newRow);
    });

    return result;
  }

  /**
   * Count number of rows.
   *
   * @returns {Number}
   */
  countRows() {
    return Array.isArray(this.data) ? this.data.length : 0;
  }

  /**
   * Count number of columns.
   *
   * @returns {Number}
   */
  countColumns() {
    let result = 0;

    if (Array.isArray(this.data)) {
      if (this.dataType === 'array') {
        result = this.data[0].length;

      } else if (this.dataType === 'object') {
        result = Object.keys(this.data[0]).length;
      }
    }

    return result;
  }

  /**
   * Destroy instance.
   */
  destroy() {
    this.data = null;
    this.hot = null;
  }
}

export {DataSource};
