'use strict';

var createPointCloudRenderer = require('../../../stackgl_modules').gl_pointcloud2d;

var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray;
var str2RGBArray = require('../../lib/str2rgbarray');
var findExtremes = require('../../plots/cartesian/autorange').findExtremes;
var getTraceColor = require('../scatter/get_trace_color');

function Pointcloud(scene, uid) {
    this.scene = scene;
    this.uid = uid;
    this.type = 'pointcloud';

    this.pickXData = [];
    this.pickYData = [];
    this.xData = [];
    this.yData = [];
    this.textLabels = [];
    this.color = 'rgb(0, 0, 0)';
    this.name = '';
    this.hoverinfo = 'all';

    this.idToIndex = new Int32Array(0);
    this.bounds = [0, 0, 0, 0];

    this.pointcloudOptions = {
        positions: new Float32Array(0),
        idToIndex: this.idToIndex,
        sizemin: 0.5,
        sizemax: 12,
        color: [0, 0, 0, 1],
        areaRatio: 1,
        borderColor: [0, 0, 0, 1]
    };
    this.pointcloud = createPointCloudRenderer(scene.glplot, this.pointcloudOptions);
    this.pointcloud._trace = this; // scene2d requires this prop
}

var proto = Pointcloud.prototype;

proto.handlePick = function(pickResult) {
    var index = this.idToIndex[pickResult.pointId];

    // prefer the readout from XY, if present
    return {
        trace: this,
        dataCoord: pickResult.dataCoord,
        traceCoord: this.pickXYData ?
            [this.pickXYData[index * 2], this.pickXYData[index * 2 + 1]] :
            [this.pickXData[index], this.pickYData[index]],
        textLabel: isArrayOrTypedArray(this.textLabels) ?
            this.textLabels[index] :
            this.textLabels,
        color: this.color,
        name: this.name,
        pointIndex: index,
        hoverinfo: this.hoverinfo
    };
};

proto.update = function(options) {
    this.index = options.index;
    this.textLabels = options.text;
    this.name = options.name;
    this.hoverinfo = options.hoverinfo;
    this.bounds = [Infinity, Infinity, -Infinity, -Infinity];

    this.updateFast(options);

    this.color = getTraceColor(options, {});
};

proto.updateFast = function(options) {
    var x = this.xData = this.pickXData = options.x;
    var y = this.yData = this.pickYData = options.y;
    var xy = this.pickXYData = options.xy;

    var userBounds = options.xbounds && options.ybounds;
    var index = options.indices;

    var len;
    var idToIndex;
    var positions;
    var bounds = this.bounds;

    var xx, yy, i;

    if(xy) {
        positions = xy;

        // dividing xy.length by 2 and truncating to integer if xy.length was not even
        len = xy.length >>> 1;

        if(userBounds) {
            bounds[0] = options.xbounds[0];
            bounds[2] = options.xbounds[1];
            bounds[1] = options.ybounds[0];
            bounds[3] = options.ybounds[1];
        } else {
            for(i = 0; i < len; i++) {
                xx = positions[i * 2];
                yy = positions[i * 2 + 1];

                if(xx < bounds[0]) bounds[0] = xx;
                if(xx > bounds[2]) bounds[2] = xx;
                if(yy < bounds[1]) bounds[1] = yy;
                if(yy > bounds[3]) bounds[3] = yy;
            }
        }

        if(index) {
            idToIndex = index;
        } else {
            idToIndex = new Int32Array(len);

            for(i = 0; i < len; i++) {
                idToIndex[i] = i;
            }
        }
    } else {
        len = x.length;

        positions = new Float32Array(2 * len);
        idToIndex = new Int32Array(len);

        for(i = 0; i < len; i++) {
            xx = x[i];
            yy = y[i];

            idToIndex[i] = i;

            positions[i * 2] = xx;
            positions[i * 2 + 1] = yy;

            if(xx < bounds[0]) bounds[0] = xx;
            if(xx > bounds[2]) bounds[2] = xx;
            if(yy < bounds[1]) bounds[1] = yy;
            if(yy > bounds[3]) bounds[3] = yy;
        }
    }

    this.idToIndex = idToIndex;
    this.pointcloudOptions.idToIndex = idToIndex;

    this.pointcloudOptions.positions = positions;

    var markerColor = str2RGBArray(options.marker.color);
    var borderColor = str2RGBArray(options.marker.border.color);
    var opacity = options.opacity * options.marker.opacity;

    markerColor[3] *= opacity;
    this.pointcloudOptions.color = markerColor;

    // detect blending from the number of points, if undefined
    // because large data with blending hits performance
    var blend = options.marker.blend;
    if(blend === null) {
        var maxPoints = 100;
        blend = x.length < maxPoints || y.length < maxPoints;
    }
    this.pointcloudOptions.blend = blend;

    borderColor[3] *= opacity;
    this.pointcloudOptions.borderColor = borderColor;

    var markerSizeMin = options.marker.sizemin;
    var markerSizeMax = Math.max(options.marker.sizemax, options.marker.sizemin);
    this.pointcloudOptions.sizeMin = markerSizeMin;
    this.pointcloudOptions.sizeMax = markerSizeMax;
    this.pointcloudOptions.areaRatio = options.marker.border.arearatio;

    this.pointcloud.update(this.pointcloudOptions);

    // add item for autorange routine
    var xa = this.scene.xaxis;
    var ya = this.scene.yaxis;
    var pad = markerSizeMax / 2 || 0.5;
    options._extremes[xa._id] = findExtremes(xa, [bounds[0], bounds[2]], {ppad: pad});
    options._extremes[ya._id] = findExtremes(ya, [bounds[1], bounds[3]], {ppad: pad});
};

proto.dispose = function() {
    this.pointcloud.dispose();
};

function createPointcloud(scene, data) {
    var plot = new Pointcloud(scene, data.uid);
    plot.update(data);
    return plot;
}

module.exports = createPointcloud;
