/**
 * zrender: 图形空间辅助类
 *
 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
 *         pissang (https://www.github.com/pissang)
 *
 * isInside：是否在区域内部
 * isOutside：是否在区域外部
 * getTextWidth：测算单行文本宽度
 */
module.exports = function (require) {
  'use strict';

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

  var curve = require('./curve');

  var _ctx;

  var _textWidthCache = {};
  var _textHeightCache = {};
  var _textWidthCacheCounter = 0;
  var _textHeightCacheCounter = 0;
  var TEXT_CACHE_MAX = 5000;
  var PI2 = Math.PI * 2;

  function normalizeRadian(angle) {
    angle %= PI2;

    if (angle < 0) {
      angle += PI2;
    }

    return angle;
  }
  /**
   * 包含判断
   *
   * @param {Object} shape : 图形
   * @param {Object} area ： 目标区域
   * @param {number} x ： 横坐标
   * @param {number} y ： 纵坐标
   */


  function isInside(shape, area, x, y) {
    if (!area || !shape) {
      // 无参数或不支持类型
      return false;
    }

    var zoneType = shape.type;
    _ctx = _ctx || util.getContext(); // 未实现或不可用时(excanvas不支持)则数学运算，主要是line，polyline，ring

    var _mathReturn = _mathMethod(shape, area, x, y);

    if (typeof _mathReturn != 'undefined') {
      return _mathReturn;
    }

    if (shape.buildPath && _ctx.isPointInPath) {
      return _buildPathMethod(shape, _ctx, area, x, y);
    } // 上面的方法都行不通时


    switch (zoneType) {
      case 'ellipse':
        // Todo，不精确
        return true;
      // 旋轮曲线  不准确

      case 'trochoid':
        var _r = area.location == 'out' ? area.r1 + area.r2 + area.d : area.r1 - area.r2 + area.d;

        return isInsideCircle(area, x, y, _r);
      // 玫瑰线 不准确

      case 'rose':
        return isInsideCircle(area, x, y, area.maxr);
      // 路径，椭圆，曲线等-----------------13

      default:
        return false;
      // Todo，暂不支持
    }
  }
  /**
   * @param {Object} shape : 图形
   * @param {Object} area ：目标区域
   * @param {number} x ： 横坐标
   * @param {number} y ： 纵坐标
   * @return {boolean=} true表示坐标处在图形中
   */


  function _mathMethod(shape, area, x, y) {
    var zoneType = shape.type; // 在矩形内则部分图形需要进一步判断

    switch (zoneType) {
      // 贝塞尔曲线
      case 'bezier-curve':
        if (typeof area.cpX2 === 'undefined') {
          return isInsideQuadraticStroke(area.xStart, area.yStart, area.cpX1, area.cpY1, area.xEnd, area.yEnd, area.lineWidth, x, y);
        }

        return isInsideCubicStroke(area.xStart, area.yStart, area.cpX1, area.cpY1, area.cpX2, area.cpY2, area.xEnd, area.yEnd, area.lineWidth, x, y);
      // 线

      case 'line':
        return isInsideLine(area.xStart, area.yStart, area.xEnd, area.yEnd, area.lineWidth, x, y);
      // 折线

      case 'polyline':
        return isInsidePolyline(area.pointList, area.lineWidth, x, y);
      // 圆环

      case 'ring':
        return isInsideRing(area.x, area.y, area.r0, area.r, x, y);
      // 圆形

      case 'circle':
        return isInsideCircle(area.x, area.y, area.r, x, y);
      // 扇形

      case 'sector':
        var startAngle = area.startAngle * Math.PI / 180;
        var endAngle = area.endAngle * Math.PI / 180;

        if (!area.clockWise) {
          startAngle = -startAngle;
          endAngle = -endAngle;
        }

        return isInsideSector(area.x, area.y, area.r0, area.r, startAngle, endAngle, !area.clockWise, x, y);
      // 多边形

      case 'path':
        return area.pathArray && isInsidePath(area.pathArray, Math.max(area.lineWidth, 5), area.brushType, x, y);

      case 'polygon':
      case 'star':
      case 'isogon':
        return isInsidePolygon(area.pointList, x, y);
      // 文本

      case 'text':
        var rect = area.__rect || shape.getRect(area);
        return isInsideRect(rect.x, rect.y, rect.width, rect.height, x, y);
      // 矩形

      case 'rectangle': // 图片

      case 'image':
        return isInsideRect(area.x, area.y, area.width, area.height, x, y);
    }
  }
  /**
   * 通过buildPath方法来判断，三个方法中较快，但是不支持线条类型的shape，
   * 而且excanvas不支持isPointInPath方法
   *
   * @param {Object} shape ： shape
   * @param {Object} context : 上下文
   * @param {Object} area ：目标区域
   * @param {number} x ： 横坐标
   * @param {number} y ： 纵坐标
   * @return {boolean} true表示坐标处在图形中
   */


  function _buildPathMethod(shape, context, area, x, y) {
    // 图形类实现路径创建了则用类的path
    context.beginPath();
    shape.buildPath(context, area);
    context.closePath();
    return context.isPointInPath(x, y);
  }
  /**
   * !isInside
   */


  function isOutside(shape, area, x, y) {
    return !isInside(shape, area, x, y);
  }
  /**
   * 线段包含判断
   * @param  {number}  x0
   * @param  {number}  y0
   * @param  {number}  x1
   * @param  {number}  y1
   * @param  {number}  lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {boolean}
   */


  function isInsideLine(x0, y0, x1, y1, lineWidth, x, y) {
    if (lineWidth === 0) {
      return false;
    }

    var _l = Math.max(lineWidth, 5);

    var _a = 0;
    var _b = x0; // Quick reject

    if (y > y0 + _l && y > y1 + _l || y < y0 - _l && y < y1 - _l || x > x0 + _l && x > x1 + _l || x < x0 - _l && x < x1 - _l) {
      return false;
    }

    if (x0 !== x1) {
      _a = (y0 - y1) / (x0 - x1);
      _b = (x0 * y1 - x1 * y0) / (x0 - x1);
    } else {
      return Math.abs(x - x0) <= _l / 2;
    }

    var tmp = _a * x - y + _b;

    var _s = tmp * tmp / (_a * _a + 1);

    return _s <= _l / 2 * _l / 2;
  }
  /**
   * 三次贝塞尔曲线描边包含判断
   * @param  {number}  x0
   * @param  {number}  y0
   * @param  {number}  x1
   * @param  {number}  y1
   * @param  {number}  x2
   * @param  {number}  y2
   * @param  {number}  x3
   * @param  {number}  y3
   * @param  {number}  lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {boolean}
   */


  function isInsideCubicStroke(x0, y0, x1, y1, x2, y2, x3, y3, lineWidth, x, y) {
    if (lineWidth === 0) {
      return false;
    }

    var _l = Math.max(lineWidth, 5); // Quick reject


    if (y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l || y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l || x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l || x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l) {
      return false;
    }

    var d = curve.cubicProjectPoint(x0, y0, x1, y1, x2, y2, x3, y3, x, y, null);
    return d <= _l / 2;
  }
  /**
   * 二次贝塞尔曲线描边包含判断
   * @param  {number}  x0
   * @param  {number}  y0
   * @param  {number}  x1
   * @param  {number}  y1
   * @param  {number}  x2
   * @param  {number}  y2
   * @param  {number}  lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {boolean}
   */


  function isInsideQuadraticStroke(x0, y0, x1, y1, x2, y2, lineWidth, x, y) {
    if (lineWidth === 0) {
      return false;
    }

    var _l = Math.max(lineWidth, 5); // Quick reject


    if (y > y0 + _l && y > y1 + _l && y > y2 + _l || y < y0 - _l && y < y1 - _l && y < y2 - _l || x > x0 + _l && x > x1 + _l && x > x2 + _l || x < x0 - _l && x < x1 - _l && x < x2 - _l) {
      return false;
    }

    var d = curve.quadraticProjectPoint(x0, y0, x1, y1, x2, y2, x, y, null);
    return d <= _l / 2;
  }
  /**
   * 圆弧描边包含判断
   * @param  {number}  cx
   * @param  {number}  cy
   * @param  {number}  r
   * @param  {number}  startAngle
   * @param  {number}  endAngle
   * @param  {boolean}  anticlockwise
   * @param  {number} lineWidth
   * @param  {number}  x
   * @param  {number}  y
   * @return {Boolean}
   */


  function isInsideArcStroke(cx, cy, r, startAngle, endAngle, anticlockwise, lineWidth, x, y) {
    if (lineWidth === 0) {
      return false;
    }

    var _l = Math.max(lineWidth, 5);

    x -= cx;
    y -= cy;
    var d = Math.sqrt(x * x + y * y);

    if (d - _l > r || d + _l < r) {
      return false;
    }

    if (Math.abs(startAngle - endAngle) >= PI2) {
      // Is a circle
      return true;
    }

    if (anticlockwise) {
      var tmp = startAngle;
      startAngle = normalizeRadian(endAngle);
      endAngle = normalizeRadian(tmp);
    } else {
      startAngle = normalizeRadian(startAngle);
      endAngle = normalizeRadian(endAngle);
    }

    if (startAngle > endAngle) {
      endAngle += PI2;
    }

    var angle = Math.atan2(y, x);

    if (angle < 0) {
      angle += PI2;
    }

    return angle >= startAngle && angle <= endAngle || angle + PI2 >= startAngle && angle + PI2 <= endAngle;
  }

  function isInsidePolyline(points, lineWidth, x, y) {
    var lineWidth = Math.max(lineWidth, 10);

    for (var i = 0, l = points.length - 1; i < l; i++) {
      var x0 = points[i][0];
      var y0 = points[i][1];
      var x1 = points[i + 1][0];
      var y1 = points[i + 1][1];

      if (isInsideLine(x0, y0, x1, y1, lineWidth, x, y)) {
        return true;
      }
    }

    return false;
  }

  function isInsideRing(cx, cy, r0, r, x, y) {
    var d = (x - cx) * (x - cx) + (y - cy) * (y - cy);
    return d < r * r && d > r0 * r0;
  }
  /**
   * 矩形包含判断
   */


  function isInsideRect(x0, y0, width, height, x, y) {
    return x >= x0 && x <= x0 + width && y >= y0 && y <= y0 + height;
  }
  /**
   * 圆形包含判断
   */


  function isInsideCircle(x0, y0, r, x, y) {
    return (x - x0) * (x - x0) + (y - y0) * (y - y0) < r * r;
  }
  /**
   * 扇形包含判断
   */


  function isInsideSector(cx, cy, r0, r, startAngle, endAngle, anticlockwise, x, y) {
    return isInsideArcStroke(cx, cy, (r0 + r) / 2, startAngle, endAngle, anticlockwise, r - r0, x, y);
  }
  /**
   * 多边形包含判断
   * 与 canvas 一样采用 non-zero winding rule
   */


  function isInsidePolygon(points, x, y) {
    var N = points.length;
    var w = 0;

    for (var i = 0, j = N - 1; i < N; i++) {
      var x0 = points[j][0];
      var y0 = points[j][1];
      var x1 = points[i][0];
      var y1 = points[i][1];
      w += windingLine(x0, y0, x1, y1, x, y);
      j = i;
    }

    return w !== 0;
  }

  function windingLine(x0, y0, x1, y1, x, y) {
    if (y > y0 && y > y1 || y < y0 && y < y1) {
      return 0;
    }

    if (y1 == y0) {
      return 0;
    }

    var dir = y1 < y0 ? 1 : -1;
    var t = (y - y0) / (y1 - y0);
    var x_ = t * (x1 - x0) + x0;
    return x_ > x ? dir : 0;
  } // 临时数组


  var roots = [-1, -1, -1];
  var extrema = [-1, -1];

  function swapExtrema() {
    var tmp = extrema[0];
    extrema[0] = extrema[1];
    extrema[1] = tmp;
  }

  function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
    // Quick reject
    if (y > y0 && y > y1 && y > y2 && y > y3 || y < y0 && y < y1 && y < y2 && y < y3) {
      return 0;
    }

    var nRoots = curve.cubicRootAt(y0, y1, y2, y3, y, roots);

    if (nRoots === 0) {
      return 0;
    } else {
      var w = 0;
      var nExtrema = -1;
      var y0_, y1_;

      for (var i = 0; i < nRoots; i++) {
        var t = roots[i];
        var x_ = curve.cubicAt(x0, x1, x2, x3, t);

        if (x_ < x) {
          // Quick reject
          continue;
        }

        if (nExtrema < 0) {
          nExtrema = curve.cubicExtrema(y0, y1, y2, y3, extrema);

          if (extrema[1] < extrema[0] && nExtrema > 1) {
            swapExtrema();
          }

          y0_ = curve.cubicAt(y0, y1, y2, y3, extrema[0]);

          if (nExtrema > 1) {
            y1_ = curve.cubicAt(y0, y1, y2, y3, extrema[1]);
          }
        }

        if (nExtrema == 2) {
          // 分成三段单调函数
          if (t < extrema[0]) {
            w += y0_ < y0 ? 1 : -1;
          } else if (t < extrema[1]) {
            w += y1_ < y0_ ? 1 : -1;
          } else {
            w += y3 < y1_ ? 1 : -1;
          }
        } else {
          // 分成两段单调函数
          if (t < extrema[0]) {
            w += y0_ < y0 ? 1 : -1;
          } else {
            w += y3 < y0_ ? 1 : -1;
          }
        }
      }

      return w;
    }
  }

  function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
    // Quick reject
    if (y > y0 && y > y1 && y > y2 || y < y0 && y < y1 && y < y2) {
      return 0;
    }

    var nRoots = curve.quadraticRootAt(y0, y1, y2, y, roots);

    if (nRoots === 0) {
      return 0;
    } else {
      var t = curve.quadraticExtremum(y0, y1, y2);

      if (t >= 0 && t <= 1) {
        var w = 0;
        var y_ = curve.quadraticAt(y0, y1, y2, t);

        for (var i = 0; i < nRoots; i++) {
          var x_ = curve.quadraticAt(x0, x1, x2, roots[i]);

          if (x_ < x) {
            continue;
          }

          if (roots[i] < t) {
            w += y_ < y0 ? 1 : -1;
          } else {
            w += y2 < y_ ? 1 : -1;
          }
        }

        return w;
      } else {
        var x_ = curve.quadraticAt(x0, x1, x2, roots[0]);

        if (x_ < x) {
          return 0;
        }

        return y2 < y0 ? 1 : -1;
      }
    }
  } // TODO
  // Arc 旋转


  function windingArc(cx, cy, r, startAngle, endAngle, anticlockwise, x, y) {
    y -= cy;

    if (y > r || y < -r) {
      return 0;
    }

    var tmp = Math.sqrt(r * r - y * y);
    roots[0] = -tmp;
    roots[1] = tmp;

    if (Math.abs(startAngle - endAngle) >= PI2) {
      // Is a circle
      startAngle = 0;
      endAngle = PI2;
      var dir = anticlockwise ? 1 : -1;

      if (x >= roots[0] + cx && x <= roots[1] + cx) {
        return dir;
      } else {
        return 0;
      }
    }

    if (anticlockwise) {
      var tmp = startAngle;
      startAngle = normalizeRadian(endAngle);
      endAngle = normalizeRadian(tmp);
    } else {
      startAngle = normalizeRadian(startAngle);
      endAngle = normalizeRadian(endAngle);
    }

    if (startAngle > endAngle) {
      endAngle += PI2;
    }

    var w = 0;

    for (var i = 0; i < 2; i++) {
      var x_ = roots[i];

      if (x_ + cx > x) {
        var angle = Math.atan2(y, x_);
        var dir = anticlockwise ? 1 : -1;

        if (angle < 0) {
          angle = PI2 + angle;
        }

        if (angle >= startAngle && angle <= endAngle || angle + PI2 >= startAngle && angle + PI2 <= endAngle) {
          if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
            dir = -dir;
          }

          w += dir;
        }
      }
    }

    return w;
  }
  /**
   * 路径包含判断
   * 与 canvas 一样采用 non-zero winding rule
   */


  function isInsidePath(pathArray, lineWidth, brushType, x, y) {
    var w = 0;
    var xi = 0;
    var yi = 0;
    var x0 = 0;
    var y0 = 0;
    var beginSubpath = true;
    var firstCmd = true;
    brushType = brushType || 'fill';
    var hasStroke = brushType === 'stroke' || brushType === 'both';
    var hasFill = brushType === 'fill' || brushType === 'both'; // var roots = [-1, -1, -1];

    for (var i = 0; i < pathArray.length; i++) {
      var seg = pathArray[i];
      var p = seg.points; // Begin a new subpath

      if (beginSubpath || seg.command === 'M') {
        if (i > 0) {
          // Close previous subpath
          if (hasFill) {
            w += windingLine(xi, yi, x0, y0, x, y);
          }

          if (w !== 0) {
            return true;
          }
        }

        x0 = p[p.length - 2];
        y0 = p[p.length - 1];
        beginSubpath = false;

        if (firstCmd && seg.command !== 'A') {
          // 如果第一个命令不是M, 是lineTo, bezierCurveTo
          // 等绘制命令的话，是会从该绘制的起点开始算的
          // Arc 会在之后做单独处理所以这里忽略
          firstCmd = false;
          xi = x0;
          yi = y0;
        }
      }

      switch (seg.command) {
        case 'M':
          xi = p[0];
          yi = p[1];
          break;

        case 'L':
          if (hasStroke) {
            if (isInsideLine(xi, yi, p[0], p[1], lineWidth, x, y)) {
              return true;
            }
          }

          if (hasFill) {
            w += windingLine(xi, yi, p[0], p[1], x, y);
          }

          xi = p[0];
          yi = p[1];
          break;

        case 'C':
          if (hasStroke) {
            if (isInsideCubicStroke(xi, yi, p[0], p[1], p[2], p[3], p[4], p[5], lineWidth, x, y)) {
              return true;
            }
          }

          if (hasFill) {
            w += windingCubic(xi, yi, p[0], p[1], p[2], p[3], p[4], p[5], x, y);
          }

          xi = p[4];
          yi = p[5];
          break;

        case 'Q':
          if (hasStroke) {
            if (isInsideQuadraticStroke(xi, yi, p[0], p[1], p[2], p[3], lineWidth, x, y)) {
              return true;
            }
          }

          if (hasFill) {
            w += windingQuadratic(xi, yi, p[0], p[1], p[2], p[3], x, y);
          }

          xi = p[2];
          yi = p[3];
          break;

        case 'A':
          // TODO Arc 旋转
          // TODO Arc 判断的开销比较大
          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 x1 = Math.cos(theta) * rx + cx;
          var y1 = Math.sin(theta) * ry + cy; // 不是直接使用 arc 命令

          if (!firstCmd) {
            w += windingLine(xi, yi, x1, y1);
          } else {
            firstCmd = false; // 第一个命令起点还未定义

            x0 = x1;
            y0 = y1;
          } // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放


          var _x = (x - cx) * ry / rx + cx;

          if (hasStroke) {
            if (isInsideArcStroke(cx, cy, ry, theta, theta + dTheta, 1 - p[7], lineWidth, _x, y)) {
              return true;
            }
          }

          if (hasFill) {
            w += windingArc(cx, cy, ry, theta, theta + dTheta, 1 - p[7], _x, y);
          }

          xi = Math.cos(theta + dTheta) * rx + cx;
          yi = Math.sin(theta + dTheta) * ry + cy;
          break;

        case 'z':
          if (hasStroke) {
            if (isInsideLine(xi, yi, x0, y0, lineWidth, x, y)) {
              return true;
            }
          }

          beginSubpath = true;
          break;
      }
    }

    if (hasFill) {
      w += windingLine(xi, yi, x0, y0, x, y);
    }

    return w !== 0;
  }
  /**
   * 测算多行文本宽度
   * @param {Object} text
   * @param {Object} textFont
   */


  function getTextWidth(text, textFont) {
    var key = text + ':' + textFont;

    if (_textWidthCache[key]) {
      return _textWidthCache[key];
    }

    _ctx = _ctx || util.getContext();

    _ctx.save();

    if (textFont) {
      _ctx.font = textFont;
    }

    text = (text + '').split('\n');
    var width = 0;

    for (var i = 0, l = text.length; i < l; i++) {
      width = Math.max(_ctx.measureText(text[i]).width, width);
    }

    _ctx.restore();

    _textWidthCache[key] = width;

    if (++_textWidthCacheCounter > TEXT_CACHE_MAX) {
      // 内存释放
      _textWidthCacheCounter = 0;
      _textWidthCache = {};
    }

    return width;
  }
  /**
   * 测算多行文本高度
   * @param {Object} text
   * @param {Object} textFont
   */


  function getTextHeight(text, textFont) {
    var key = text + ':' + textFont;

    if (_textHeightCache[key]) {
      return _textHeightCache[key];
    }

    _ctx = _ctx || util.getContext();

    _ctx.save();

    if (textFont) {
      _ctx.font = textFont;
    }

    text = (text + '').split('\n'); // 比较粗暴

    var height = (_ctx.measureText('国').width + 2) * text.length;

    _ctx.restore();

    _textHeightCache[key] = height;

    if (++_textHeightCacheCounter > TEXT_CACHE_MAX) {
      // 内存释放
      _textHeightCacheCounter = 0;
      _textHeightCache = {};
    }

    return height;
  }

  return {
    isInside: isInside,
    isOutside: isOutside,
    getTextWidth: getTextWidth,
    getTextHeight: getTextHeight,
    isInsidePath: isInsidePath,
    isInsidePolygon: isInsidePolygon,
    isInsideSector: isInsideSector,
    isInsideCircle: isInsideCircle,
    isInsideLine: isInsideLine,
    isInsideRect: isInsideRect,
    isInsidePolyline: isInsidePolyline,
    isInsideCubicStroke: isInsideCubicStroke,
    isInsideQuadraticStroke: isInsideQuadraticStroke
  };
}(require);