/* *
 *
 *  (c) 2010-2019 Torstein Honsi
 *
 *  License: www.highcharts.com/license
 *
 * */

'use strict';

import H from '../parts/Globals.js';
import '../parts/Utilities.js';
import '../parts/Options.js';
import '../parts/Point.js';
import '../parts/Series.js';
import '../parts/Legend.js';
import './ColorSeriesMixin.js';

var colorPointMixin = H.colorPointMixin,
    colorSeriesMixin = H.colorSeriesMixin,
    LegendSymbolMixin = H.LegendSymbolMixin,
    merge = H.merge,
    noop = H.noop,
    pick = H.pick,
    Series = H.Series,
    seriesType = H.seriesType,
    seriesTypes = H.seriesTypes;

/**
 * @private
 * @class
 * @name Highcharts.seriesTypes.heatmap
 *
 * @augments Highcharts.Series
 */
seriesType(
    'heatmap',
    'scatter',

    /**
     * A heatmap is a graphical representation of data where the individual
     * values contained in a matrix are represented as colors.
     *
     * @sample highcharts/demo/heatmap/
     *         Simple heatmap
     * @sample highcharts/demo/heatmap-canvas/
     *         Heavy heatmap
     *
     * @extends      plotOptions.scatter
     * @excluding    animationLimit, connectEnds, connectNulls, dashStyle,
     *               findNearestPointBy, getExtremesFromAll, jitter, linecap,
     *               lineWidth, marker, pointInterval, pointIntervalUnit,
     *               pointRange, pointStart, shadow, softThreshold, stacking,
     *               step, threshold
     * @product      highcharts highmaps
     * @optionparent plotOptions.heatmap
     */
    {

        /**
         * Animation is disabled by default on the heatmap series.
         */
        animation: false,

        /**
         * The border width for each heat map item.
         */
        borderWidth: 0,

        /**
         * Padding between the points in the heatmap.
         *
         * @type      {number}
         * @default   0
         * @since     6.0
         * @apioption plotOptions.heatmap.pointPadding
         */

        /**
         * The main color of the series. In heat maps this color is rarely used,
         * as we mostly use the color to denote the value of each point. Unless
         * options are set in the [colorAxis](#colorAxis), the default value
         * is pulled from the [options.colors](#colors) array.
         *
         * @type      {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
         * @since     4.0
         * @product   highcharts
         * @apioption plotOptions.heatmap.color
         */

        /**
         * The column size - how many X axis units each column in the heatmap
         * should span.
         *
         * @sample {highcharts} maps/demo/heatmap/
         *         One day
         * @sample {highmaps} maps/demo/heatmap/
         *         One day
         *
         * @type      {number}
         * @default   1
         * @since     4.0
         * @product   highcharts highmaps
         * @apioption plotOptions.heatmap.colsize
         */

        /**
         * The row size - how many Y axis units each heatmap row should span.
         *
         * @sample {highcharts} maps/demo/heatmap/
         *         1 by default
         * @sample {highmaps} maps/demo/heatmap/
         *         1 by default
         *
         * @type      {number}
         * @default   1
         * @since     4.0
         * @product   highcharts highmaps
         * @apioption plotOptions.heatmap.rowsize
         */

        /**
         * The color applied to null points. In styled mode, a general CSS class
         * is applied instead.
         *
         * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
         */
        nullColor: '#f7f7f7',

        dataLabels: {
            /** @ignore-option */
            formatter: function () { // #2945
                return this.point.value;
            },
            /** @ignore-option */
            inside: true,
            /** @ignore-option */
            verticalAlign: 'middle',
            /** @ignore-option */
            crop: false,
            /** @ignore-option */
            overflow: false,
            /** @ignore-option */
            padding: 0 // #3837
        },

        /** @ignore */
        marker: null,

        /**
         * @ignore
         */
        pointRange: null, // dynamically set to colsize by default

        tooltip: {
            pointFormat: '{point.x}, {point.y}: {point.value}<br/>'
        },

        states: {

            hover: {

                /** @ignore-option */
                halo: false, // #3406, halo is disabled on heatmaps by default

                /**
                 * How much to brighten the point on interaction. Requires the
                 * main color to be defined in hex or rgb(a) format.
                 *
                 * In styled mode, the hover brightening is by default replaced
                 * with a fill-opacity set in the `.highcharts-point:hover`
                 * rule.
                 */
                brightness: 0.2
            }

        }

    }, merge(colorSeriesMixin, {

        pointArrayMap: ['y', 'value'],
        hasPointSpecificOptions: true,
        getExtremesFromAll: true,
        directTouch: true,

        /**
         * Override the init method to add point ranges on both axes.
         *
         * @private
         * @function Highcharts.seriesTypes.heatmap#init
         */
        init: function () {
            var options;

            seriesTypes.scatter.prototype.init.apply(this, arguments);

            options = this.options;
            // #3758, prevent resetting in setData
            options.pointRange = pick(options.pointRange, options.colsize || 1);
            // general point range
            this.yAxis.axisPointRange = options.rowsize || 1;
        },

        /**
         * @private
         * @function Highcharts.seriesTypes.heatmap#translate
         */
        translate: function () {
            var series = this,
                options = series.options,
                xAxis = series.xAxis,
                yAxis = series.yAxis,
                seriesPointPadding = options.pointPadding || 0,
                between = function (x, a, b) {
                    return Math.min(Math.max(a, x), b);
                },
                pointPlacement = series.pointPlacementToXValue(); // #7860

            series.generatePoints();

            series.points.forEach(function (point) {
                var xPad = (options.colsize || 1) / 2,
                    yPad = (options.rowsize || 1) / 2,
                    x1 = between(
                        Math.round(
                            xAxis.len -
                        xAxis.translate(point.x - xPad,
                            0,
                            1,
                            0,
                            1,
                            -pointPlacement)
                        ),
                        -xAxis.len, 2 * xAxis.len
                    ),
                    x2 = between(
                        Math.round(
                            xAxis.len -
                        xAxis.translate(point.x + xPad,
                            0,
                            1,
                            0,
                            1,
                            -pointPlacement)
                        ),
                        -xAxis.len, 2 * xAxis.len
                    ),
                    y1 = between(
                        Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)),
                        -yAxis.len, 2 * yAxis.len
                    ),
                    y2 = between(
                        Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1)),
                        -yAxis.len, 2 * yAxis.len
                    ),
                    pointPadding = pick(point.pointPadding, seriesPointPadding);

                // Set plotX and plotY for use in K-D-Tree and more
                point.plotX = point.clientX = (x1 + x2) / 2;
                point.plotY = (y1 + y2) / 2;

                point.shapeType = 'rect';
                point.shapeArgs = {
                    x: Math.min(x1, x2) + pointPadding,
                    y: Math.min(y1, y2) + pointPadding,
                    width: Math.abs(x2 - x1) - pointPadding * 2,
                    height: Math.abs(y2 - y1) - pointPadding * 2
                };
            });

            series.translateColors();
        },

        /**
         * @private
         * @function Highcharts.seriesTypes.heatmap#drawPoints
         */
        drawPoints: function () {

            // In styled mode, use CSS, otherwise the fill used in the style
            // sheet will take precedence over the fill attribute.
            var func = this.chart.styledMode ? 'css' : 'attr';

            seriesTypes.column.prototype.drawPoints.call(this);

            this.points.forEach(function (point) {
                point.graphic[func](this.colorAttribs(point));
            }, this);
        },

        // Define hasData function for non-cartesian series.
        // Returns true if the series has points at all.
        hasData: function () {
            return !!this.processedXData.length; // != 0
        },

        // Override to also allow null points, used when building the k-d-tree
        // for tooltips in boost mode.
        getValidPoints: function (points, insideOnly) {
            return Series.prototype.getValidPoints.call(
                this,
                points,
                insideOnly,
                true
            );
        },

        /**
         * @ignore
         * @deprecated
         * @function Highcharts.seriesTypes.heatmap#animate
         */
        animate: noop,

        /**
         * @ignore
         * @deprecated
         * @function Highcharts.seriesTypes.heatmap#getBox
         */
        getBox: noop,

        /**
         * @private
         * @borrows Highcharts.LegendSymbolMixin.drawRectangle as Highcharts.seriesTypes.heatmap#drawLegendSymbol
         */
        drawLegendSymbol: LegendSymbolMixin.drawRectangle,

        /**
         * @private
         * @borrows Highcharts.seriesTypes.column#alignDataLabel as Highcharts.seriesTypes.heatmap#alignDataLabel
         */
        alignDataLabel: seriesTypes.column.prototype.alignDataLabel,

        /**
         * @private
         * @function Highcharts.seriesTypes.heatmap#getExtremes
         */
        getExtremes: function () {
        // Get the extremes from the value data
            Series.prototype.getExtremes.call(this, this.valueData);
            this.valueMin = this.dataMin;
            this.valueMax = this.dataMax;

            // Get the extremes from the y data
            Series.prototype.getExtremes.call(this);
        }

    }), H.extend({

        /**
         * @private
         * @function Highcharts.Point#haloPath
         *
         * @param {number} size
         *
         * @return {Highcharts.SVGPathArray}
         */
        haloPath: function (size) {
            if (!size) {
                return [];
            }
            var rect = this.shapeArgs;

            return [
                'M', rect.x - size, rect.y - size,
                'L', rect.x - size, rect.y + rect.height + size,
                rect.x + rect.width + size, rect.y + rect.height + size,
                rect.x + rect.width + size, rect.y - size,
                'Z'
            ];
        }
    }, colorPointMixin)
);

/**
 * A `heatmap` series. If the [type](#series.heatmap.type) option is
 * not specified, it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.heatmap
 * @excluding dataParser, dataURL, marker, pointRange, stack
 * @product   highcharts highmaps
 * @apioption series.heatmap
 */

/**
 * An array of data points for the series. For the `heatmap` series
 * type, points can be given in the following ways:
 *
 * 1.  An array of arrays with 3 or 2 values. In this case, the values
 * correspond to `x,y,value`. If the first value is a string, it is
 * applied as the name of the point, and the `x` value is inferred.
 * The `x` value can also be omitted, in which case the inner arrays
 * should be of length 2\. Then the `x` value is automatically calculated,
 * either starting at 0 and incremented by 1, or from `pointStart`
 * and `pointInterval` given in the series options.
 *
 *  ```js
 *     data: [
 *         [0, 9, 7],
 *         [1, 10, 4],
 *         [2, 6, 3]
 *     ]
 *  ```
 *
 * 2.  An array of objects with named values. The following snippet shows only a
 * few settings, see the complete options set below. If the total number of data
 * points exceeds the series' [turboThreshold](#series.heatmap.turboThreshold),
 * this option is not available.
 *
 *  ```js
 *     data: [{
 *         x: 1,
 *         y: 3,
 *         value: 10,
 *         name: "Point2",
 *         color: "#00FF00"
 *     }, {
 *         x: 1,
 *         y: 7,
 *         value: 10,
 *         name: "Point1",
 *         color: "#FF00FF"
 *     }]
 *  ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @type      {Array<Array<number>|*>}
 * @extends   series.line.data
 * @excluding marker
 * @product   highcharts highmaps
 * @apioption series.heatmap.data
 */

/**
 * The color of the point. In heat maps the point color is rarely set
 * explicitly, as we use the color to denote the `value`. Options for
 * this are set in the [colorAxis](#colorAxis) configuration.
 *
 * @type      {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
 * @product   highcharts highmaps
 * @apioption series.heatmap.data.color
 */

/**
 * The value of the point, resulting in a color controled by options
 * as set in the [colorAxis](#colorAxis) configuration.
 *
 * @type      {number}
 * @product   highcharts highmaps
 * @apioption series.heatmap.data.value
 */

/**
 * The x value of the point. For datetime axes,
 * the X value is the timestamp in milliseconds since 1970.
 *
 * @type      {number}
 * @product   highcharts highmaps
 * @apioption series.heatmap.data.x
 */

/**
 * The y value of the point.
 *
 * @type      {number}
 * @product   highcharts highmaps
 * @apioption series.heatmap.data.y
 */

/**
 * Point padding for a single point.
 *
 * @sample maps/plotoptions/tilemap-pointpadding
 *         Point padding on tiles
 *
 * @type      {number}
 * @product   highcharts highmaps
 * @apioption series.heatmap.data.pointPadding
 */
