/**
 * Geometry collecting bars data
 *
 * @module echarts-gl/chart/bars/BarsGeometry
 * @author Yi Shen(http://github.com/pissang)
 */
import * as echarts from 'echarts/lib/echarts';
import dynamicConvertMixin from './dynamicConvertMixin';
import trianglesSortMixin from './trianglesSortMixin';
import Geometry from 'claygl/src/Geometry';
import glMatrix from 'claygl/src/dep/glmatrix';
var vec3 = glMatrix.vec3;
var mat3 = glMatrix.mat3;
/**
 * @constructor
 * @alias module:echarts-gl/chart/bars/BarsGeometry
 * @extends clay.Geometry
 */

var BarsGeometry = Geometry.extend(function () {
  return {
    attributes: {
      position: new Geometry.Attribute('position', 'float', 3, 'POSITION'),
      normal: new Geometry.Attribute('normal', 'float', 3, 'NORMAL'),
      color: new Geometry.Attribute('color', 'float', 4, 'COLOR'),
      prevPosition: new Geometry.Attribute('prevPosition', 'float', 3),
      prevNormal: new Geometry.Attribute('prevNormal', 'float', 3)
    },
    dynamic: true,
    enableNormal: false,
    bevelSize: 1,
    bevelSegments: 0,
    // Map from vertexIndex to dataIndex.
    _dataIndices: null,
    _vertexOffset: 0,
    _triangleOffset: 0
  };
},
/** @lends module:echarts-gl/chart/bars/BarsGeometry.prototype */
{
  resetOffset: function () {
    this._vertexOffset = 0;
    this._triangleOffset = 0;
  },
  setBarCount: function (barCount) {
    var enableNormal = this.enableNormal;
    var vertexCount = this.getBarVertexCount() * barCount;
    var triangleCount = this.getBarTriangleCount() * barCount;

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

      if (enableNormal) {
        this.attributes.normal.init(vertexCount);
      } else {
        this.attributes.normal.value = null;
      }

      this.attributes.color.init(vertexCount);
    }

    if (this.triangleCount !== triangleCount) {
      this.indices = vertexCount > 0xffff ? new Uint32Array(triangleCount * 3) : new Uint16Array(triangleCount * 3);
      this._dataIndices = new Uint32Array(vertexCount);
    }
  },
  getBarVertexCount: function () {
    var bevelSegments = this.bevelSize > 0 ? this.bevelSegments : 0;
    return bevelSegments > 0 ? this._getBevelBarVertexCount(bevelSegments) : this.enableNormal ? 24 : 8;
  },
  getBarTriangleCount: function () {
    var bevelSegments = this.bevelSize > 0 ? this.bevelSegments : 0;
    return bevelSegments > 0 ? this._getBevelBarTriangleCount(bevelSegments) : 12;
  },
  _getBevelBarVertexCount: function (bevelSegments) {
    return (bevelSegments + 1) * 4 * (bevelSegments + 1) * 2;
  },
  _getBevelBarTriangleCount: function (bevelSegments) {
    var widthSegments = bevelSegments * 4 + 3;
    var heightSegments = bevelSegments * 2 + 1;
    return (widthSegments + 1) * heightSegments * 2 + 4;
  },
  setColor: function (idx, color) {
    var vertexCount = this.getBarVertexCount();
    var start = vertexCount * idx;
    var end = vertexCount * (idx + 1);

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

    this.dirtyAttribute('color');
  },

  /**
   * Get dataIndex of vertex.
   * @param {number} vertexIndex
   */
  getDataIndexOfVertex: function (vertexIndex) {
    return this._dataIndices ? this._dataIndices[vertexIndex] : null;
  },

  /**
   * Add a bar
   * @param {Array.<number>} start
   * @param {Array.<number>} end
   * @param {Array.<number>} orient  right direction
   * @param {Array.<number>} size size on x and z
   * @param {Array.<number>} color
   */
  addBar: function () {
    var v3Create = vec3.create;
    var v3ScaleAndAdd = vec3.scaleAndAdd;
    var end = v3Create();
    var px = v3Create();
    var py = v3Create();
    var pz = v3Create();
    var nx = v3Create();
    var ny = v3Create();
    var nz = v3Create();
    var pts = [];
    var normals = [];

    for (var i = 0; i < 8; i++) {
      pts[i] = v3Create();
    }

    var cubeFaces4 = [// PX
    [0, 1, 5, 4], // NX
    [2, 3, 7, 6], // PY
    [4, 5, 6, 7], // NY
    [3, 2, 1, 0], // PZ
    [0, 4, 7, 3], // NZ
    [1, 2, 6, 5]];
    var face4To3 = [0, 1, 2, 0, 2, 3];
    var cubeFaces3 = [];

    for (var i = 0; i < cubeFaces4.length; i++) {
      var face4 = cubeFaces4[i];

      for (var j = 0; j < 2; j++) {
        var face = [];

        for (var k = 0; k < 3; k++) {
          face.push(face4[face4To3[j * 3 + k]]);
        }

        cubeFaces3.push(face);
      }
    }

    return function (start, dir, leftDir, size, color, dataIndex) {
      // Use vertex, triangle maybe sorted.
      var startVertex = this._vertexOffset;

      if (this.bevelSize > 0 && this.bevelSegments > 0) {
        this._addBevelBar(start, dir, leftDir, size, this.bevelSize, this.bevelSegments, color);
      } else {
        vec3.copy(py, dir);
        vec3.normalize(py, py); // x * y => z

        vec3.cross(pz, leftDir, py);
        vec3.normalize(pz, pz); // y * z => x

        vec3.cross(px, py, pz);
        vec3.normalize(pz, pz);
        vec3.negate(nx, px);
        vec3.negate(ny, py);
        vec3.negate(nz, pz);
        v3ScaleAndAdd(pts[0], start, px, size[0] / 2);
        v3ScaleAndAdd(pts[0], pts[0], pz, size[2] / 2);
        v3ScaleAndAdd(pts[1], start, px, size[0] / 2);
        v3ScaleAndAdd(pts[1], pts[1], nz, size[2] / 2);
        v3ScaleAndAdd(pts[2], start, nx, size[0] / 2);
        v3ScaleAndAdd(pts[2], pts[2], nz, size[2] / 2);
        v3ScaleAndAdd(pts[3], start, nx, size[0] / 2);
        v3ScaleAndAdd(pts[3], pts[3], pz, size[2] / 2);
        v3ScaleAndAdd(end, start, py, size[1]);
        v3ScaleAndAdd(pts[4], end, px, size[0] / 2);
        v3ScaleAndAdd(pts[4], pts[4], pz, size[2] / 2);
        v3ScaleAndAdd(pts[5], end, px, size[0] / 2);
        v3ScaleAndAdd(pts[5], pts[5], nz, size[2] / 2);
        v3ScaleAndAdd(pts[6], end, nx, size[0] / 2);
        v3ScaleAndAdd(pts[6], pts[6], nz, size[2] / 2);
        v3ScaleAndAdd(pts[7], end, nx, size[0] / 2);
        v3ScaleAndAdd(pts[7], pts[7], pz, size[2] / 2);
        var attributes = this.attributes;

        if (this.enableNormal) {
          normals[0] = px;
          normals[1] = nx;
          normals[2] = py;
          normals[3] = ny;
          normals[4] = pz;
          normals[5] = nz;
          var vertexOffset = this._vertexOffset;

          for (var i = 0; i < cubeFaces4.length; i++) {
            var idx3 = this._triangleOffset * 3;

            for (var k = 0; k < 6; k++) {
              this.indices[idx3++] = vertexOffset + face4To3[k];
            }

            vertexOffset += 4;
            this._triangleOffset += 2;
          }

          for (var i = 0; i < cubeFaces4.length; i++) {
            var normal = normals[i];

            for (var k = 0; k < 4; k++) {
              var idx = cubeFaces4[i][k];
              attributes.position.set(this._vertexOffset, pts[idx]);
              attributes.normal.set(this._vertexOffset, normal);
              attributes.color.set(this._vertexOffset++, color);
            }
          }
        } else {
          for (var i = 0; i < cubeFaces3.length; i++) {
            var idx3 = this._triangleOffset * 3;

            for (var k = 0; k < 3; k++) {
              this.indices[idx3 + k] = cubeFaces3[i][k] + this._vertexOffset;
            }

            this._triangleOffset++;
          }

          for (var i = 0; i < pts.length; i++) {
            attributes.position.set(this._vertexOffset, pts[i]);
            attributes.color.set(this._vertexOffset++, color);
          }
        }
      }

      var endVerex = this._vertexOffset;

      for (var i = startVertex; i < endVerex; i++) {
        this._dataIndices[i] = dataIndex;
      }
    };
  }(),

  /**
   * Add a bar with bevel
   * @param {Array.<number>} start
   * @param {Array.<number>} end
   * @param {Array.<number>} orient  right direction
   * @param {Array.<number>} size size on x and z
   * @param {number} bevelSize
   * @param {number} bevelSegments
   * @param {Array.<number>} color
   */
  _addBevelBar: function () {
    var px = vec3.create();
    var py = vec3.create();
    var pz = vec3.create();
    var rotateMat = mat3.create();
    var bevelStartSize = [];
    var xOffsets = [1, -1, -1, 1];
    var zOffsets = [1, 1, -1, -1];
    var yOffsets = [2, 0];
    return function (start, dir, leftDir, size, bevelSize, bevelSegments, color) {
      vec3.copy(py, dir);
      vec3.normalize(py, py); // x * y => z

      vec3.cross(pz, leftDir, py);
      vec3.normalize(pz, pz); // y * z => x

      vec3.cross(px, py, pz);
      vec3.normalize(pz, pz);
      rotateMat[0] = px[0];
      rotateMat[1] = px[1];
      rotateMat[2] = px[2];
      rotateMat[3] = py[0];
      rotateMat[4] = py[1];
      rotateMat[5] = py[2];
      rotateMat[6] = pz[0];
      rotateMat[7] = pz[1];
      rotateMat[8] = pz[2];
      bevelSize = Math.min(size[0], size[2]) / 2 * bevelSize;

      for (var i = 0; i < 3; i++) {
        bevelStartSize[i] = Math.max(size[i] - bevelSize * 2, 0);
      }

      var rx = (size[0] - bevelStartSize[0]) / 2;
      var ry = (size[1] - bevelStartSize[1]) / 2;
      var rz = (size[2] - bevelStartSize[2]) / 2;
      var pos = [];
      var normal = [];
      var vertexOffset = this._vertexOffset;
      var endIndices = [];

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

        for (var m = 0; m <= bevelSegments; m++) {
          for (var j = 0; j < 4; j++) {
            if (m === 0 && i === 0 || i === 1 && m === bevelSegments) {
              endIndices[i].push(vertexOffset);
            }

            for (var n = 0; n <= bevelSegments; n++) {
              var phi = n / bevelSegments * Math.PI / 2 + Math.PI / 2 * j;
              var theta = m / bevelSegments * Math.PI / 2 + Math.PI / 2 * i; // var r = rx < ry ? (rz < rx ? rz : rx) : (rz < ry ? rz : ry);

              normal[0] = rx * Math.cos(phi) * Math.sin(theta);
              normal[1] = ry * Math.cos(theta);
              normal[2] = rz * Math.sin(phi) * Math.sin(theta);
              pos[0] = normal[0] + xOffsets[j] * bevelStartSize[0] / 2;
              pos[1] = normal[1] + ry + yOffsets[i] * bevelStartSize[1] / 2;
              pos[2] = normal[2] + zOffsets[j] * bevelStartSize[2] / 2; // Normal is not right if rx, ry, rz not equal.

              if (!(Math.abs(rx - ry) < 1e-6 && Math.abs(ry - rz) < 1e-6)) {
                normal[0] /= rx * rx;
                normal[1] /= ry * ry;
                normal[2] /= rz * rz;
              }

              vec3.normalize(normal, normal);
              vec3.transformMat3(pos, pos, rotateMat);
              vec3.transformMat3(normal, normal, rotateMat);
              vec3.add(pos, pos, start);
              this.attributes.position.set(vertexOffset, pos);

              if (this.enableNormal) {
                this.attributes.normal.set(vertexOffset, normal);
              }

              this.attributes.color.set(vertexOffset, color);
              vertexOffset++;
            }
          }
        }
      }

      var widthSegments = bevelSegments * 4 + 3;
      var heightSegments = bevelSegments * 2 + 1;
      var len = widthSegments + 1;

      for (var j = 0; j < heightSegments; j++) {
        for (var i = 0; i <= widthSegments; i++) {
          var i2 = j * len + i + this._vertexOffset;
          var i1 = j * len + (i + 1) % len + this._vertexOffset;
          var i4 = (j + 1) * len + (i + 1) % len + this._vertexOffset;
          var i3 = (j + 1) * len + i + this._vertexOffset;
          this.setTriangleIndices(this._triangleOffset++, [i4, i2, i1]);
          this.setTriangleIndices(this._triangleOffset++, [i4, i3, i2]);
        }
      } // Close top and bottom


      this.setTriangleIndices(this._triangleOffset++, [endIndices[0][0], endIndices[0][2], endIndices[0][1]]);
      this.setTriangleIndices(this._triangleOffset++, [endIndices[0][0], endIndices[0][3], endIndices[0][2]]);
      this.setTriangleIndices(this._triangleOffset++, [endIndices[1][0], endIndices[1][1], endIndices[1][2]]);
      this.setTriangleIndices(this._triangleOffset++, [endIndices[1][0], endIndices[1][2], endIndices[1][3]]);
      this._vertexOffset = vertexOffset;
    };
  }()
});
echarts.util.defaults(BarsGeometry.prototype, dynamicConvertMixin);
echarts.util.defaults(BarsGeometry.prototype, trianglesSortMixin);
export default BarsGeometry;