/**
 * @fileOverview math util
 * @author huangtonger@aliyun.com
 */

const BaseUtil = require('./base')
const tolerance = 0.001
const MathUtil = {
  /**
   * 是否在区间内
   * @param   {number}       value  值
   * @param   {number}       min    最小值
   * @param   {number}       max    最大值
   * @return  {boolean}      bool   布尔
   */
  isBetween(value, min, max) {
    return value >= min && value <= max
  },
  /**
   * 两线段交点
   * @param  {object}  p0 第一条线段起点
   * @param  {object}  p1 第一条线段终点
   * @param  {object}  p2 第二条线段起点
   * @param  {object}  p3 第二条线段终点
   * @return {object}  交点
   */
  getLineIntersect(p0, p1, p2, p3) {
    const E = {
      x: p2.x - p0.x,
      y: p2.y - p0.y
    }
    const D0 = {
      x: p1.x - p0.x,
      y: p1.y - p0.y
    }
    const D1 = {
      x: p3.x - p2.x,
      y: p3.y - p2.y
    }
    const kross = D0.x * D1.y - D0.y * D1.x
    const sqrKross = kross * kross
    const sqrLen0 = D0.x * D0.x + D0.y * D0.y
    const sqrLen1 = D1.x * D1.x + D1.y * D1.y
    let point = null
    if (sqrKross > tolerance * sqrLen0 * sqrLen1) {
      const s = (E.x * D1.y - E.y * D1.x) / kross
      const t = (E.x * D0.y - E.y * D0.x) / kross
      if (MathUtil.isBetween(s, 0, 1) && MathUtil.isBetween(t, 0, 1)) {
        point = {
          x: p0.x + s * D0.x,
          y: p0.y + s * D0.y
        }
      }
    }
    return point
  },
  /**
   * point and rectangular intersection point
   * @param  {object} rect  rect
   * @param  {object} point point
   * @return {object} rst;
   */
  getRectIntersectByPoint(rect, point) {
    const { x, y, width, height } = rect
    const cx = x + width / 2
    const cy = y + height / 2
    const points = []
    const center = {
      x: cx,
      y: cy
    }
    points.push({
      x,
      y
    })
    points.push({
      x: x + width,
      y
    })
    points.push({
      x: x + width,
      y: y + height
    })
    points.push({
      x,
      y: y + height
    })
    points.push({
      x,
      y
    })
    let rst = null
    for (let i = 1; i < points.length; i++) {
      rst = MathUtil.getLineIntersect(points[i - 1], points[i], center, point)
      if (rst) {
        break
      }
    }
    return rst
  },
  /**
   * get point and circle inIntersect
   * @param {Object} circle 圆点，x,y,r
   * @param {Object} point 点 x,y
   * @return {object} applied point
   */
  getCircleIntersectByPoint(circle, point) {
    const cx = circle.x
    const cy = circle.y
    const r = circle.r
    const { x, y } = point
    const d = Math.sqrt(Math.pow((x - cx), 2) + Math.pow((y - cy), 2))
    if (d < r) {
      return null
    }
    const dx = (x - cx)
    const dy = (y - cy)
    const signX = Math.sign(dx)
    const signY = Math.sign(dy)
    const angle = Math.atan(dy / dx)
    return {
      x: cx + Math.abs(r * Math.cos(angle)) * signX,
      y: cy + Math.abs(r * Math.sin(angle)) * signY
    }
  },
  /**
   * get point and ellipse inIntersect
   * @param {Object} ellipse 椭圆 x,y,rx,ry
   * @param {Object} point 点 x,y
   * @return {object} applied point
   */
  getEllispeIntersectByPoint(ellipse, point) {
    // 计算线段 (point.x, point.y) 到 (ellipse.x, ellipse.y) 与椭圆的交点
    const a = ellipse.rx
    const b = ellipse.ry
    const cx = ellipse.x
    const cy = ellipse.y
    // const c = Math.sqrt(a * a - b * b); // 焦距
    const dx = (point.x - cx)
    const dy = (point.y - cy)
    let angle = Math.atan2(dy / b, dx / a) // 直接通过 x,y 求夹角，求出来的范围是 -PI, PI
    if (angle < 0) {
      angle += 2 * Math.PI // 转换到 0，2PI
    }
    // 通过参数方程求交点
    // const r = (b * b) / (a - c * Math.sin(angle));
    return {
      x: cx + a * Math.cos(angle),
      y: cy + b * Math.sin(angle)
    }
  },
  /**
   * coordinate matrix transformation
   * @param  {number} point   coordinate
   * @param  {number} matrix  matrix
   * @param  {number} tag     could be 0 or 1
   * @return {object} transformed point
   */
  applyMatrix(point, matrix, tag = 1) {
    const vector = [point.x, point.y, tag]
    BaseUtil.vec3.transformMat3(vector, vector, matrix)
    return {
      x: vector[0],
      y: vector[1]
    }
  },
  /**
   * coordinate matrix invert transformation
   * @param  {number} point   coordinate
   * @param  {number} matrix  matrix
   * @param  {number} tag     could be 0 or 1
   * @return {object} transformed point
   */
  invertMatrix(point, matrix, tag = 1) {
    const inversedMatrix = BaseUtil.mat3.invert([], matrix)
    const vector = [point.x, point.y, tag]
    BaseUtil.vec3.transformMat3(vector, vector, inversedMatrix)
    return {
      x: vector[0],
      y: vector[1]
    }
  },

  /**
   * if the graph about the shortest path matrix is connected
   * @param  {array} matrix   shortest path matrix
   * @return {boolean} connected
   */
  isConnected(matrix) {
    if (matrix.length > 0) {
      for (let j = 0; j < matrix[0].length; j++) {
        if (matrix[0][j] === Infinity) return false
      }
    }
    return true
  },

  randomInitPos(size, xRange = [0, 1], yRange = [0, 1]) {
    const positions = []
    for (let i = 0; i < size; i++) {
      const x = Math.random() * (xRange[1] - xRange[0]) + xRange[0]
      const y = Math.random() * (yRange[1] - yRange[0]) + yRange[0]
      positions.push([x, y])
    }
    return positions
  },
  getCircleCenterByPoints(p1, p2, p3) {
    const a = p1.x - p2.x
    const b = p1.y - p2.y
    const c = p1.x - p3.x
    const d = p1.y - p3.y
    const e = (p1.x * p1.x - p2.x * p2.x - p2.y * p2.y + p1.y * p1.y) / 2
    const f = (p1.x * p1.x - p3.x * p3.x - p3.y * p3.y + p1.y * p1.y) / 2
    const denominator = b * c - a * d
    return {
      x: -(d * e - b * f) / denominator,
      y: -(a * f - c * e) / denominator
    }
  },
  distance(p1, p2) {
    const vx = p1.x - p2.x
    const vy = p1.y - p2.y
    return Math.sqrt(vx * vx + vy * vy)
  }
}
module.exports = BaseUtil.mix({}, BaseUtil, MathUtil)
