/**
* Copyright 2012-2020, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var tube2mesh = require('gl-streamtube3d');
var createTubeMesh = tube2mesh.createTubeMesh;

var Lib = require('../../lib');
var parseColorScale = require('../../lib/gl_format_color').parseColorScale;
var extractOpts = require('../../components/colorscale').extractOpts;
var zip3 = require('../../plots/gl3d/zip3');

var axisName2scaleIndex = {xaxis: 0, yaxis: 1, zaxis: 2};

function Streamtube(scene, uid) {
    this.scene = scene;
    this.uid = uid;
    this.mesh = null;
    this.data = null;
}

var proto = Streamtube.prototype;

proto.handlePick = function(selection) {
    var sceneLayout = this.scene.fullSceneLayout;
    var dataScale = this.scene.dataScale;

    function fromDataScale(v, axisName) {
        var ax = sceneLayout[axisName];
        var scale = dataScale[axisName2scaleIndex[axisName]];
        return ax.l2c(v) / scale;
    }

    if(selection.object === this.mesh) {
        var pos = selection.data.position;
        var uvx = selection.data.velocity;

        selection.traceCoordinate = [
            fromDataScale(pos[0], 'xaxis'),
            fromDataScale(pos[1], 'yaxis'),
            fromDataScale(pos[2], 'zaxis'),

            fromDataScale(uvx[0], 'xaxis'),
            fromDataScale(uvx[1], 'yaxis'),
            fromDataScale(uvx[2], 'zaxis'),

            // u/v/w norm
            selection.data.intensity * this.data._normMax,
            // divergence
            selection.data.divergence
        ];

        selection.textLabel = this.data.hovertext || this.data.text;

        return true;
    }
};

function 
(vec) {
    var len = vec.length;
    var s;

    if(len > 2) {
        s = vec.slice(1, len - 1);
    } else if(len === 2) {
        s = [(vec[0] + vec[1]) / 2];
    } else {
        s = vec;
    }
    return s;
}

function getBoundPads(vec) {
    var len = vec.length;
    if(len === 1) {
        return [0.5, 0.5];
    } else {
        return [vec[1] - vec[0], vec[len - 1] - vec[len - 2]];
    }
}

function convert(scene, trace) {
    var sceneLayout = scene.fullSceneLayout;
    var dataScale = scene.dataScale;
    var len = trace._len;
    var tubeOpts = {};

    function toDataCoords(arr, axisName) {
        var ax = sceneLayout[axisName];
        var scale = dataScale[axisName2scaleIndex[axisName]];
        return Lib.simpleMap(arr, function(v) { return ax.d2l(v) * scale; });
    }

    tubeOpts.vectors = zip3(
        toDataCoords(trace._u, 'xaxis'),
        toDataCoords(trace._v, 'yaxis'),
        toDataCoords(trace._w, 'zaxis'),
        len
    );

    // Over-specified mesh case, this would error in tube2mesh
    if(!len) {
        return {
            positions: [],
            cells: []
        };
    }

    var meshx = toDataCoords(trace._Xs, 'xaxis');
    var meshy = toDataCoords(trace._Ys, 'yaxis');
    var meshz = toDataCoords(trace._Zs, 'zaxis');

    tubeOpts.meshgrid = [meshx, meshy, meshz];
    tubeOpts.gridFill = trace._gridFill;

    var slen = trace._slen;
    if(slen) {
        tubeOpts.startingPositions = zip3(
            toDataCoords(trace._startsX, 'xaxis'),
            toDataCoords(trace._startsY, 'yaxis'),
            toDataCoords(trace._startsZ, 'zaxis')
        );
    } else {
        // Default starting positions:
        //
        // if len>2, cut xz plane at min-y,
        // takes all x/y/z pts on that plane except those on the edges
        // to generate "well-defined" tubes,
        //
        // if len=2, take position halfway between two the pts,
        //
        // if len=1, take that pt
        var sy0 = meshy[0];
        var sx = getDfltStartingPositions(meshx);
        var sz = getDfltStartingPositions(meshz);
        var startingPositions = new Array(sx.length * sz.length);
        var m = 0;

        for(var i = 0; i < sx.length; i++) {
            for(var k = 0; k < sz.length; k++) {
                startingPositions[m++] = [sx[i], sy0, sz[k]];
            }
        }
        tubeOpts.startingPositions = startingPositions;
    }

    tubeOpts.colormap = parseColorScale(trace);
    tubeOpts.tubeSize = trace.sizeref;
    tubeOpts.maxLength = trace.maxdisplayed;

    // add some padding around the bounds
    // to e.g. allow tubes starting from a slice of the x/y/z mesh
    // to go beyond bounds a little bit w/o getting clipped
    var xbnds = toDataCoords(trace._xbnds, 'xaxis');
    var ybnds = toDataCoords(trace._ybnds, 'yaxis');
    var zbnds = toDataCoords(trace._zbnds, 'zaxis');
    var xpads = getBoundPads(meshx);
    var ypads = getBoundPads(meshy);
    var zpads = getBoundPads(meshz);

    var bounds = [
        [xbnds[0] - xpads[0], ybnds[0] - ypads[0], zbnds[0] - zpads[0]],
        [xbnds[1] + xpads[1], ybnds[1] + ypads[1], zbnds[1] + zpads[1]]
    ];

    var meshData = tube2mesh(tubeOpts, bounds);

    // N.B. cmin/cmax correspond to the min/max vector norm
    // in the u/v/w arrays, which in general is NOT equal to max
    // intensity that colors the tubes.
    var cOpts = extractOpts(trace);
    meshData.vertexIntensityBounds = [cOpts.min / trace._normMax, cOpts.max / trace._normMax];

    // pass gl-mesh3d lighting attributes
    var lp = trace.lightposition;
    meshData.lightPosition = [lp.x, lp.y, lp.z];
    meshData.ambient = trace.lighting.ambient;
    meshData.diffuse = trace.lighting.diffuse;
    meshData.specular = trace.lighting.specular;
    meshData.roughness = trace.lighting.roughness;
    meshData.fresnel = trace.lighting.fresnel;
    meshData.opacity = trace.opacity;

    // stash autorange pad value
    trace._pad = meshData.tubeScale * trace.sizeref * 2;

    return meshData;
}

proto.update = function(data) {
    this.data = data;

    var meshData = convert(this.scene, data);
    this.mesh.update(meshData);
};

proto.dispose = function() {
    this.scene.glplot.remove(this.mesh);
    this.mesh.dispose();
};

function createStreamtubeTrace(scene, data) {
    var gl = scene.glplot.gl;

    var meshData = convert(scene, data);
    var mesh = createTubeMesh(gl, meshData);

    var streamtube = new Streamtube(scene, data.uid);
    streamtube.mesh = mesh;
    streamtube.data = data;
    mesh._trace = streamtube;

    scene.glplot.add(mesh);

    return streamtube;
}

module.exports = createStreamtubeTrace;

getDfltStartingPositions