/**
 * 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;