'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var layout = require('../../util/layout.js');
var model = require('../../util/model.js');
var util = require('../../../../../../zrender@6.0.0/node_modules/zrender/lib/core/util.js');
var graphic = require('../../util/graphic.js');
var Model = require('../../model/Model.js');
var number = require('../../util/number.js');
var matrixCoordHelper = require('./matrixCoordHelper.js');
var log = require('../../util/log.js');
var CoordinateSystem = require('../../core/CoordinateSystem.js');

var Matrix = /** @class */function () {
  function Matrix(matrixModel, ecModel, api) {
    this.dimensions = Matrix.dimensions;
    this.type = 'matrix';
    this._model = matrixModel;
    var models = this._dimModels = {
      x: matrixModel.getDimensionModel('x'),
      y: matrixModel.getDimensionModel('y')
    };
    this._dims = {
      x: models.x.dim,
      y: models.y.dim
    };
    this._resize(matrixModel, api);
  }
  /**
   * @see fetchers in `model/referHelper.ts`,
   * which is used to parse data in ordinal way.
   * In most series only 'x' and 'y' is required,
   * but some series, such as heatmap, can specify value.
   */
  Matrix.getDimensionsInfo = function () {
    return [{
      name: 'x',
      type: 'ordinal'
    }, {
      name: 'y',
      type: 'ordinal'
    }, {
      name: 'value'
    }];
  };
  Matrix.create = function (ecModel, api) {
    var matrixList = [];
    ecModel.eachComponent('matrix', function (matrixModel) {
      var matrix = new Matrix(matrixModel, ecModel, api);
      matrixList.push(matrix);
      matrixModel.coordinateSystem = matrix;
    });
    // Inject coordinate system
    // PENDING: optimize to not to travel all components?
    //  (collect relevant components in ecModel only when model update?)
    ecModel.eachComponent(function (mainType, componentModel) {
      CoordinateSystem.injectCoordSysByOption({
        targetModel: componentModel,
        coordSysType: 'matrix',
        coordSysProvider: CoordinateSystem.simpleCoordSysInjectionProvider
      });
    });
    return matrixList;
  };
  Matrix.prototype.getRect = function () {
    return this._rect;
  };
  Matrix.prototype._resize = function (matrixModel, api) {
    var dims = this._dims;
    var dimModels = this._dimModels;
    var rect = this._rect = layout.getLayoutRect(matrixModel.getBoxLayoutParams(), {
      width: api.getWidth(),
      height: api.getHeight()
    });
    layOutUnitsOnDimension(dimModels, dims, rect, 0);
    layOutUnitsOnDimension(dimModels, dims, rect, 1);
    layOutDimCellsRestInfoByUnit(0, dims);
    layOutDimCellsRestInfoByUnit(1, dims);
    layOutBodyCornerCellMerge(this._model.getBody(), dims);
    layOutBodyCornerCellMerge(this._model.getCorner(), dims);
  };
  /**
   * @implement
   * - The input is allowed to be `[NaN/null/undefined, xxx]`/`[xxx, NaN/null/undefined]`;
   *  the return is `[NaN, xxxresult]`/`[xxxresult, NaN]` or clamped boundary value if
   *  `clamp` passed. This is for the usage that only get coord on single x or y.
   * - Alwasy return an numeric array, but never be null/undefined.
   *  If it can not be located or invalid, return `[NaN, NaN]`.
   */
  Matrix.prototype.dataToPoint = function (data, opt, out) {
    out = out || [];
    this.dataToLayout(data, opt, _dtpOutDataToLayout);
    out[0] = _dtpOutDataToLayout.rect.x + _dtpOutDataToLayout.rect.width / 2;
    out[1] = _dtpOutDataToLayout.rect.y + _dtpOutDataToLayout.rect.height / 2;
    return out;
  };
  /**
   * @implement
   * - The input is allowed to be `[NaN/null/undefined, xxx]`/`[xxx, NaN/null/undefined]`;
   *  the return is `{x: NaN, width: NaN, y: xxxresulty, height: xxxresulth}`/
   *  `{y: NaN, height: NaN, x: xxxresultx, width: xxxresultw}` or clamped boundary value
   *  if `clamp` passed. This is for the usage that only get coord on single x or y.
   * - The returned `out.rect` and `out.matrixXYLocatorRange` is always an object or an 2d-array,
   *  but never be null/undefined. If it cannot be located or invalid, `NaN` is in their
   *  corresponding number props.
   * - Do not provide `out.contentRect`, because it's allowed to input non-leaf dimension x/y or
   *  a range of x/y, which determines a rect covering multiple cells (even not merged), in which
   *  case the padding and borderWidth can not be determined to make a contentRect. Therefore only
   *  return `out.rect` in any case for consistency. The caller is responsible for adding space to
   *  avoid covering cell borders, if necessary.
   */
  Matrix.prototype.dataToLayout = function (data, opt, out) {
    var dims = this._dims;
    out = out || {};
    var outRect = out.rect = out.rect || {};
    outRect.x = outRect.y = outRect.width = outRect.height = NaN;
    var outLocRange = out.matrixXYLocatorRange = matrixCoordHelper.resetXYLocatorRange(out.matrixXYLocatorRange);
    if (!util.isArray(data)) {
      if (process.env.NODE_ENV !== 'production') {
        log.error('Input data must be an array in `convertToLayout`, `convertToPixel`');
      }
      return out;
    }
    matrixCoordHelper.parseCoordRangeOption(outLocRange, null, data, dims, util.retrieve2(opt && opt.clamp, matrixCoordHelper.MatrixClampOption.none));
    if (!opt || !opt.ignoreMergeCells) {
      if (!opt || opt.clamp !== matrixCoordHelper.MatrixClampOption.corner) {
        this._model.getBody().expandRangeByCellMerge(outLocRange);
      }
      if (!opt || opt.clamp !== matrixCoordHelper.MatrixClampOption.body) {
        this._model.getCorner().expandRangeByCellMerge(outLocRange);
      }
    }
    matrixCoordHelper.xyLocatorRangeToRectOneDim(outRect, outLocRange, dims, 0);
    matrixCoordHelper.xyLocatorRangeToRectOneDim(outRect, outLocRange, dims, 1);
    return out;
  };
  /**
   * The returned locator pair can be the input of `dataToPoint` or `dataToLayout`.
   *
   * If point[0] is out of the matrix rect,
   *  the out[0] is NaN;
   * else if it is on the right of top-left corner of body,
   *  the out[0] is the oridinal number (>= 0).
   * else
   *  out[0] is the locator for corner or header (<= 0).
   *
   * The same rule goes for point[1] and out[1].
   *
   * But point[0] and point[1] are calculated separately, i.e.,
   * the reuslt can be `[1, NaN]` or `[NaN, 1]` if only one dimension is out of boundary.
   *
   * @implement
   */
  Matrix.prototype.pointToData = function (point, opt, out) {
    var dims = this._dims;
    pointToDataOneDimPrepareCtx(_tmpCtxPointToData, 0, dims, point, opt && opt.clamp);
    pointToDataOneDimPrepareCtx(_tmpCtxPointToData, 1, dims, point, opt && opt.clamp);
    out = out || [];
    out[0] = out[1] = NaN;
    if (_tmpCtxPointToData.y === CtxPointToDataAreaType.inCorner && _tmpCtxPointToData.x === CtxPointToDataAreaType.inBody) {
      pointToDataOnlyHeaderFillOut(_tmpCtxPointToData, out, 0, dims);
    } else if (_tmpCtxPointToData.x === CtxPointToDataAreaType.inCorner && _tmpCtxPointToData.y === CtxPointToDataAreaType.inBody) {
      pointToDataOnlyHeaderFillOut(_tmpCtxPointToData, out, 1, dims);
    } else {
      pointToDataBodyCornerFillOut(_tmpCtxPointToData, out, 0, dims);
      pointToDataBodyCornerFillOut(_tmpCtxPointToData, out, 1, dims);
    }
    return out;
  };
  Matrix.prototype.convertToPixel = function (ecModel, finder, value, opt) {
    var coordSys = getCoordSys(finder);
    return coordSys === this ? coordSys.dataToPoint(value, opt) : undefined;
  };
  Matrix.prototype.convertToLayout = function (ecModel, finder, value, opt) {
    var coordSys = getCoordSys(finder);
    return coordSys === this ? coordSys.dataToLayout(value, opt) : undefined;
  };
  Matrix.prototype.convertFromPixel = function (ecModel, finder, pixel, opt) {
    var coordSys = getCoordSys(finder);
    return coordSys === this ? coordSys.pointToData(pixel, opt) : undefined;
  };
  Matrix.prototype.containPoint = function (point) {
    return this._rect.contain(point[0], point[1]);
  };
  Matrix.dimensions = ['x', 'y', 'value'];
  return Matrix;
}();
var _dtpOutDataToLayout = {
  rect: matrixCoordHelper.createNaNRectLike()
};
var _ptdLevelIt = new model.ListIterator();
var _ptdDimCellIt = new model.ListIterator();
function layOutUnitsOnDimension(dimModels, dims, matrixRect, dimIdx) {
  var otherDimIdx = 1 - dimIdx;
  var thisDim = dims[graphic.XY[dimIdx]];
  var otherDim = dims[graphic.XY[otherDimIdx]];
  // Notice: If matrix.x/y.show is false, still lay out, to ensure the
  // consistent return of `dataToLayout`.
  var otherDimShow = otherDim.shouldShow();
  // Reset
  for (var it_1 = thisDim.resetCellIterator(); it_1.next();) {
    it_1.item.wh = it_1.item.xy = NaN;
  }
  for (var it_2 = otherDim.resetLayoutIterator(null, dimIdx); it_2.next();) {
    it_2.item.wh = it_2.item.xy = NaN;
  }
  // Set specified size from option.
  var restSize = matrixRect[graphic.WH[dimIdx]];
  var restCellsCount = thisDim.getLocatorCount(dimIdx) + otherDim.getLocatorCount(dimIdx);
  var tmpLevelModel = new Model.default();
  for (var it_3 = otherDim.resetLevelIterator(); it_3.next();) {
    // Consider `matrix.x.levelSize` and `matrix.x.levels[i].levelSize`.
    tmpLevelModel.option = it_3.item.option;
    tmpLevelModel.parentModel = dimModels[graphic.XY[otherDimIdx]];
    layOutSpecified(it_3.item, otherDimShow ? tmpLevelModel.get('levelSize') : 0);
  }
  var tmpCellModel = new Model.default();
  for (var it_4 = thisDim.resetCellIterator(); it_4.next();) {
    // Only leaf support size specification, to avoid unnecessary complexity.
    if (it_4.item.type === matrixCoordHelper.MatrixCellLayoutInfoType.leaf) {
      tmpCellModel.option = it_4.item.option;
      tmpCellModel.parentModel = undefined;
      layOutSpecified(it_4.item, tmpCellModel.get('size'));
    }
  }
  function layOutSpecified(item, sizeOption) {
    var size = parseSizeOption(sizeOption, dimIdx, matrixRect);
    if (!util.eqNaN(size)) {
      item.wh = confineSize(size, restSize);
      restSize = confineSize(restSize - item.wh);
      restCellsCount--;
    }
  }
  // Set all sizes and positions to levels and leaf cells of which size is unspecified.
  // Contents lay out based on matrix, rather than inverse; therefore do not support
  // calculating size based on content, but allocate equally.
  var computedCellWH = restCellsCount ? restSize / restCellsCount : 0;
  // If all size specified, but some space remain (may also caused by matrix.x/y.show: false)
  // do not align to the big most edge.
  var notAlignToBigmost = !restCellsCount && restSize >= 1; // `1` for cumulative precision error.
  var currXY = matrixRect[graphic.XY[dimIdx]];
  var maxLocator = thisDim.getLocatorCount(dimIdx) - 1;
  var it = new model.ListIterator();
  // Lay out levels of the perpendicular dim.
  for (otherDim.resetLayoutIterator(it, dimIdx); it.next();) {
    layOutUnspecified(it.item);
  }
  for (thisDim.resetLayoutIterator(it, dimIdx); it.next();) {
    layOutUnspecified(it.item);
  }
  function layOutUnspecified(item) {
    if (util.eqNaN(item.wh)) {
      item.wh = computedCellWH;
    }
    item.xy = currXY;
    if (item.id[graphic.XY[dimIdx]] === maxLocator && !notAlignToBigmost) {
      // Align to the rightmost border, consider cumulative precision error.
      item.wh = matrixRect[graphic.XY[dimIdx]] + matrixRect[graphic.WH[dimIdx]] - item.xy;
    }
    currXY += item.wh;
  }
}
function layOutDimCellsRestInfoByUnit(dimIdx, dims) {
  // Finally save layout info based on the unit leaves and levels.
  for (var it_5 = dims[graphic.XY[dimIdx]].resetCellIterator(); it_5.next();) {
    var dimCell = it_5.item;
    layOutRectOneDimBasedOnUnit(dimCell.rect, dimIdx, dimCell.id, dimCell.span, dims);
    // Consider level varitation on tree leaves, should extend the size to touch matrix body
    // to avoid weird appearance.
    layOutRectOneDimBasedOnUnit(dimCell.rect, 1 - dimIdx, dimCell.id, dimCell.span, dims);
    if (dimCell.type === matrixCoordHelper.MatrixCellLayoutInfoType.nonLeaf) {
      // `xy` and `wh` need to be saved in non-leaf since it supports locating by non-leaf
      // in `dataToPoint` or `dataToLayout`.
      dimCell.xy = dimCell.rect[graphic.XY[dimIdx]];
      dimCell.wh = dimCell.rect[graphic.WH[dimIdx]];
    }
  }
}
function layOutBodyCornerCellMerge(bodyOrCorner, dims) {
  bodyOrCorner.travelExistingCells(function (cell) {
    var computedSpan = cell.span;
    if (computedSpan) {
      var layoutRect = cell.spanRect;
      var id = cell.id;
      layOutRectOneDimBasedOnUnit(layoutRect, 0, id, computedSpan, dims);
      layOutRectOneDimBasedOnUnit(layoutRect, 1, id, computedSpan, dims);
    }
  });
}
// Save to rect for rendering.
function layOutRectOneDimBasedOnUnit(outRect, dimIdx, id, span, dims) {
  outRect[graphic.WH[dimIdx]] = 0;
  var locator = id[graphic.XY[dimIdx]];
  var dim = locator < 0 ? dims[graphic.XY[1 - dimIdx]] : dims[graphic.XY[dimIdx]];
  var layoutUnit = dim.getUnitLayoutInfo(dimIdx, id[graphic.XY[dimIdx]]);
  outRect[graphic.XY[dimIdx]] = layoutUnit.xy;
  outRect[graphic.WH[dimIdx]] = layoutUnit.wh;
  if (span[graphic.XY[dimIdx]] > 1) {
    var layoutUnit2 = dim.getUnitLayoutInfo(dimIdx, id[graphic.XY[dimIdx]] + span[graphic.XY[dimIdx]] - 1);
    // Be careful the cumulative error - cell must be aligned.
    outRect[graphic.WH[dimIdx]] = layoutUnit2.xy + layoutUnit2.wh - layoutUnit.xy;
  }
}
/**
 * Return NaN if not defined or invalid.
 */
function parseSizeOption(sizeOption, dimIdx, matrixRect) {
  var sizeNum = number.parsePositionSizeOption(sizeOption, matrixRect[graphic.WH[dimIdx]]);
  return confineSize(sizeNum, matrixRect[graphic.WH[dimIdx]]);
}
function confineSize(sizeNum, sizeLimit) {
  return Math.max(Math.min(sizeNum, util.retrieve2(sizeLimit, Infinity)), 0);
}
function getCoordSys(finder) {
  var matrixModel = finder.matrixModel;
  var seriesModel = finder.seriesModel;
  var coordSys = matrixModel ? matrixModel.coordinateSystem : seriesModel ? seriesModel.coordinateSystem : null;
  return coordSys;
}
var CtxPointToDataAreaType = {
  inBody: 1,
  inCorner: 2,
  outside: 3
};
// For handy performance optimization in pointToData.
var _tmpCtxPointToData = {
  x: null,
  y: null,
  point: []
};
function pointToDataOneDimPrepareCtx(ctx, dimIdx, dims, point, clamp) {
  var thisDim = dims[graphic.XY[dimIdx]];
  var otherDim = dims[graphic.XY[1 - dimIdx]];
  // Notice: considered cases: `matrix.x/y.show: false`, `matrix.x/y.data` is empty.
  // In this cases the `layout.xy` is on the edge and `layout.wh` is `0`; they still can be
  // use to calculate clampping.
  var bodyMaxUnit = thisDim.getUnitLayoutInfo(dimIdx, thisDim.getLocatorCount(dimIdx) - 1);
  var body0Unit = thisDim.getUnitLayoutInfo(dimIdx, 0);
  var cornerMinUnit = otherDim.getUnitLayoutInfo(dimIdx, -otherDim.getLocatorCount(dimIdx));
  var cornerMinus1Unit = otherDim.shouldShow() ? otherDim.getUnitLayoutInfo(dimIdx, -1) : null;
  var coord = ctx.point[dimIdx] = point[dimIdx]; // Transfer the oridinal coord.
  if (!body0Unit && !cornerMinus1Unit) {
    ctx[graphic.XY[dimIdx]] = CtxPointToDataAreaType.outside;
    return;
  }
  if (clamp === matrixCoordHelper.MatrixClampOption.body) {
    if (body0Unit) {
      ctx[graphic.XY[dimIdx]] = CtxPointToDataAreaType.inBody;
      coord = number.mathMin(bodyMaxUnit.xy + bodyMaxUnit.wh, number.mathMax(body0Unit.xy, coord));
      ctx.point[dimIdx] = coord;
    } else {
      // If clamp to body, the result must not be in header.
      ctx[graphic.XY[dimIdx]] = CtxPointToDataAreaType.outside;
    }
    return;
  } else if (clamp === matrixCoordHelper.MatrixClampOption.corner) {
    if (cornerMinus1Unit) {
      ctx[graphic.XY[dimIdx]] = CtxPointToDataAreaType.inCorner;
      coord = number.mathMin(cornerMinus1Unit.xy + cornerMinus1Unit.wh, number.mathMax(cornerMinUnit.xy, coord));
      ctx.point[dimIdx] = coord;
    } else {
      // If clamp to corner, the result must not be in body.
      ctx[graphic.XY[dimIdx]] = CtxPointToDataAreaType.outside;
    }
    return;
  }
  var pxLoc0 = body0Unit ? body0Unit.xy : cornerMinus1Unit ? cornerMinus1Unit.xy + cornerMinus1Unit.wh : NaN;
  var pxMin = cornerMinUnit ? cornerMinUnit.xy : pxLoc0;
  var pxMax = bodyMaxUnit ? bodyMaxUnit.xy + bodyMaxUnit.wh : pxLoc0;
  if (coord < pxMin) {
    if (!clamp) {
      // Quick pass for later calc, since mouse event on any place will enter this method if use `pointToData`.
      ctx[graphic.XY[dimIdx]] = CtxPointToDataAreaType.outside;
      return;
    }
    coord = pxMin;
  } else if (coord > pxMax) {
    if (!clamp) {
      ctx[graphic.XY[dimIdx]] = CtxPointToDataAreaType.outside;
      return;
    }
    coord = pxMax;
  }
  ctx.point[dimIdx] = coord; // Save the updated coord.
  ctx[graphic.XY[dimIdx]] = pxLoc0 <= coord && coord <= pxMax ? CtxPointToDataAreaType.inBody : pxMin <= coord && coord <= pxLoc0 ? CtxPointToDataAreaType.inCorner : CtxPointToDataAreaType.outside;
  // Every props in ctx must be set in every branch of this method.
}
// Assume partialOut has been set to NaN outside.
// This method may fill out[0] and out[1] in one call.
function pointToDataOnlyHeaderFillOut(ctx, partialOut, dimIdx, dims) {
  var otherDimIdx = 1 - dimIdx;
  if (ctx[graphic.XY[dimIdx]] === CtxPointToDataAreaType.outside) {
    return;
  }
  for (dims[graphic.XY[dimIdx]].resetCellIterator(_ptdDimCellIt); _ptdDimCellIt.next();) {
    var cell = _ptdDimCellIt.item;
    if (isCoordInRect(ctx.point[dimIdx], cell.rect, dimIdx) && isCoordInRect(ctx.point[otherDimIdx], cell.rect, otherDimIdx)) {
      // non-leaves are also allowed to be located.
      // If the point is in x or y dimension cell area, should check both x and y coord to
      // determine a cell; in this way a non-leaf cell can be determined.
      partialOut[dimIdx] = cell.ordinal;
      partialOut[otherDimIdx] = cell.id[graphic.XY[otherDimIdx]];
      return;
    }
  }
}
// Assume partialOut has been set to NaN outside.
// This method may fill out[0] and out[1] in one call.
function pointToDataBodyCornerFillOut(ctx, partialOut, dimIdx, dims) {
  if (ctx[graphic.XY[dimIdx]] === CtxPointToDataAreaType.outside) {
    return;
  }
  var dim = ctx[graphic.XY[dimIdx]] === CtxPointToDataAreaType.inCorner ? dims[graphic.XY[1 - dimIdx]] : dims[graphic.XY[dimIdx]];
  for (dim.resetLayoutIterator(_ptdLevelIt, dimIdx); _ptdLevelIt.next();) {
    if (isCoordInLayoutInfo(ctx.point[dimIdx], _ptdLevelIt.item)) {
      partialOut[dimIdx] = _ptdLevelIt.item.id[graphic.XY[dimIdx]];
      return;
    }
  }
}
function isCoordInLayoutInfo(coord, cell) {
  return cell.xy <= coord && coord <= cell.xy + cell.wh;
}
function isCoordInRect(coord, rect, dimIdx) {
  return rect[graphic.XY[dimIdx]] <= coord && coord <= rect[graphic.XY[dimIdx]] + rect[graphic.WH[dimIdx]];
}

exports.default = Matrix;
