text: import { deepMix, each, isFunction, mix, parsePadding, isNil } from '../../util/common';
  
  import Axis from '../../component/axis/index';
  
  import Global from '../../global';
  
  function formatTicks(ticks) {
    const tmp = ticks.slice(0);
    if (tmp.length > 0) {
      const first = tmp[0];
      const last = tmp[tmp.length - 1];
      if (first.value !== 0) {
        tmp.unshift({
          value: 0
        });
      }
      if (last.value !== 1) {
        tmp.push({
          value: 1
        });
      }
    }
  
    return tmp;
  }
  
  class AxisController {
    constructor(cfg) {
      this.axisCfg = {};
      this.frontPlot = null;
      this.backPlot = null;
      this.axes = {}; // store the axes's options
      mix(this, cfg);
    }
  
    _isHide(field) {
      const axisCfg = this.axisCfg;
      return !axisCfg || axisCfg[field] === false;
    }
  
    _getLinePosition(scale, dimType, index, transposed) {
      let position = '';
      const field = scale.field;
      const axisCfg = this.axisCfg;
      if (axisCfg[field] && axisCfg[field].position) {
        position = axisCfg[field].position;
      } else if (dimType === 'x') {
        position = transposed ? 'left' : 'bottom';
      } else if (dimType === 'y') {
        position = index ? 'right' : 'left';
        if (transposed) {
          position = 'bottom';
        }
      }
  
      return position;
    }
  
    _getLineCfg(coord, dimType, position) {
      let start;
      let end;
      let factor = 1; // Mark clockwise or counterclockwise
      if (dimType === 'x') {
        start = {
          x: 0,
          y: 0
        };
        end = {
          x: 1,
          y: 0
        };
      } else {
        if (position === 'right') { // there will be several y axes
          start = {
            x: 1,
            y: 0
          };
          end = {
            x: 1,
            y: 1
          };
        } else {
          start = {
            x: 0,
            y: 0
          };
          end = {
            x: 0,
            y: 1
          };
          factor = -1;
        }
      }
      if (coord.transposed) {
        factor *= -1;
      }
  
      return {
        offsetFactor: factor,
        start: coord.convertPoint(start),
        end: coord.convertPoint(end)
      };
    }
  
    _getCircleCfg(coord) {
      return {
        startAngle: coord.startAngle,
        endAngle: coord.endAngle,
        center: coord.center,
        radius: coord.circleRadius
      };
    }
  
    _getRadiusCfg(coord) {
      const transposed = coord.transposed;
      let start;
      let end;
      if (transposed) {
        start = { x: 0, y: 0 };
        end = { x: 1, y: 0 };
      } else {
        start = { x: 0, y: 0 };
        end = { x: 0, y: 1 };
      }
      return {
        offsetFactor: -1,
        start: coord.convertPoint(start),
        end: coord.convertPoint(end)
      };
    }
  
    _getAxisCfg(coord, scale, verticalScale, dimType, defaultCfg) {
      const self = this;
      const axisCfg = this.axisCfg;
      const ticks = scale.getTicks();
  
      const cfg = deepMix({
        ticks,
        frontContainer: this.frontPlot,
        backContainer: this.backPlot
      }, defaultCfg, axisCfg[scale.field]);
  
      const labels = [];
      const label = cfg.label;
      const count = ticks.length;
      let maxWidth = 0;
      let maxHeight = 0;
      let labelCfg = label;
  
      each(ticks, (tick, index) => {
        if (isFunction(label)) {
          const executedLabel = label(tick.text, index, count);
          labelCfg = executedLabel ? mix({}, Global._defaultAxis.label, executedLabel) : null;
        }
        if (labelCfg) {
          const textStyle = {};
          if (labelCfg.textAlign) {
            textStyle.textAlign = labelCfg.textAlign;
          }
          if (labelCfg.textBaseline) {
            textStyle.textBaseline = labelCfg.textBaseline;
          }
  
          const container = labelCfg.top ? this.frontPlot : this.backPlot;
          const axisLabel = container.addShape('text', {
            className: 'axis-label',
            aria: false,
            attrs: mix({
              x: 0,
              y: 0,
              text: tick.text,
              fontFamily: self.chart.get('canvas').get('fontFamily')
            }, labelCfg),
            value: tick.value,
            textStyle,
            top: labelCfg.top,
            context: self.chart.get('canvas').get('context')
          });
          labels.push(axisLabel);
          const { width, height } = axisLabel.getBBox();
          maxWidth = Math.max(maxWidth, width);
          maxHeight = Math.max(maxHeight, height);
        }
      });
  
      cfg.labels = labels;
      cfg.maxWidth = maxWidth;
      cfg.maxHeight = maxHeight;
      return cfg;
    }
  
    _createAxis(coord, scale, verticalScale, dimType, index = '') {
      const self = this;
      const coordType = coord.type;
      const transposed = coord.transposed;
      let type;
      let key;
      let defaultCfg;
      if (coordType === 'cartesian' || coordType === 'rect') {
        const position = self._getLinePosition(scale, dimType, index, transposed);
        defaultCfg = Global.axis[position];
        defaultCfg.position = position;
        type = 'Line';
        key = position;
      } else {
        if ((dimType === 'x' && !transposed) || (dimType === 'y' && transposed)) {
          defaultCfg = Global.axis.circle;
          type = 'Circle';
          key = 'circle';
        } else {
          defaultCfg = Global.axis.radius;
          type = 'Line';
          key = 'radius';
        }
      }
      const cfg = self._getAxisCfg(coord, scale, verticalScale, dimType, defaultCfg);
      cfg.type = type;
      cfg.dimType = dimType;
      cfg.verticalScale = verticalScale;
      cfg.index = index;
      this.axes[key] = cfg;
    }
  
    createAxis(coord, xScale, yScales) {
      const self = this;
      if (xScale && !self._isHide(xScale.field)) {
        self._createAxis(coord, xScale, yScales[0], 'x');
      }
      each(yScales, function(yScale, index) {
        if (!self._isHide(yScale.field)) {
          self._createAxis(coord, yScale, xScale, 'y', index);
        }
      });
  
      const axes = this.axes;
      const chart = self.chart;
      if (chart._isAutoPadding()) {
        const userPadding = parsePadding(chart.get('padding'));
        const appendPadding = parsePadding(chart.get('appendPadding'));
        const legendRange = chart.get('legendRange') || {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0
        };
  
        const padding = [
          userPadding[0] === 'auto' ? legendRange.top + appendPadding[0] * 2 : userPadding[0],
          userPadding[1] === 'auto' ? legendRange.right + appendPadding[1] : userPadding[1],
          userPadding[2] === 'auto' ? legendRange.bottom + appendPadding[2] : userPadding[2],
          userPadding[3] === 'auto' ? legendRange.left + appendPadding[3] : userPadding[3]
        ];
  
        if (coord.isPolar) {
          const circleAxis = axes.circle;
          if (circleAxis) {
            const { maxHeight, maxWidth, labelOffset } = circleAxis;
            padding[0] += maxHeight + labelOffset;
            padding[1] += maxWidth + labelOffset;
            padding[2] += maxHeight + labelOffset;
            padding[3] += maxWidth + labelOffset;
          }
        } else {
          if (axes.right && userPadding[1] === 'auto') {
            const { maxWidth, labelOffset } = axes.right;
            padding[1] += maxWidth + labelOffset;
          }
  
          if (axes.left && userPadding[3] === 'auto') {
            const { maxWidth, labelOffset } = axes.left;
            padding[3] += maxWidth + labelOffset;
          }
  
          if (axes.bottom && userPadding[2] === 'auto') {
            const { maxHeight, labelOffset } = axes.bottom;
            padding[2] += maxHeight + labelOffset;
          }
        }
        chart.set('_padding', padding);
        chart._updateLayout(padding);
      }
  
      each(axes, axis => {
        const { type, grid, verticalScale, ticks, dimType, position, index } = axis;
        let appendCfg;
        if (coord.isPolar) {
          if (type === 'Line') {
            appendCfg = self._getRadiusCfg(coord);
          } else if (type === 'Circle') {
            appendCfg = self._getCircleCfg(coord);
          }
        } else {
          appendCfg = self._getLineCfg(coord, dimType, position);
        }
  
        if (grid && verticalScale) {
          const gridPoints = [];
          const verticalTicks = formatTicks(verticalScale.getTicks());
  
          each(ticks, tick => {
            const subPoints = [];
            each(verticalTicks, verticalTick => {
              const x = dimType === 'x' ? tick.value : verticalTick.value;
              const y = dimType === 'x' ? verticalTick.value : tick.value;
  
              if (x >= 0 && x <= 1 && y >= 0 && y <= 1) {
                const point = coord.convertPoint({
                  x,
                  y
                });
                subPoints.push(point);
              }
            });
  
            gridPoints.push({
              points: subPoints,
              _id: 'axis-' + dimType + index + '-grid-' + tick.tickValue
            });
          });
          axis.gridPoints = gridPoints;
  
          if (coord.isPolar) {
            axis.center = coord.center;
            axis.startAngle = coord.startAngle;
            axis.endAngle = coord.endAngle;
          }
        }
        appendCfg._id = 'axis-' + dimType;
        if (!isNil(index)) {
          appendCfg._id = 'axis-' + dimType + index;
        }
  
        new Axis[type](mix(axis, appendCfg));
      });
    }
  
    clear() {
      this.axes = {};
      this.frontPlot.clear();
      this.backPlot.clear();
    }
  }
  
  export default AxisController;
  