/**
 * @fileOverview interval shapes
 * @author dxq613@gmail.com
 * @author sima.zhang1990@gmail.com
 * @author huangtonger@aliyun.com
 */

const Util = require('../../util');
const Shape = require('./shape');
const PathUtil = require('../util/path');
const ShapeUtil = require('../util/shape');
const Global = require('../../global');
const G = require('../../renderer');

const GPathUtil = Util.PathUtil;

// 获取柱状图的几个点
function getRectPoints(cfg, isPyramid) {
  const x = cfg.x;
  const y = cfg.y;
  const y0 = cfg.y0; // 0 点的位置
  const width = cfg.size;
  // 有3种情况，
  // 1. y，x都不是数组
  // 2. y是数组，x不是
  // 3. x是数组，y不是
  let ymin = y0;
  let ymax = y;
  if (Util.isArray(y)) {
    ymax = y[1];
    ymin = y[0];
  }

  let xmin;
  let xmax;
  if (Util.isArray(x)) {
    xmin = x[0];
    xmax = x[1];
  } else {
    xmin = x - width / 2;
    xmax = x + width / 2;
  }

  const points = [];
  points.push({
    x: xmin,
    y: ymin
  }, {
    x: xmin,
    y: ymax
  });

  if (isPyramid) {
    points.push({
      x: xmax,
      y: (ymax + ymin) / 2
    });
  } else {
    points.push({
      x: xmax,
      y: ymax
    }, {
      x: xmax,
      y: ymin
    });
  }

  return points;
}

function getRectPath(points) {
  const path = [];
  for (let i = 0; i < points.length; i++) {
    const point = points[i];
    if (point) {
      const action = i === 0 ? 'M' : 'L';
      path.push([ action, point.x, point.y ]);
    }
  }
  const first = points[0];
  path.push([ 'L', first.x, first.y ]);
  path.push([ 'z' ]);
  return path;
}

function getLinePoints(cfg) {
  const x = cfg.x;
  const y = cfg.y;
  const y0 = cfg.y0; // 0 点的位置
  const points = [];

  if (Util.isArray(y)) {
    Util.each(y, (yItem, idx) => {
      points.push({
        x: Util.isArray(x) ? x[idx] : x,
        y: yItem
      });
    });
  } else {
    points.push({
      x,
      y
    }, {
      x,
      y: y0
    });
  }

  return points;
}

function getTickPoints(cfg) {
  const x = cfg.x;
  const y = Util.isArray(cfg.y) ? cfg.y[1] : cfg.y;
  const y0 = Util.isArray(cfg.y) ? cfg.y[0] : cfg.y0;

  const barWidth = cfg.size;
  const points = [];

  points.push({
    x: x - barWidth / 2,
    y
  }, {
    x: x + barWidth / 2,
    y
  }, {
    x,
    y
  }, {
    x,
    y: y0
  }, {
    x: x - barWidth / 2,
    y: y0
  }, {
    x: x + barWidth / 2,
    y: y0
  });

  return points;
}

function getTickPath(points) {
  const path = [];
  path.push(
    [ 'M', points[0].x, points[0].y ],
    [ 'L', points[1].x, points[1].y ],
    [ 'M', points[2].x, points[2].y ],
    [ 'L', points[3].x, points[3].y ],
    [ 'M', points[4].x, points[4].y ],
    [ 'L', points[5].x, points[5].y ]
  );
  return path;
}

function getFillAttrs(cfg) {
  const defaultAttrs = Global.shape.interval;
  const attrs = Util.mix({}, defaultAttrs, cfg.style);
  ShapeUtil.addFillAttrs(attrs, cfg);
  if (cfg.color) {
    attrs.stroke = attrs.stroke || cfg.color;
  }
  return attrs;
}

function getLineAttrs(cfg) {
  const defaultAttrs = Global.shape.hollowInterval;
  const attrs = Util.mix({}, defaultAttrs, cfg.style);
  ShapeUtil.addStrokeAttrs(attrs, cfg);
  return attrs;
}

function getFunnelPath(cfg, isFunnel) {
  const path = [];
  const points = cfg.points;
  const nextPoints = cfg.nextPoints;
  if (!Util.isNil(nextPoints)) {
    path.push(
      [ 'M', points[0].x, points[0].y ],
      [ 'L', points[1].x, points[1].y ],
      [ 'L', nextPoints[1].x, nextPoints[1].y ],
      [ 'L', nextPoints[0].x, nextPoints[0].y ],
      [ 'Z' ]
    );
  } else if (isFunnel) {
    path.push(
      [ 'M', points[0].x, points[0].y ],
      [ 'L', points[1].x, points[1].y ],
      [ 'L', points[2].x, points[2].y ],
      [ 'L', points[3].x, points[3].y ],
      [ 'Z' ]
    );
  } else {
    path.push(
      [ 'M', points[0].x, points[0].y ],
      [ 'L', points[1].x, points[1].y ],
      [ 'L', points[2].x, points[2].y ],
      [ 'L', points[2].x, points[2].y ],
      [ 'Z' ]
    );
  }

  return path;
}

function getThetaCfg(point, coord) {
  const r = coord.getRadius();
  const inner = coord.innerRadius;
  let startAngle;
  let endAngle;
  const ir = r * inner;
  let startPoint;
  let endPoint;

  if (!Util.isArray(point.x) && Util.isArray(point.y)) {
    point.x = [ point.x, point.x ]; // 如果x是一个值，y是数组，将x转成数组
  }
  if (Util.isArray(point.x)) {
    startPoint = {
      x: point.x[0],
      y: point.y[0]
    };
    endPoint = {
      x: point.x[1],
      y: point.y[1]
    };
    startAngle = PathUtil.getPointAngle(coord, startPoint);
    endAngle = PathUtil.getPointAngle(coord, endPoint);
    if (endAngle <= startAngle) { // 考虑占比百分百的情形
      endAngle = endAngle + Math.PI * 2;
    }
  } else {
    endPoint = point;
    startAngle = coord.startAngle;
    endAngle = PathUtil.getPointAngle(coord, endPoint);
  }
  return {
    r,
    ir,
    startAngle,
    endAngle
  };
}

// 获取选中时的样式，当前仅支持饼图
function getSelectedCfg(type, cfg) {
  const geom = cfg.geom;
  const coord = geom.get('coord');
  const point = cfg.point;
  const r = 7.5;
  let selectedCfg;
  if (coord && coord.type === 'theta') {
    const thetaCfg = getThetaCfg(point, coord);
    const middleAngle = (thetaCfg.endAngle - thetaCfg.startAngle) / 2 + thetaCfg.startAngle;
    const x = r * Math.cos(middleAngle);
    const y = r * Math.sin(middleAngle);
    selectedCfg = {
      transform: [
        [ 't', x, y ]
      ]
    };
  }
  return Util.mix({}, selectedCfg);
}

const Interval = Shape.registerFactory('interval', {
  defaultShapeType: 'rect',
  getActiveCfg(type, cfg) {
    if (!type || Util.inArray([ 'rect', 'funnel', 'pyramid' ], type)) { // 透明度降低 0.15
      const fillOpacity = cfg.fillOpacity || cfg.opacity || 1;
      return {
        fillOpacity: fillOpacity - 0.15
      };
    }
    const lineWidth = cfg.lineWidth || 0;
    return {
      lineWidth: lineWidth + 1
    };
  },
  getDefaultPoints(pointInfo) {
    return getRectPoints(pointInfo);
  },
  getSelectedCfg(type, cfg) {
    return getSelectedCfg(type, cfg);
  }
});

// 默认柱状图
Shape.registerShape('interval', 'rect', {
  draw(cfg, container) {
    const attrs = getFillAttrs(cfg);
    let path = getRectPath(cfg.points);
    path = this.parsePath(path);
    return container.addShape('path', {
      attrs: Util.mix(attrs, {
        path
      })
    });
  },
  getMarkerCfg(cfg) {
    const rectCfg = getFillAttrs(cfg);
    const isInCircle = cfg.isInCircle;
    return Util.mix({
      symbol: isInCircle ? 'circle' : 'square',
      radius: isInCircle ? 4.5 : 4
    }, rectCfg);
  }
});

// 空心柱状图
Shape.registerShape('interval', 'hollowRect', {
  draw(cfg, container) {
    const attrs = getLineAttrs(cfg);
    let path = getRectPath(cfg.points);
    path = this.parsePath(path);
    return container.addShape('path', {
      attrs: Util.mix(attrs, {
        path
      })
    });
  },
  getMarkerCfg(cfg) {
    const rectCfg = getLineAttrs(cfg);
    const isInCircle = cfg.isInCircle;
    return Util.mix({
      symbol: isInCircle ? 'circle' : 'square',
      radius: isInCircle ? 4.5 : 4
    }, rectCfg);
  }
});

// 线形柱状图
Shape.registerShape('interval', 'line', {
  getPoints(pointInfo) {
    return getLinePoints(pointInfo);
  },
  draw(cfg, container) {
    const attrs = getLineAttrs(cfg);
    attrs.lineWidth = cfg.size || 1; // size 就是线的宽度
    let path = getRectPath(cfg.points);
    path = this.parsePath(path);
    return container.addShape('path', {
      attrs: Util.mix(attrs, {
        path
      })
    });
  },
  getMarkerCfg(cfg) {
    const lineCfg = getLineAttrs(cfg);
    return Util.mix({
      symbol: 'line',
      radius: 5
    }, lineCfg);
  }
});

// 钉子形的柱状图
Shape.registerShape('interval', 'tick', {
  getPoints(pointInfo) {
    return getTickPoints(pointInfo);
  },
  draw(cfg, container) {
    const attrs = getLineAttrs(cfg);
    // @2018-12-25 by blue.lb 经过测试发现size代表的是宽度，而style中的lineWidth才是设置线宽，放在interval暂时先特殊处理
    if (!attrs.lineWidth) {
      attrs.lineWidth = 2;
    }
    let path = getTickPath(cfg.points);
    path = this.parsePath(path);
    return container.addShape('path', {
      attrs: Util.mix(attrs, {
        path
      })
    });
  },
  getMarkerCfg(cfg) {
    const lineCfg = getLineAttrs(cfg);
    return Util.mix({
      symbol: 'tick',
      radius: 5
    }, lineCfg);
  }
});

// 漏斗图
Shape.registerShape('interval', 'funnel', {
  getPoints(pointInfo) {
    pointInfo.size = pointInfo.size * 2; // 漏斗图的 size 是柱状图的两倍
    return getRectPoints(pointInfo);
  },
  draw(cfg, container) {
    const attrs = getFillAttrs(cfg);
    let path = getFunnelPath(cfg, true);
    path = this.parsePath(path);
    return container.addShape('path', {
      attrs: Util.mix(attrs, {
        path
      })
    });
  },
  getMarkerCfg(cfg) {
    const funnelCfg = getFillAttrs(cfg);
    return Util.mix({
      symbol: 'square',
      radius: 4
    }, funnelCfg);
  }
});

// 金字塔图
Shape.registerShape('interval', 'pyramid', {
  getPoints(pointInfo) {
    pointInfo.size = pointInfo.size * 2; // 漏斗图的 size 是柱状图的两倍
    return getRectPoints(pointInfo, true);
  },
  draw(cfg, container) {
    const attrs = getFillAttrs(cfg);
    let path = getFunnelPath(cfg, false);
    path = this.parsePath(path);
    return container.addShape('path', {
      attrs: Util.mix(attrs, {
        path
      })
    });
  },
  getMarkerCfg(cfg) {
    const funnelCfg = getFillAttrs(cfg);
    return Util.mix({
      symbol: 'square',
      radius: 4
    }, funnelCfg);
  }
});

// 水波图
/**
 * 用贝塞尔曲线模拟正弦波
 * Using Bezier curves to fit sine wave.
 * There is 4 control points for each curve of wave,
 * which is at 1/4 wave length of the sine wave.
 *
 * The control points for a wave from (a) to (d) are a-b-c-d:
 *          c *----* d
 *     b *
 *       |
 * ... a * ..................
 *
 * whose positions are a: (0, 0), b: (0.5, 0.5), c: (1, 1), d: (PI / 2, 1)
 *
 * @param {number} x          x position of the left-most point (a)
 * @param {number} stage      0-3, stating which part of the wave it is
 * @param {number} waveLength wave length of the sine wave
 * @param {number} amplitude  wave amplitude
 * @return {Array} 正弦片段曲线
 */
function getWaterWavePositions(x, stage, waveLength, amplitude) {
  if (stage === 0) {
    return [
      [ x + 1 / 2 * waveLength / Math.PI / 2, amplitude / 2 ],
      [ x + 1 / 2 * waveLength / Math.PI, amplitude ],
      [ x + waveLength / 4, amplitude ]
    ];
  } else if (stage === 1) {
    return [
      [ x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 2),
        amplitude
      ],
      [ x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 1),
        amplitude / 2
      ],
      [ x + waveLength / 4, 0 ]
    ];
  } else if (stage === 2) {
    return [
      [ x + 1 / 2 * waveLength / Math.PI / 2, -amplitude / 2 ],
      [ x + 1 / 2 * waveLength / Math.PI, -amplitude ],
      [ x + waveLength / 4, -amplitude ]
    ];
  }
  return [
    [ x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 2), -amplitude ],
    [ x + 1 / 2 * waveLength / Math.PI / 2 * (Math.PI - 1), -amplitude / 2 ],
    [ x + waveLength / 4, 0 ]
  ];
}
/**
 * 获取水波路径
 * @param  {number} radius          半径
 * @param  {number} waterLevel      水位
 * @param  {number} waveLength      波长
 * @param  {number} phase           相位
 * @param  {number} amplitude       震幅
 * @param  {number} cx              圆心x
 * @param  {number} cy              圆心y
 * @return {Array}  path            路径
 * @reference http://gitlab.alipay-inc.com/datavis/g6/blob/1.2.0/src/graph/utils/path.js#L135
 */
function getWaterWavePath(radius, waterLevel, waveLength, phase, amplitude, cx, cy) {
  const curves = Math.ceil(2 * radius / waveLength * 4) * 2;
  const path = [];

  // map phase to [-Math.PI * 2, 0]
  while (phase < -Math.PI * 2) {
    phase += Math.PI * 2;
  }
  while (phase > 0) {
    phase -= Math.PI * 2;
  }
  phase = phase / Math.PI / 2 * waveLength;

  const left = cx - radius + phase - radius * 2;
  /**
   * top-left corner as start point
   *
   * draws this point
   *  |
   * \|/
   *  ~~~~~~~~
   *  |      |
   *  +------+
   */
  path.push([ 'M', left, waterLevel ]);

  /**
   * top wave
   *
   * ~~~~~~~~ <- draws this sine wave
   * |      |
   * +------+
   */
  let waveRight = 0;
  for (let c = 0; c < curves; ++c) {
    const stage = c % 4;
    const pos = getWaterWavePositions(c * waveLength / 4, stage, waveLength, amplitude);
    path.push([
      'C',
      pos[0][0] + left, -pos[0][1] + waterLevel,
      pos[1][0] + left, -pos[1][1] + waterLevel,
      pos[2][0] + left, -pos[2][1] + waterLevel
    ]);

    if (c === curves - 1) {
      waveRight = pos[2][0];
    }
  }

  /**
   * top-right corner
   *
   *                       ~~~~~~~~
   * 3. draws this line -> |      | <- 1. draws this line
   *                       +------+
   *                          ^
   *                          |
   *                  2. draws this line
   */
  path.push([ 'L', waveRight + left, cy + radius ]);
  path.push([ 'L', left, cy + radius ]);
  path.push([ 'L', left, waterLevel ]);
  return path;
}

/**
 * 添加水波
 * @param {number} x           中心x
 * @param {number} y           中心y
 * @param {number} level       水位等级 0～1
 * @param {number} waveCount   水波数
 * @param {number} colors      色值
 * @param {number} group       图组
 * @param {number} clip        用于剪切的图形
 * @param {number} radius      绘制图形的高度
 */
function addWaterWave(x, y, level, waveCount, colors, group, clip, radius) {
  const bbox = clip.getBBox();
  const width = bbox.maxX - bbox.minX;
  const height = bbox.maxY - bbox.minY;
  const duration = 5000;
  const delayDiff = 300;
  for (let i = 0; i < waveCount; i++) {
    const wave = group.addShape('path', {
      attrs: {
        path: getWaterWavePath(
          radius,
          bbox.minY + height * level,
          width / 4, 0, width / 64, x, y
        ),
        fill: colors[i],
        clip
      }
    });
    // FIXME wave animation error in svg
    if (Global.renderer === 'canvas') {
      wave.animate({
        transform: [
          [ 't', width / 2, 0 ]
        ],
        repeat: true
      }, duration - i * delayDiff);
    }
  }
}

Shape.registerShape('interval', 'liquid-fill-gauge', {
  draw(cfg, container) {
    const self = this;
    const cy = 0.5;
    let sumX = 0;
    let minX = Infinity;
    Util.each(cfg.points, p => {
      if (p.x < minX) {
        minX = p.x;
      }
      sumX += p.x;
    });
    const cx = sumX / cfg.points.length;
    const cp = self.parsePoint({ x: cx, y: cy });
    const minP = self.parsePoint({ x: minX, y: 0.5 });
    const xWidth = cp.x - minP.x;
    const radius = Math.min(xWidth, minP.y);
    const attrs = getFillAttrs(cfg);
    const clipCircle = new G.Circle({
      attrs: {
        x: cp.x,
        y: cp.y,
        r: radius
      }
    });
    addWaterWave(
      cp.x, cp.y,
      cfg.y / (2 * cp.y),
      1,
      [ attrs.fill ],
      container,
      clipCircle,
      radius * 4
    );
    return container.addShape('circle', {
      attrs: Util.mix(getLineAttrs(cfg), {
        x: cp.x,
        y: cp.y,
        r: radius + radius / 8
      })
    });
  }
});

const pathMetaCache = {};
Shape.registerShape('interval', 'liquid-fill-path', {
  draw(cfg, container) {
    const self = this;
    const attrs = Util.mix({}, getFillAttrs(cfg));
    const path = cfg.shape[1];

    const cy = 0.5;
    let sumX = 0;
    let minX = Infinity;
    Util.each(cfg.points, p => {
      if (p.x < minX) {
        minX = p.x;
      }
      sumX += p.x;
    });
    const cx = sumX / cfg.points.length;
    const cp = self.parsePoint({ x: cx, y: cy });
    const minP = self.parsePoint({ x: minX, y: 0.5 });
    const xWidth = cp.x - minP.x;
    const radius = Math.min(xWidth, minP.y);

    let pathMeta;
    if (pathMetaCache[path]) {
      pathMeta = pathMetaCache[path];
    } else {
      const segments = GPathUtil.parsePathString(path);
      pathMetaCache[path] = pathMeta = {
        segments
      };
    }
    const transform = [];
    if (attrs.rotate) {
      transform.push([ 'r', attrs.rotate / 180 * Math.PI ]);
      delete attrs.rotate;
    }
    const shape = container.addShape('path', {
      attrs: Util.mix(attrs, {
        fillOpacity: 0,
        path: pathMeta.segments
      })
    });
    const bbox = Util.cloneDeep(shape.getBBox());
    const rangeX = bbox.maxX - bbox.minX;
    const rangeY = bbox.maxY - bbox.minY;
    const range = Math.max(rangeX, rangeY);
    const scale = radius * 2 / range;
    shape.transform(
      transform.concat([
        [ 's', scale, scale ]
      ])
    );
    const dw = scale * rangeX / 2; // (bbox.maxX - bbox.minX) / 2;
    const dh = scale * rangeY / 2; // (bbox.maxY - bbox.minY) / 2;
    shape.transform([
      [ 't', cp.x - dw, cp.y - dh ]
    ]);
    addWaterWave(
      cp.x, cp.y,
      cfg.y / (2 * cp.y),
      1,
      [ attrs.fill ],
      container,
      shape,
      minP.y * 4
    );

    const keyShape = container.addShape('path', {
      attrs: Util.mix(getLineAttrs(cfg), {
        path: pathMeta.segments
      })
    });
    keyShape.transform(
      transform.concat([
        [ 's', scale, scale ],
        [ 't', cp.x - dw, cp.y - dh ]
      ])
    );
    return keyShape;
  }
});

Shape.registerShape('interval', 'top-line', {
  draw(cfg, container) {
    const attrs = getFillAttrs(cfg);
    const style = cfg.style || {};
    const linePath = [
      [ 'M', cfg.points[1].x, cfg.points[1].y ],
      [ 'L', cfg.points[2].x, cfg.points[2].y ]
    ];
    const lineAttrs = {
      stroke: style.stroke || 'white',
      lineWidth: style.lineWidth || 1,
      path: this.parsePath(linePath)
    };
    let path = getRectPath(cfg.points);
    path = this.parsePath(path);
    delete attrs.stroke; // 不在柱子上绘制线
    const rectShape = container.addShape('path', {
      attrs: Util.mix(attrs, {
        zIndex: 0,
        path
      })
    });
    container.addShape('path', {
      zIndex: 1,
      attrs: lineAttrs
    });
    return rectShape;
  },
  getMarkerCfg(cfg) {
    const rectCfg = getFillAttrs(cfg);
    const isInCircle = cfg.isInCircle;
    return Util.mix({
      symbol: isInCircle ? 'circle' : 'square',
      radius: isInCircle ? 4.5 : 4
    }, rectCfg);
  }
});

module.exports = Interval;
