const Util = require('../util/common');
const { Group } = require('../graphic/');
const DEFAULT_CFG = {
  anchorOffset: 5, // 锚点的偏移量
  inflectionOffset: 15, // 拐点的偏移量
  sidePadding: 20, // 文本距离画布四边的距离
  lineHeight: 32, // 文本的行高
  adjustOffset: 15, // 发生调整时的偏移量
  skipOverlapLabels: false, // 是否不展示重叠的文本
  triggerOn: 'touchstart', // 点击行为触发的时间类型
  activeShape: false, // 当有图形被选中的时候，是否激活图形
  activeStyle: {
    offset: 1,
    appendRadius: 8,
    fillOpacity: 0.5
  },
  label1OffsetY: -1,
  label2OffsetY: 1
};

function getEndPoint(center, angle, r) {
  return {
    x: center.x + r * Math.cos(angle),
    y: center.y + r * Math.sin(angle)
  };
}

// 计算中间角度
function getMiddleAngle(startAngle, endAngle) {
  if (endAngle < startAngle) {
    endAngle += Math.PI * 2;
  }
  return (endAngle + startAngle) / 2;
}

// 判断两个矩形是否相交
function isOverlap(label1, label2) {
  const label1BBox = label1.getBBox();
  const label2BBox = label2.getBBox();
  return (
    (Math.max(label1BBox.minX, label2BBox.minX) <= Math.min(label1BBox.maxX, label2BBox.minX))
    &&
    (Math.max(label1BBox.minY, label2BBox.minY) <= Math.min(label1BBox.maxY, label2BBox.maxY))
  );
}

class controller {
  constructor(cfg) {
    Util.mix(this, cfg);
    const chart = this.chart;
    this.canvasDom = chart.get('canvas').get('el');
  }

  renderLabels() {
    const self = this;
    const { chart, pieLabelCfg, labelGroup } = self;

    const halves = [
      [], // left
      [] // right
    ]; // 存储左右 labels
    const geom = chart.get('geoms')[0];
    const shapes = geom.get('container').get('children');
    const { anchorOffset, inflectionOffset, label1, label2, lineHeight, skipOverlapLabels, label1OffsetY, label2OffsetY } = pieLabelCfg;
    const coord = chart.get('coord');
    const { center, circleRadius: radius } = coord;
    shapes.forEach(shape => {
      const { startAngle, endAngle } = shape._attrs.attrs;
      const middleAngle = getMiddleAngle(startAngle, endAngle);
      const anchorPoint = getEndPoint(center, middleAngle, radius + anchorOffset);
      const inflectionPoint = getEndPoint(center, middleAngle, radius + inflectionOffset);
      const origin = shape.get('origin');
      const { _origin, color } = origin;
      const label = {
        _anchor: anchorPoint,
        _inflection: inflectionPoint,
        _data: _origin,
        x: inflectionPoint.x,
        y: inflectionPoint.y,
        r: radius + inflectionOffset,
        fill: color
      };

      const textGroup = new Group({
        context: chart.get('canvas').get('context'), // 兼容 node、小程序环境
        data: _origin // 存储原始数据
      });
      const textAttrs = {
        x: 0,
        y: 0,
        fontSize: 12,
        lineHeight: 12,
        fill: '#808080'
      };
      if (Util.isFunction(label1)) {
        textGroup.addShape('Text', {
          attrs: Util.mix({
            textBaseline: 'bottom'
          }, textAttrs, label1(_origin, color)),
          data: _origin, // 存储原始数据
          offsetY: label1OffsetY
        });
      }

      if (Util.isFunction(label2)) {
        textGroup.addShape('Text', {
          attrs: Util.mix({
            textBaseline: 'top'
          }, textAttrs, label2(_origin, color)),
          data: _origin, // 存储原始数据
          offsetY: label2OffsetY
        });
      }
      label.textGroup = textGroup;
      // 判断文本的方向
      if (anchorPoint.x < center.x) {
        label._side = 'left';
        halves[0].push(label);
      } else {
        label._side = 'right';
        halves[1].push(label);
      }
    });

    let drawnLabels = [];

    if (skipOverlapLabels) {
      let lastLabel; // 存储上一个 label 对象，用于检测文本是否重叠
      const labels = halves[1].concat(halves[0]); // 顺时针
      for (let i = 0, len = labels.length; i < len; i++) {
        const label = labels[i];
        const textGroup = self._drawLabel(label);
        if (lastLabel) {
          if (isOverlap(textGroup, lastLabel)) { // 重叠了就不绘制
            continue;
          }
        }

        labelGroup.add(textGroup);
        self._drawLabelLine(label);
        lastLabel = textGroup;
        drawnLabels.push(textGroup);
      }
    } else {
      const height = chart.get('height');
      const maxCountForOneSide = parseInt(height / lineHeight, 10);

      halves.forEach(half => {
        if (half.length > maxCountForOneSide) {
          half.splice(maxCountForOneSide, half.length - maxCountForOneSide);
        }

        half.sort((a, b) => {
          return a.y - b.y;
        });

        const labels = self._antiCollision(half);
        drawnLabels = drawnLabels.concat(labels);
      });
    }

    this.drawnLabels = drawnLabels;
  }

  bindEvents() {
    const pieLabelCfg = this.pieLabelCfg;
    const triggerOn = pieLabelCfg.triggerOn || 'touchstart';
    Util.addEventListener(this.canvasDom, triggerOn, this._handleEvent);
  }

  unBindEvents() {
    const pieLabelCfg = this.pieLabelCfg;
    const triggerOn = pieLabelCfg.triggerOn || 'touchstart';
    Util.removeEventListener(this.canvasDom, triggerOn, this._handleEvent);
  }

  clear() {
    this.labelGroup && this.labelGroup.clear();
    this.halo && this.halo.remove(true);
    this.lastSelectedData = null;
    this.drawnLabels = [];
    this.unBindEvents();
  }

  _drawLabel(label) {
    const { pieLabelCfg, chart } = this;
    const canvasWidth = chart.get('width');
    const { sidePadding } = pieLabelCfg;
    const { y, textGroup } = label;

    const children = textGroup.get('children');
    const textAttrs = {
      textAlign: label._side === 'left' ? 'left' : 'right',
      x: label._side === 'left' ? sidePadding : canvasWidth - sidePadding
    };

    children.forEach(child => {
      child.attr(textAttrs);
      child.attr('y', y + child.get('offsetY'));
    });

    return textGroup;
  }

  _drawLabelLine(label, maxLabelWidth) {
    const { chart, pieLabelCfg, labelGroup } = this;
    const canvasWidth = chart.get('width');
    const { sidePadding, adjustOffset, lineStyle, anchorStyle, skipOverlapLabels } = pieLabelCfg;
    const { _anchor, _inflection, fill, y } = label;
    const lastPoint = {
      x: label._side === 'left' ? sidePadding : canvasWidth - sidePadding,
      y
    };

    let points = [
      _anchor,
      _inflection,
      lastPoint
    ];
    if (!skipOverlapLabels && _inflection.y !== y) { // 展示全部文本文本位置做过调整
      if (_inflection.y < y) { // 文本被调整下去了，则添加拐点连接线
        const point1 = _inflection;
        const point2 = {
          x: label._side === 'left' ? lastPoint.x + maxLabelWidth + adjustOffset : lastPoint.x - maxLabelWidth - adjustOffset,
          y: _inflection.y
        };
        const point3 = {
          x: label._side === 'left' ? lastPoint.x + maxLabelWidth : lastPoint.x - maxLabelWidth,
          y: lastPoint.y
        };

        points = [
          _anchor,
          point1,
          point2,
          point3,
          lastPoint
        ];

        if ((label._side === 'right' && point2.x < point1.x) || (label._side === 'left' && point2.x > point1.x)) {
          points = [
            _anchor,
            point3,
            lastPoint
          ];
        }
      } else {
        points = [
          _anchor,
          {
            x: _inflection.x,
            y
          },
          lastPoint
        ];
      }
    }

    labelGroup.addShape('Polyline', {
      attrs: Util.mix({
        points,
        lineWidth: 1,
        stroke: fill
      }, lineStyle)
    });

    // 绘制锚点
    labelGroup.addShape('Circle', {
      attrs: Util.mix({
        x: _anchor.x,
        y: _anchor.y,
        r: 2,
        fill
      }, anchorStyle)
    });
  }

  _antiCollision(half) {
    const self = this;
    const { chart, pieLabelCfg } = self;
    const coord = chart.get('coord');
    const canvasHeight = chart.get('height');
    const { center, circleRadius: r } = coord;
    const { inflectionOffset, lineHeight } = pieLabelCfg;
    const startY = center.y - r - inflectionOffset - lineHeight;
    let overlapping = true;
    let totalH = canvasHeight;
    let i;

    let maxY = 0;
    let minY = Number.MIN_VALUE;
    let maxLabelWidth = 0;
    const boxes = half.map(function(label) {
      const labelY = label.y;
      if (labelY > maxY) {
        maxY = labelY;
      }
      if (labelY < minY) {
        minY = labelY;
      }

      const textGroup = label.textGroup;
      const labelWidth = textGroup.getBBox().width;
      if (labelWidth >= maxLabelWidth) {
        maxLabelWidth = labelWidth;
      }

      return {
        size: lineHeight,
        targets: [ labelY - startY ]
      };
    });
    if ((maxY - startY) > totalH) {
      totalH = maxY - startY;
    }

    const iteratorBoxed = function(boxes) {
      boxes.forEach(box => {
        const target = (Math.min.apply(minY, box.targets) + Math.max.apply(minY, box.targets)) / 2;
        box.pos = Math.min(Math.max(minY, target - box.size / 2), totalH - box.size);
      });
    };

    while (overlapping) {
      iteratorBoxed(boxes);
      // detect overlapping and join boxes
      overlapping = false;
      i = boxes.length;
      while (i--) {
        if (i > 0) {
          const previousBox = boxes[i - 1];
          const box = boxes[i];
          if (previousBox.pos + previousBox.size > box.pos) { // overlapping
            previousBox.size += box.size;
            previousBox.targets = previousBox.targets.concat(box.targets);

            // overflow, shift up
            if (previousBox.pos + previousBox.size > totalH) {
              previousBox.pos = totalH - previousBox.size;
            }
            boxes.splice(i, 1); // removing box
            overlapping = true;
          }
        }
      }
    }

    i = 0;
    boxes.forEach(function(b) {
      let posInCompositeBox = startY; // middle of the label
      b.targets.forEach(function() {
        half[i].y = b.pos + posInCompositeBox + lineHeight / 2;
        posInCompositeBox += lineHeight;
        i++;
      });
    });

    const drawnLabels = [];
    half.forEach(function(label) {
      const textGroup = self._drawLabel(label);
      const labelGroup = self.labelGroup;
      labelGroup.add(textGroup);
      self._drawLabelLine(label, maxLabelWidth);
      drawnLabels.push(textGroup);
    });

    return drawnLabels;
  }

  _handleEvent = ev => {
    const self = this;
    const { chart, drawnLabels, pieLabelCfg } = self;
    const { onClick, activeShape } = pieLabelCfg;
    const canvasEvent = Util.createEvent(ev, chart);
    const { x, y } = canvasEvent;

    // 查找被点击的 label
    let clickedShape;
    for (let i = 0, len = drawnLabels.length; i < len; i++) {
      const shape = drawnLabels[i];
      const bbox = shape.getBBox();
      // 通过最小包围盒来判断击中情况
      if (x >= bbox.minX && x <= bbox.maxX && y >= bbox.minY && y <= bbox.maxY) {
        clickedShape = shape;
        break;
      }
    }

    const pieData = chart.getSnapRecords({ x, y });
    if (clickedShape) {
      canvasEvent.data = clickedShape.get('data');
    } else if (pieData.length) { // 击中饼图扇形区域
      canvasEvent.data = pieData[0]._origin;
    }

    onClick && onClick(canvasEvent);
    canvasEvent.data && activeShape && this._activeShape(canvasEvent.data);
  }

  _getSelectedShapeByData(data) {
    let selectedShape = null;
    const chart = this.chart;
    const geom = chart.get('geoms')[0];
    const container = geom.get('container');
    const children = container.get('children');
    Util.each(children, child => {
      if (child.get('isShape') && (child.get('className') === geom.get('type'))) { // get geometry's shape
        const shapeData = child.get('origin')._origin;
        if (Util.isObjectValueEqual(shapeData, data)) {
          selectedShape = child;
          return false;
        }
      }
    });
    return selectedShape;
  }

  _activeShape(data) {
    const { chart, lastSelectedData, pieLabelCfg } = this;
    if (data === lastSelectedData) {
      return;
    }
    this.lastSelectedData = data;
    const activeStyle = pieLabelCfg.activeStyle;
    const selectedShape = this._getSelectedShapeByData(data);
    const { x, y, startAngle, endAngle, r, fill } = selectedShape._attrs.attrs;
    const frontPlot = chart.get('frontPlot');
    this.halo && this.halo.remove(true);
    const halo = frontPlot.addShape('sector', {
      attrs: Util.mix({
        x,
        y,
        r: r + activeStyle.offset + activeStyle.appendRadius,
        r0: r + activeStyle.offset,
        fill,
        startAngle,
        endAngle
      }, activeStyle)
    });
    this.halo = halo;
    chart.get('canvas').draw();
  }
}


module.exports = {
  init(chart) {
    const frontPlot = chart.get('frontPlot');
    const labelGroup = frontPlot.addGroup({
      className: 'pie-label',
      zIndex: 0
    });
    const pieLabelController = new controller({
      chart,
      labelGroup
    });
    chart.set('pieLabelController', pieLabelController);
    chart.pieLabel = function(cfg) {
      cfg = Util.deepMix({}, DEFAULT_CFG, cfg);
      pieLabelController.pieLabelCfg = cfg;

      return this;
    };

  },
  afterGeomDraw(chart) {
    const controller = chart.get('pieLabelController');
    if (controller.pieLabelCfg) { // 用户配置了饼图文本
      controller.renderLabels();
      controller.bindEvents(); // 绑定事件
    }
  },
  clearInner(chart) {
    const controller = chart.get('pieLabelController');
    if (controller.pieLabelCfg) { // 用户配置了饼图文本
      controller.clear();
    }
  }
};
