/**
 * @file defines echarts Heatmap Chart
 * @author Ovilia (me@zhangwenli.com)
 * Inspired by https://github.com/mourner/simpleheat
 *
 * @module
 */
define(function (require) {
    var defaultOptions = {
        blurSize: 30,

        // gradientColors is either shaped of ['blue', 'cyan', 'lime', 'yellow', 'red']
        // or [{
        //    offset: 0.2,
        //    color: 'blue'
        // }, {
        //    offset 0.8,
        //    color: 'cyan'
        // }]
        gradientColors: ['blue', 'cyan', 'lime', 'yellow', 'red'],
        minAlpha: 0.05,
        valueScale: 1,
        opacity: 1
    };

    var BRUSH_SIZE = 20;
    var GRADIENT_LEVELS = 256;

    /**
     * Heatmap Chart
     *
     * @class
     * @param {Object} opt options
     */
    function Heatmap(opt) {
        this.option = opt;
        if (opt) {
            for (var i in defaultOptions) {
                if (opt[i] !== undefined) {
                    this.option[i] = opt[i];
                } else {
                    this.option[i] = defaultOptions[i];
                }
            }
        } else {
            this.option = defaultOptions;
        }
    }

    Heatmap.prototype = {
        /**
         * Renders Heatmap and returns the rendered canvas
         * @param {Array} [x, y, value] array of data
         * @param {number} canvas width
         * @param {number} canvas height
         * @return {Object} rendered canvas
         */
        getCanvas: function(data, width, height) {
            var brush = this._getBrush();
            var gradient = this._getGradient();
            var r = BRUSH_SIZE + this.option.blurSize;

            var canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            var ctx = canvas.getContext('2d');

            var len = data.length;
            for (var i = 0; i < len; ++i) {
                var p = data[i];
                var x = p[0];
                var y = p[1];
                var value = p[2];

                // calculate alpha using value
                var alpha = Math.min(1, Math.max(value * this.option.valueScale
                    || this.option.minAlpha, this.option.minAlpha));

                // draw with the circle brush with alpha
                ctx.globalAlpha = alpha;
                ctx.drawImage(brush, x - r, y - r);
            }

            // colorize the canvas using alpha value and set with gradient
            var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
            var pixels = imageData.data;
            var len = pixels.length / 4;
            while(len--) {
                var id = len * 4 + 3;
                var alpha = pixels[id] / 256;
                var colorOffset = Math.floor(alpha * (GRADIENT_LEVELS - 1));
                pixels[id - 3] = gradient[colorOffset * 4];     // red
                pixels[id - 2] = gradient[colorOffset * 4 + 1]; // green
                pixels[id - 1] = gradient[colorOffset * 4 + 2]; // blue
                pixels[id] *= this.option.opacity;              // alpha
            }
            ctx.putImageData(imageData, 0, 0);

            return canvas;
        },

        /**
         * get canvas of a black circle brush used for canvas to draw later
         * @private
         * @returns {Object} circle brush canvas
         */
        _getBrush: function() {
            if (!this._brushCanvas) {
                this._brushCanvas = document.createElement('canvas');

                // set brush size
                var r = BRUSH_SIZE + this.option.blurSize;
                var d = r * 2;
                this._brushCanvas.width = d;
                this._brushCanvas.height = d;

                var ctx = this._brushCanvas.getContext('2d');

                // in order to render shadow without the distinct circle,
                // draw the distinct circle in an invisible place,
                // and use shadowOffset to draw shadow in the center of the canvas
                ctx.shadowOffsetX = d;
                ctx.shadowBlur = this.option.blurSize;
                // draw the shadow in black, and use alpha and shadow blur to generate
                // color in color map
                ctx.shadowColor = 'black';

                // draw circle in the left to the canvas
                ctx.beginPath();
                ctx.arc(-r, r, BRUSH_SIZE, 0, Math.PI * 2, true);
                ctx.closePath();
                ctx.fill();
            }
            return this._brushCanvas;
        },

        /**
         * get gradient color map
         * @private
         * @returns {array} gradient color pixels
         */
        _getGradient: function() {
            if (!this._gradientPixels) {
                var levels = GRADIENT_LEVELS;
                var canvas = document.createElement('canvas');
                canvas.width = 1;
                canvas.height = levels;
                var ctx = canvas.getContext('2d');
                
                // add color to gradient stops
                var gradient = ctx.createLinearGradient(0, 0, 0, levels);
                var len = this.option.gradientColors.length;
                for (var i = 0; i < len; ++i) {
                    if (typeof this.option.gradientColors[i] === 'string') {
                        gradient.addColorStop((i + 1) / len,
                            this.option.gradientColors[i]);
                    } else {
                        gradient.addColorStop(this.option.gradientColors[i].offset,
                            this.option.gradientColors[i].color);
                    }
                }

                ctx.fillStyle = gradient;
                ctx.fillRect(0, 0, 1, levels);
                this._gradientPixels = ctx.getImageData(0, 0, 1, levels).data;
            }
            return this._gradientPixels;
        }
    };

    return Heatmap;
});
