var _config = require("../../config");

var __DEV__ = _config.__DEV__;

var _util = require("@okfe/zrender/lib/core/util");

var isTypedArray = _util.isTypedArray;
var extend = _util.extend;
var assert = _util.assert;
var each = _util.each;
var isObject = _util.isObject;

var _model = require("../../util/model");

var getDataItemValue = _model.getDataItemValue;
var isDataItemOption = _model.isDataItemOption;

var _number = require("../../util/number");

var parseDate = _number.parseDate;

var Source = require("../Source");

var _sourceType = require("./sourceType");

var SOURCE_FORMAT_TYPED_ARRAY = _sourceType.SOURCE_FORMAT_TYPED_ARRAY;
var SOURCE_FORMAT_ARRAY_ROWS = _sourceType.SOURCE_FORMAT_ARRAY_ROWS;
var SOURCE_FORMAT_ORIGINAL = _sourceType.SOURCE_FORMAT_ORIGINAL;
var SOURCE_FORMAT_OBJECT_ROWS = _sourceType.SOURCE_FORMAT_OBJECT_ROWS;

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// TODO
// ??? refactor? check the outer usage of data provider.
// merge with defaultDimValueGetter?

/**
 * If normal array used, mutable chunk size is supported.
 * If typed array used, chunk size must be fixed.
 */
function DefaultDataProvider(source, dimSize) {
  if (!Source.isInstance(source)) {
    source = Source.seriesDataToSource(source);
  }

  this._source = source;
  var data = this._data = source.data;
  var sourceFormat = source.sourceFormat; // Typed array. TODO IE10+?

  if (sourceFormat === SOURCE_FORMAT_TYPED_ARRAY) {
    this._offset = 0;
    this._dimSize = dimSize;
    this._data = data;
  }

  var methods = providerMethods[sourceFormat === SOURCE_FORMAT_ARRAY_ROWS ? sourceFormat + '_' + source.seriesLayoutBy : sourceFormat];
  extend(this, methods);
}

var providerProto = DefaultDataProvider.prototype; // If data is pure without style configuration

providerProto.pure = false; // If data is persistent and will not be released after use.

providerProto.persistent = true; // ???! FIXME legacy data provider do not has method getSource

providerProto.getSource = function () {
  return this._source;
};

var providerMethods = {
  'arrayRows_column': {
    pure: true,
    count: function () {
      return Math.max(0, this._data.length - this._source.startIndex);
    },
    getItem: function (idx) {
      return this._data[idx + this._source.startIndex];
    },
    appendData: appendDataSimply
  },
  'arrayRows_row': {
    pure: true,
    count: function () {
      var row = this._data[0];
      return row ? Math.max(0, row.length - this._source.startIndex) : 0;
    },
    getItem: function (idx) {
      idx += this._source.startIndex;
      var item = [];
      var data = this._data;

      for (var i = 0; i < data.length; i++) {
        var row = data[i];
        item.push(row ? row[idx] : null);
      }

      return item;
    },
    appendData: function () {
      throw new Error('Do not support appendData when set seriesLayoutBy: "row".');
    }
  },
  'objectRows': {
    pure: true,
    count: countSimply,
    getItem: getItemSimply,
    appendData: appendDataSimply
  },
  'keyedColumns': {
    pure: true,
    count: function () {
      var dimName = this._source.dimensionsDefine[0].name;
      var col = this._data[dimName];
      return col ? col.length : 0;
    },
    getItem: function (idx) {
      var item = [];
      var dims = this._source.dimensionsDefine;

      for (var i = 0; i < dims.length; i++) {
        var col = this._data[dims[i].name];
        item.push(col ? col[idx] : null);
      }

      return item;
    },
    appendData: function (newData) {
      var data = this._data;
      each(newData, function (newCol, key) {
        var oldCol = data[key] || (data[key] = []);

        for (var i = 0; i < (newCol || []).length; i++) {
          oldCol.push(newCol[i]);
        }
      });
    }
  },
  'original': {
    count: countSimply,
    getItem: getItemSimply,
    appendData: appendDataSimply
  },
  'typedArray': {
    persistent: false,
    pure: true,
    count: function () {
      return this._data ? this._data.length / this._dimSize : 0;
    },
    getItem: function (idx, out) {
      idx = idx - this._offset;
      out = out || [];
      var offset = this._dimSize * idx;

      for (var i = 0; i < this._dimSize; i++) {
        out[i] = this._data[offset + i];
      }

      return out;
    },
    appendData: function (newData) {
      this._data = newData;
    },
    // Clean self if data is already used.
    clean: function () {
      // PENDING
      this._offset += this.count();
      this._data = null;
    }
  }
};

function countSimply() {
  return this._data.length;
}

function getItemSimply(idx) {
  return this._data[idx];
}

function appendDataSimply(newData) {
  for (var i = 0; i < newData.length; i++) {
    this._data.push(newData[i]);
  }
}

var rawValueGetters = {
  arrayRows: getRawValueSimply,
  objectRows: function (dataItem, dataIndex, dimIndex, dimName) {
    return dimIndex != null ? dataItem[dimName] : dataItem;
  },
  keyedColumns: getRawValueSimply,
  original: function (dataItem, dataIndex, dimIndex, dimName) {
    // FIXME
    // In some case (markpoint in geo (geo-map.html)), dataItem
    // is {coord: [...]}
    var value = getDataItemValue(dataItem);
    return dimIndex == null || !(value instanceof Array) ? value : value[dimIndex];
  },
  typedArray: getRawValueSimply
};

function getRawValueSimply(dataItem, dataIndex, dimIndex, dimName) {
  return dimIndex != null ? dataItem[dimIndex] : dataItem;
}

var defaultDimValueGetters = {
  arrayRows: getDimValueSimply,
  objectRows: function (dataItem, dimName, dataIndex, dimIndex) {
    return converDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
  },
  keyedColumns: getDimValueSimply,
  original: function (dataItem, dimName, dataIndex, dimIndex) {
    // Performance sensitive, do not use modelUtil.getDataItemValue.
    // If dataItem is an plain object with no value field, the var `value`
    // will be assigned with the object, but it will be tread correctly
    // in the `convertDataValue`.
    var value = dataItem && (dataItem.value == null ? dataItem : dataItem.value); // If any dataItem is like { value: 10 }

    if (!this._rawData.pure && isDataItemOption(dataItem)) {
      this.hasItemOption = true;
    }

    return converDataValue(value instanceof Array ? value[dimIndex] // If value is a single number or something else not array.
    : value, this._dimensionInfos[dimName]);
  },
  typedArray: function (dataItem, dimName, dataIndex, dimIndex) {
    return dataItem[dimIndex];
  }
};

function getDimValueSimply(dataItem, dimName, dataIndex, dimIndex) {
  return converDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
}
/**
 * This helper method convert value in data.
 * @param {string|number|Date} value
 * @param {Object|string} [dimInfo] If string (like 'x'), dimType defaults 'number'.
 *        If "dimInfo.ordinalParseAndSave", ordinal value can be parsed.
 */


function converDataValue(value, dimInfo) {
  // Performance sensitive.
  var dimType = dimInfo && dimInfo.type;

  if (dimType === 'ordinal') {
    // If given value is a category string
    var ordinalMeta = dimInfo && dimInfo.ordinalMeta;
    return ordinalMeta ? ordinalMeta.parseAndCollect(value) : value;
  }

  if (dimType === 'time' // spead up when using timestamp
  && typeof value !== 'number' && value != null && value !== '-') {
    value = +parseDate(value);
  } // dimType defaults 'number'.
  // If dimType is not ordinal and value is null or undefined or NaN or '-',
  // parse to NaN.


  return value == null || value === '' ? NaN // If string (like '-'), using '+' parse to NaN
  // If object, also parse to NaN
  : +value;
} // ??? FIXME can these logic be more neat: getRawValue, getRawDataItem,
// Consider persistent.
// Caution: why use raw value to display on label or tooltip?
// A reason is to avoid format. For example time value we do not know
// how to format is expected. More over, if stack is used, calculated
// value may be 0.91000000001, which have brings trouble to display.
// TODO: consider how to treat null/undefined/NaN when display?

/**
 * @param {module:echarts/data/List} data
 * @param {number} dataIndex
 * @param {string|number} [dim] dimName or dimIndex
 * @return {Array.<number>|string|number} can be null/undefined.
 */


function retrieveRawValue(data, dataIndex, dim) {
  if (!data) {
    return;
  } // Consider data may be not persistent.


  var dataItem = data.getRawDataItem(dataIndex);

  if (dataItem == null) {
    return;
  }

  var sourceFormat = data.getProvider().getSource().sourceFormat;
  var dimName;
  var dimIndex;
  var dimInfo = data.getDimensionInfo(dim);

  if (dimInfo) {
    dimName = dimInfo.name;
    dimIndex = dimInfo.index;
  }

  return rawValueGetters[sourceFormat](dataItem, dataIndex, dimIndex, dimName);
}
/**
 * Compatible with some cases (in pie, map) like:
 * data: [{name: 'xx', value: 5, selected: true}, ...]
 * where only sourceFormat is 'original' and 'objectRows' supported.
 *
 * ??? TODO
 * Supported detail options in data item when using 'arrayRows'.
 *
 * @param {module:echarts/data/List} data
 * @param {number} dataIndex
 * @param {string} attr like 'selected'
 */


function retrieveRawAttr(data, dataIndex, attr) {
  if (!data) {
    return;
  }

  var sourceFormat = data.getProvider().getSource().sourceFormat;

  if (sourceFormat !== SOURCE_FORMAT_ORIGINAL && sourceFormat !== SOURCE_FORMAT_OBJECT_ROWS) {
    return;
  }

  var dataItem = data.getRawDataItem(dataIndex);

  if (sourceFormat === SOURCE_FORMAT_ORIGINAL && !isObject(dataItem)) {
    dataItem = null;
  }

  if (dataItem) {
    return dataItem[attr];
  }
}

exports.DefaultDataProvider = DefaultDataProvider;
exports.defaultDimValueGetters = defaultDimValueGetters;
exports.retrieveRawValue = retrieveRawValue;
exports.retrieveRawAttr = retrieveRawAttr;