/**
 * SVG Path
 * @module zrender/shape/Path
 * @see http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathData
 * @author: Pissang (shenyi.914@gmail.com)
 */

/**
 * @typedef {Object} IPathStyle
 * @property {string} path path描述数据, 详见 {@link http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathData}
 * @property {number} x x轴位移
 * @property {number} y y轴位移
 * @property {string} [brushType='fill']
 * @property {string} [color='#000000'] 填充颜色
 * @property {string} [strokeColor='#000000'] 描边颜色
 * @property {string} [lineCape='butt'] 线帽样式，可以是 butt, round, square
 * @property {number} [lineWidth=1] 描边宽度
 * @property {number} [opacity=1] 绘制透明度
 * @property {number} [shadowBlur=0] 阴影模糊度，大于0有效
 * @property {string} [shadowColor='#000000'] 阴影颜色
 * @property {number} [shadowOffsetX=0] 阴影横向偏移
 * @property {number} [shadowOffsetY=0] 阴影纵向偏移
 * @property {string} [text] 图形中的附加文本
 * @property {string} [textColor='#000000'] 文本颜色
 * @property {string} [textFont] 附加文本样式，eg:'bold 18px verdana'
 * @property {string} [textPosition='end'] 附加文本位置, 可以是 inside, left, right, top, bottom
 * @property {string} [textAlign] 默认根据textPosition自动设置，附加文本水平对齐。
 *                                可以是start, end, left, right, center
 * @property {string} [textBaseline] 默认根据textPosition自动设置，附加文本垂直对齐。
 *                                可以是top, bottom, middle, alphabetic, hanging, ideographic
 */
module.exports = function (require) {
  var Base = require('./Base');

  var PathProxy = require('./util/PathProxy');

  var PathSegment = PathProxy.PathSegment;

  var vMag = function (v) {
    return Math.sqrt(v[0] * v[0] + v[1] * v[1]);
  };

  var vRatio = function (u, v) {
    return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v));
  };

  var vAngle = function (u, v) {
    return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v));
  };
  /**
   * @alias module:zrender/shape/Path
   * @constructor
   * @extends module:zrender/shape/Base
   * @param {Object} options
   */


  var Path = function (options) {
    Base.call(this, options);
    /**
     * Path绘制样式
     * @name module:zrender/shape/Path#style
     * @type {module:zrender/shape/Path~IPathStyle}
     */

    /**
     * Path高亮绘制样式
     * @name module:zrender/shape/Path#highlightStyle
     * @type {module:zrender/shape/Path~IPathStyle}
     */
  };

  Path.prototype = {
    type: 'path',
    buildPathArray: function (data, x, y) {
      if (!data) {
        return [];
      } // 平移


      x = x || 0;
      y = y || 0; // command string

      var cs = data; // command chars

      var cc = ['m', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A'];
      cs = cs.replace(/-/g, ' -');
      cs = cs.replace(/  /g, ' ');
      cs = cs.replace(/ /g, ',');
      cs = cs.replace(/,,/g, ',');
      var n; // create pipes so that we can split the data

      for (n = 0; n < cc.length; n++) {
        cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]);
      } // create array


      var arr = cs.split('|');
      var ca = []; // init context point

      var cpx = 0;
      var cpy = 0;

      for (n = 1; n < arr.length; n++) {
        var str = arr[n];
        var c = str.charAt(0);
        str = str.slice(1);
        str = str.replace(new RegExp('e,-', 'g'), 'e-');
        var p = str.split(',');

        if (p.length > 0 && p[0] === '') {
          p.shift();
        }

        for (var i = 0; i < p.length; i++) {
          p[i] = parseFloat(p[i]);
        }

        while (p.length > 0) {
          if (isNaN(p[0])) {
            break;
          }

          var cmd = null;
          var points = [];
          var ctlPtx;
          var ctlPty;
          var prevCmd;
          var rx;
          var ry;
          var psi;
          var fa;
          var fs;
          var x1 = cpx;
          var y1 = cpy; // convert l, H, h, V, and v to L

          switch (c) {
            case 'l':
              cpx += p.shift();
              cpy += p.shift();
              cmd = 'L';
              points.push(cpx, cpy);
              break;

            case 'L':
              cpx = p.shift();
              cpy = p.shift();
              points.push(cpx, cpy);
              break;

            case 'm':
              cpx += p.shift();
              cpy += p.shift();
              cmd = 'M';
              points.push(cpx, cpy);
              c = 'l';
              break;

            case 'M':
              cpx = p.shift();
              cpy = p.shift();
              cmd = 'M';
              points.push(cpx, cpy);
              c = 'L';
              break;

            case 'h':
              cpx += p.shift();
              cmd = 'L';
              points.push(cpx, cpy);
              break;

            case 'H':
              cpx = p.shift();
              cmd = 'L';
              points.push(cpx, cpy);
              break;

            case 'v':
              cpy += p.shift();
              cmd = 'L';
              points.push(cpx, cpy);
              break;

            case 'V':
              cpy = p.shift();
              cmd = 'L';
              points.push(cpx, cpy);
              break;

            case 'C':
              points.push(p.shift(), p.shift(), p.shift(), p.shift());
              cpx = p.shift();
              cpy = p.shift();
              points.push(cpx, cpy);
              break;

            case 'c':
              points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift());
              cpx += p.shift();
              cpy += p.shift();
              cmd = 'C';
              points.push(cpx, cpy);
              break;

            case 'S':
              ctlPtx = cpx;
              ctlPty = cpy;
              prevCmd = ca[ca.length - 1];

              if (prevCmd.command === 'C') {
                ctlPtx = cpx + (cpx - prevCmd.points[2]);
                ctlPty = cpy + (cpy - prevCmd.points[3]);
              }

              points.push(ctlPtx, ctlPty, p.shift(), p.shift());
              cpx = p.shift();
              cpy = p.shift();
              cmd = 'C';
              points.push(cpx, cpy);
              break;

            case 's':
              ctlPtx = cpx, ctlPty = cpy;
              prevCmd = ca[ca.length - 1];

              if (prevCmd.command === 'C') {
                ctlPtx = cpx + (cpx - prevCmd.points[2]);
                ctlPty = cpy + (cpy - prevCmd.points[3]);
              }

              points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift());
              cpx += p.shift();
              cpy += p.shift();
              cmd = 'C';
              points.push(cpx, cpy);
              break;

            case 'Q':
              points.push(p.shift(), p.shift());
              cpx = p.shift();
              cpy = p.shift();
              points.push(cpx, cpy);
              break;

            case 'q':
              points.push(cpx + p.shift(), cpy + p.shift());
              cpx += p.shift();
              cpy += p.shift();
              cmd = 'Q';
              points.push(cpx, cpy);
              break;

            case 'T':
              ctlPtx = cpx, ctlPty = cpy;
              prevCmd = ca[ca.length - 1];

              if (prevCmd.command === 'Q') {
                ctlPtx = cpx + (cpx - prevCmd.points[0]);
                ctlPty = cpy + (cpy - prevCmd.points[1]);
              }

              cpx = p.shift();
              cpy = p.shift();
              cmd = 'Q';
              points.push(ctlPtx, ctlPty, cpx, cpy);
              break;

            case 't':
              ctlPtx = cpx, ctlPty = cpy;
              prevCmd = ca[ca.length - 1];

              if (prevCmd.command === 'Q') {
                ctlPtx = cpx + (cpx - prevCmd.points[0]);
                ctlPty = cpy + (cpy - prevCmd.points[1]);
              }

              cpx += p.shift();
              cpy += p.shift();
              cmd = 'Q';
              points.push(ctlPtx, ctlPty, cpx, cpy);
              break;

            case 'A':
              rx = p.shift();
              ry = p.shift();
              psi = p.shift();
              fa = p.shift();
              fs = p.shift();
              x1 = cpx, y1 = cpy;
              cpx = p.shift(), cpy = p.shift();
              cmd = 'A';
              points = this._convertPoint(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
              break;

            case 'a':
              rx = p.shift();
              ry = p.shift();
              psi = p.shift();
              fa = p.shift();
              fs = p.shift();
              x1 = cpx, y1 = cpy;
              cpx += p.shift();
              cpy += p.shift();
              cmd = 'A';
              points = this._convertPoint(x1, y1, cpx, cpy, fa, fs, rx, ry, psi);
              break;
          } // 平移变换


          for (var j = 0, l = points.length; j < l; j += 2) {
            points[j] += x;
            points[j + 1] += y;
          }

          ca.push(new PathSegment(cmd || c, points));
        }

        if (c === 'z' || c === 'Z') {
          ca.push(new PathSegment('z', []));
        }
      }

      return ca;
    },
    _convertPoint: function (x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) {
      var psi = psiDeg * (Math.PI / 180.0);
      var xp = Math.cos(psi) * (x1 - x2) / 2.0 + Math.sin(psi) * (y1 - y2) / 2.0;
      var yp = -1 * Math.sin(psi) * (x1 - x2) / 2.0 + Math.cos(psi) * (y1 - y2) / 2.0;
      var lambda = xp * xp / (rx * rx) + yp * yp / (ry * ry);

      if (lambda > 1) {
        rx *= Math.sqrt(lambda);
        ry *= Math.sqrt(lambda);
      }

      var f = Math.sqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) / (rx * rx * (yp * yp) + ry * ry * (xp * xp)));

      if (fa === fs) {
        f *= -1;
      }

      if (isNaN(f)) {
        f = 0;
      }

      var cxp = f * rx * yp / ry;
      var cyp = f * -ry * xp / rx;
      var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp;
      var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp;
      var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]);
      var u = [(xp - cxp) / rx, (yp - cyp) / ry];
      var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry];
      var dTheta = vAngle(u, v);

      if (vRatio(u, v) <= -1) {
        dTheta = Math.PI;
      }

      if (vRatio(u, v) >= 1) {
        dTheta = 0;
      }

      if (fs === 0 && dTheta > 0) {
        dTheta = dTheta - 2 * Math.PI;
      }

      if (fs === 1 && dTheta < 0) {
        dTheta = dTheta + 2 * Math.PI;
      }

      return [cx, cy, rx, ry, theta, dTheta, psi, fs];
    },

    /**
     * 创建路径
     * @param {CanvasRenderingContext2D} ctx
     * @param {module:zrender/shape/Path~IPathStyle} style
     */
    buildPath: function (ctx, style) {
      var path = style.path; // 平移坐标

      var x = style.x || 0;
      var y = style.y || 0;
      style.pathArray = style.pathArray || this.buildPathArray(path, x, y);
      var pathArray = style.pathArray; // 记录边界点，用于判断inside

      var pointList = style.pointList = [];
      var singlePointList = [];

      for (var i = 0, l = pathArray.length; i < l; i++) {
        if (pathArray[i].command.toUpperCase() == 'M') {
          singlePointList.length > 0 && pointList.push(singlePointList);
          singlePointList = [];
        }

        var p = pathArray[i].points;

        for (var j = 0, k = p.length; j < k; j += 2) {
          singlePointList.push([p[j], p[j + 1]]);
        }
      }

      singlePointList.length > 0 && pointList.push(singlePointList);

      for (var i = 0, l = pathArray.length; i < l; i++) {
        var c = pathArray[i].command;
        var p = pathArray[i].points;

        switch (c) {
          case 'L':
            ctx.lineTo(p[0], p[1]);
            break;

          case 'M':
            ctx.moveTo(p[0], p[1]);
            break;

          case 'C':
            ctx.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]);
            break;

          case 'Q':
            ctx.quadraticCurveTo(p[0], p[1], p[2], p[3]);
            break;

          case 'A':
            var cx = p[0];
            var cy = p[1];
            var rx = p[2];
            var ry = p[3];
            var theta = p[4];
            var dTheta = p[5];
            var psi = p[6];
            var fs = p[7];
            var r = rx > ry ? rx : ry;
            var scaleX = rx > ry ? 1 : rx / ry;
            var scaleY = rx > ry ? ry / rx : 1;
            ctx.translate(cx, cy);
            ctx.rotate(psi);
            ctx.scale(scaleX, scaleY);
            ctx.arc(0, 0, r, theta, theta + dTheta, 1 - fs);
            ctx.scale(1 / scaleX, 1 / scaleY);
            ctx.rotate(-psi);
            ctx.translate(-cx, -cy);
            break;

          case 'z':
            ctx.closePath();
            break;
        }
      }

      return;
    },

    /**
     * 计算返回Path包围盒矩形。
     * @param {module:zrender/shape/Path~IPathStyle} style
     * @return {module:zrender/shape/Base~IBoundingRect}
     */
    getRect: function (style) {
      if (style.__rect) {
        return style.__rect;
      }

      var lineWidth;

      if (style.brushType == 'stroke' || style.brushType == 'fill') {
        lineWidth = style.lineWidth || 1;
      } else {
        lineWidth = 0;
      }

      var minX = Number.MAX_VALUE;
      var maxX = Number.MIN_VALUE;
      var minY = Number.MAX_VALUE;
      var maxY = Number.MIN_VALUE; // 平移坐标

      var x = style.x || 0;
      var y = style.y || 0;
      var pathArray = style.pathArray || this.buildPathArray(style.path);

      for (var i = 0; i < pathArray.length; i++) {
        var p = pathArray[i].points;

        for (var j = 0; j < p.length; j++) {
          if (j % 2 === 0) {
            if (p[j] + x < minX) {
              minX = p[j];
            }

            if (p[j] + x > maxX) {
              maxX = p[j];
            }
          } else {
            if (p[j] + y < minY) {
              minY = p[j];
            }

            if (p[j] + y > maxY) {
              maxY = p[j];
            }
          }
        }
      }

      var rect;

      if (minX === Number.MAX_VALUE || maxX === Number.MIN_VALUE || minY === Number.MAX_VALUE || maxY === Number.MIN_VALUE) {
        rect = {
          x: 0,
          y: 0,
          width: 0,
          height: 0
        };
      } else {
        rect = {
          x: Math.round(minX - lineWidth / 2),
          y: Math.round(minY - lineWidth / 2),
          width: maxX - minX + lineWidth,
          height: maxY - minY + lineWidth
        };
      }

      style.__rect = rect;
      return rect;
    }
  };

  require('../tool/util').inherits(Path, Base);

  return Path;
}(require);