/**
 * Lines geometry
 * Use screen space projected lines lineWidth > MAX_LINE_WIDTH
 * https://mattdesl.svbtle.com/drawing-lines-is-hard
 * @module echarts-gl/util/geometry/LinesGeometry
 * @author Yi Shen(http://github.com/pissang)
 */
import Geometry from 'claygl/src/Geometry';
import * as echarts from 'echarts/lib/echarts';
import dynamicConvertMixin from './dynamicConvertMixin';
import glmatrix from 'claygl/src/dep/glmatrix';
var vec2 = glmatrix.vec2; // var CURVE_RECURSION_LIMIT = 8;
// var CURVE_COLLINEAR_EPSILON = 40;

var sampleLinePoints = [[0, 0], [1, 1]];
/**
 * @constructor
 * @alias module:echarts-gl/util/geometry/LinesGeometry
 * @extends clay.Geometry
 */

var LinesGeometry = Geometry.extend(function () {
  return {
    segmentScale: 4,
    dynamic: true,

    /**
     * Need to use mesh to expand lines if lineWidth > MAX_LINE_WIDTH
     */
    useNativeLine: true,
    attributes: {
      position: new Geometry.Attribute('position', 'float', 2, 'POSITION'),
      normal: new Geometry.Attribute('normal', 'float', 2),
      offset: new Geometry.Attribute('offset', 'float', 1),
      color: new Geometry.Attribute('color', 'float', 4, 'COLOR')
    }
  };
},
/** @lends module: echarts-gl/util/geometry/LinesGeometry.prototype */
{
  /**
   * Reset offset
   */
  resetOffset: function () {
    this._vertexOffset = 0;
    this._faceOffset = 0;
    this._itemVertexOffsets = [];
  },

  /**
   * @param {number} nVertex
   */
  setVertexCount: function (nVertex) {
    var attributes = this.attributes;

    if (this.vertexCount !== nVertex) {
      attributes.position.init(nVertex);
      attributes.color.init(nVertex);

      if (!this.useNativeLine) {
        attributes.offset.init(nVertex);
        attributes.normal.init(nVertex);
      }

      if (nVertex > 0xffff) {
        if (this.indices instanceof Uint16Array) {
          this.indices = new Uint32Array(this.indices);
        }
      } else {
        if (this.indices instanceof Uint32Array) {
          this.indices = new Uint16Array(this.indices);
        }
      }
    }
  },

  /**
   * @param {number} nTriangle
   */
  setTriangleCount: function (nTriangle) {
    if (this.triangleCount !== nTriangle) {
      if (nTriangle === 0) {
        this.indices = null;
      } else {
        this.indices = this.vertexCount > 0xffff ? new Uint32Array(nTriangle * 3) : new Uint16Array(nTriangle * 3);
      }
    }
  },
  _getCubicCurveApproxStep: function (p0, p1, p2, p3) {
    var len = vec2.dist(p0, p1) + vec2.dist(p2, p1) + vec2.dist(p3, p2);
    var step = 1 / (len + 1) * this.segmentScale;
    return step;
  },

  /**
   * Get vertex count of cubic curve
   * @param {Array.<number>} p0
   * @param {Array.<number>} p1
   * @param {Array.<number>} p2
   * @param {Array.<number>} p3
   * @return number
   */
  getCubicCurveVertexCount: function (p0, p1, p2, p3) {
    var step = this._getCubicCurveApproxStep(p0, p1, p2, p3);

    var segCount = Math.ceil(1 / step);

    if (!this.useNativeLine) {
      return segCount * 2 + 2;
    } else {
      return segCount * 2;
    }
  },

  /**
   * Get face count of cubic curve
   * @param {Array.<number>} p0
   * @param {Array.<number>} p1
   * @param {Array.<number>} p2
   * @param {Array.<number>} p3
   * @return number
   */
  getCubicCurveTriangleCount: function (p0, p1, p2, p3) {
    var step = this._getCubicCurveApproxStep(p0, p1, p2, p3);

    var segCount = Math.ceil(1 / step);

    if (!this.useNativeLine) {
      return segCount * 2;
    } else {
      return 0;
    }
  },

  /**
   * Get vertex count of line
   * @return {number}
   */
  getLineVertexCount: function () {
    return this.getPolylineVertexCount(sampleLinePoints);
  },

  /**
   * Get face count of line
   * @return {number}
   */
  getLineTriangleCount: function () {
    return this.getPolylineTriangleCount(sampleLinePoints);
  },

  /**
   * Get how many vertices will polyline take.
   * @type {number|Array} points Can be a 1d/2d list of points, or a number of points amount.
   * @return {number}
   */
  getPolylineVertexCount: function (points) {
    var pointsLen;

    if (typeof points === 'number') {
      pointsLen = points;
    } else {
      var is2DArray = typeof points[0] !== 'number';
      pointsLen = is2DArray ? points.length : points.length / 2;
    }

    return !this.useNativeLine ? (pointsLen - 1) * 2 + 2 : (pointsLen - 1) * 2;
  },

  /**
   * Get how many triangles will polyline take.
   * @type {number|Array} points Can be a 1d/2d list of points, or a number of points amount.
   * @return {number}
   */
  getPolylineTriangleCount: function (points) {
    var pointsLen;

    if (typeof points === 'number') {
      pointsLen = points;
    } else {
      var is2DArray = typeof points[0] !== 'number';
      pointsLen = is2DArray ? points.length : points.length / 2;
    }

    return !this.useNativeLine ? (pointsLen - 1) * 2 : 0;
  },

  /**
   * Add a cubic curve
   * @param {Array.<number>} p0
   * @param {Array.<number>} p1
   * @param {Array.<number>} p2
   * @param {Array.<number>} p3
   * @param {Array.<number>} color
   * @param {number} [lineWidth=1]
   */
  addCubicCurve: function (p0, p1, p2, p3, color, lineWidth) {
    if (lineWidth == null) {
      lineWidth = 1;
    } // incremental interpolation
    // http://antigrain.com/research/bezier_interpolation/index.html#PAGE_BEZIER_INTERPOLATION


    var x0 = p0[0],
        y0 = p0[1];
    var x1 = p1[0],
        y1 = p1[1];
    var x2 = p2[0],
        y2 = p2[1];
    var x3 = p3[0],
        y3 = p3[1];

    var step = this._getCubicCurveApproxStep(p0, p1, p2, p3);

    var step2 = step * step;
    var step3 = step2 * step;
    var pre1 = 3.0 * step;
    var pre2 = 3.0 * step2;
    var pre4 = 6.0 * step2;
    var pre5 = 6.0 * step3;
    var tmp1x = x0 - x1 * 2.0 + x2;
    var tmp1y = y0 - y1 * 2.0 + y2;
    var tmp2x = (x1 - x2) * 3.0 - x0 + x3;
    var tmp2y = (y1 - y2) * 3.0 - y0 + y3;
    var fx = x0;
    var fy = y0;
    var dfx = (x1 - x0) * pre1 + tmp1x * pre2 + tmp2x * step3;
    var dfy = (y1 - y0) * pre1 + tmp1y * pre2 + tmp2y * step3;
    var ddfx = tmp1x * pre4 + tmp2x * pre5;
    var ddfy = tmp1y * pre4 + tmp2y * pre5;
    var dddfx = tmp2x * pre5;
    var dddfy = tmp2y * pre5;
    var t = 0;
    var k = 0;
    var segCount = Math.ceil(1 / step);
    var points = new Float32Array((segCount + 1) * 3);
    var points = [];
    var offset = 0;

    for (var k = 0; k < segCount + 1; k++) {
      points[offset++] = fx;
      points[offset++] = fy;
      fx += dfx;
      fy += dfy;
      dfx += ddfx;
      dfy += ddfy;
      ddfx += dddfx;
      ddfy += dddfy;
      t += step;

      if (t > 1) {
        fx = dfx > 0 ? Math.min(fx, x3) : Math.max(fx, x3);
        fy = dfy > 0 ? Math.min(fy, y3) : Math.max(fy, y3);
      }
    }

    this.addPolyline(points, color, lineWidth);
  },

  /**
   * Add a straight line
   * @param {Array.<number>} p0
   * @param {Array.<number>} p1
   * @param {Array.<number>} color
   * @param {number} [lineWidth=1]
   */
  addLine: function (p0, p1, color, lineWidth) {
    this.addPolyline([p0, p1], color, lineWidth);
  },

  /**
   * Add a straight line
   * @param {Array.<Array> | Array.<number>} points
   * @param {Array.<number> | Array.<Array>} color
   * @param {number} [lineWidth=1]
   * @param {number} [arrayOffset=0]
   * @param {number} [pointsCount] Default to be amount of points in the first argument
   */
  addPolyline: function () {
    var dirA = vec2.create();
    var dirB = vec2.create();
    var normal = vec2.create();
    var tangent = vec2.create();
    var point = [],
        nextPoint = [],
        prevPoint = [];
    return function (points, color, lineWidth, arrayOffset, pointsCount) {
      if (!points.length) {
        return;
      }

      var is2DArray = typeof points[0] !== 'number';

      if (pointsCount == null) {
        pointsCount = is2DArray ? points.length : points.length / 2;
      }

      if (pointsCount < 2) {
        return;
      }

      if (arrayOffset == null) {
        arrayOffset = 0;
      }

      if (lineWidth == null) {
        lineWidth = 1;
      }

      this._itemVertexOffsets.push(this._vertexOffset);

      var notSharingColor = is2DArray ? typeof color[0] !== 'number' : color.length / 4 === pointsCount;
      var positionAttr = this.attributes.position;
      var colorAttr = this.attributes.color;
      var offsetAttr = this.attributes.offset;
      var normalAttr = this.attributes.normal;
      var indices = this.indices;
      var vertexOffset = this._vertexOffset;
      var pointColor;

      for (var k = 0; k < pointsCount; k++) {
        if (is2DArray) {
          point = points[k + arrayOffset];

          if (notSharingColor) {
            pointColor = color[k + arrayOffset];
          } else {
            pointColor = color;
          }
        } else {
          var k2 = k * 2 + arrayOffset;
          point = point || [];
          point[0] = points[k2];
          point[1] = points[k2 + 1];

          if (notSharingColor) {
            var k4 = k * 4 + arrayOffset;
            pointColor = pointColor || [];
            pointColor[0] = color[k4];
            pointColor[1] = color[k4 + 1];
            pointColor[2] = color[k4 + 2];
            pointColor[3] = color[k4 + 3];
          } else {
            pointColor = color;
          }
        }

        if (!this.useNativeLine) {
          var offset;

          if (k < pointsCount - 1) {
            if (is2DArray) {
              vec2.copy(nextPoint, points[k + 1]);
            } else {
              var k2 = (k + 1) * 2 + arrayOffset;
              nextPoint = nextPoint || [];
              nextPoint[0] = points[k2];
              nextPoint[1] = points[k2 + 1];
            } // TODO In case dir is (0, 0)
            // TODO miterLimit


            if (k > 0) {
              vec2.sub(dirA, point, prevPoint);
              vec2.sub(dirB, nextPoint, point);
              vec2.normalize(dirA, dirA);
              vec2.normalize(dirB, dirB);
              vec2.add(tangent, dirA, dirB);
              vec2.normalize(tangent, tangent);
              var miter = lineWidth / 2 * Math.min(1 / vec2.dot(dirA, tangent), 2);
              normal[0] = -tangent[1];
              normal[1] = tangent[0];
              offset = miter;
            } else {
              vec2.sub(dirA, nextPoint, point);
              vec2.normalize(dirA, dirA);
              normal[0] = -dirA[1];
              normal[1] = dirA[0];
              offset = lineWidth / 2;
            }
          } else {
            vec2.sub(dirA, point, prevPoint);
            vec2.normalize(dirA, dirA);
            normal[0] = -dirA[1];
            normal[1] = dirA[0];
            offset = lineWidth / 2;
          }

          normalAttr.set(vertexOffset, normal);
          normalAttr.set(vertexOffset + 1, normal);
          offsetAttr.set(vertexOffset, offset);
          offsetAttr.set(vertexOffset + 1, -offset);
          vec2.copy(prevPoint, point);
          positionAttr.set(vertexOffset, point);
          positionAttr.set(vertexOffset + 1, point);
          colorAttr.set(vertexOffset, pointColor);
          colorAttr.set(vertexOffset + 1, pointColor);
          vertexOffset += 2;
        } else {
          if (k > 1) {
            positionAttr.copy(vertexOffset, vertexOffset - 1);
            colorAttr.copy(vertexOffset, vertexOffset - 1);
            vertexOffset++;
          }
        }

        if (!this.useNativeLine) {
          if (k > 0) {
            var idx3 = this._faceOffset * 3;
            var indices = this.indices; // 0-----2
            // 1-----3
            // 0->1->2, 1->3->2

            indices[idx3] = vertexOffset - 4;
            indices[idx3 + 1] = vertexOffset - 3;
            indices[idx3 + 2] = vertexOffset - 2;
            indices[idx3 + 3] = vertexOffset - 3;
            indices[idx3 + 4] = vertexOffset - 1;
            indices[idx3 + 5] = vertexOffset - 2;
            this._faceOffset += 2;
          }
        } else {
          colorAttr.set(vertexOffset, pointColor);
          positionAttr.set(vertexOffset, point);
          vertexOffset++;
        }
      }

      this._vertexOffset = vertexOffset;
    };
  }(),

  /**
   * Set color of single line.
   */
  setItemColor: function (idx, color) {
    var startOffset = this._itemVertexOffsets[idx];
    var endOffset = idx < this._itemVertexOffsets.length - 1 ? this._itemVertexOffsets[idx + 1] : this._vertexOffset;

    for (var i = startOffset; i < endOffset; i++) {
      this.attributes.color.set(i, color);
    }

    this.dirty('color');
  }
});
echarts.util.defaults(LinesGeometry.prototype, dynamicConvertMixin);
export default LinesGeometry;