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

'use strict';

import H from '../parts/Globals.js';
import onSeriesMixin from '../mixins/on-series.js';

var noop = H.noop,
    seriesType = H.seriesType;

/**
 * @private
 * @class
 * @name Highcharts.seriesTypes.windbarb
 *
 * @augments Highcharts.Series
 */
seriesType('windbarb', 'column'

    /**
 * Wind barbs are a convenient way to represent wind speed and direction in one
 * graphical form. Wind direction is given by the stem direction, and wind speed
 * by the number and shape of barbs.
 *
 * @sample {highcharts|highstock} highcharts/demo/windbarb-series/
 *         Wind barb series
 *
 * @extends      plotOptions.column
 * @excluding    boostThreshold, marker, connectEnds, connectNulls,
 *               cropThreshold, dashStyle, gapSize, gapUnit, dataGrouping,
 *               linecap, shadow, stacking, step
 * @since        6.0.0
 * @product      highcharts highstock
 * @optionparent plotOptions.windbarb
 */
    , {
    /**
     * The line width of the wind barb symbols.
     */
        lineWidth: 2,
        /**
     * The id of another series in the chart that the wind barbs are projected
     * on. When `null`, the wind symbols are drawn on the X axis, but offset up
     * or down by the `yOffset` setting.
     *
     * @sample {highcharts|highstock} highcharts/plotoptions/windbarb-onseries
     *         Projected on area series
     *
     * @type {string|null}
     */
        onSeries: null,
        states: {
            hover: {
                lineWidthPlus: 0
            }
        },
        tooltip: {
        /**
         * The default point format for the wind barb tooltip. Note the
         * `point.beaufort` property that refers to the Beaufort wind scale. The
         * names can be internationalized by modifying
         * `Highcharts.seriesTypes.windbarb.prototype.beaufortNames`.
         */
            pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.value}</b> ({point.beaufort})<br/>'
        },
        /**
     * Pixel length of the stems.
     */
        vectorLength: 20,
        /**
     * Vertical offset from the cartesian position, in pixels. The default value
     * makes sure the symbols don't overlap the X axis when `onSeries` is
     * `null`, and that they don't overlap the linked series when `onSeries` is
     * given.
     */
        yOffset: -20,
        /**
     * Horizontal offset from the cartesian position, in pixels. When the chart
     * is inverted, this option allows translation like
     * [yOffset](#plotOptions.windbarb.yOffset) in non inverted charts.
     *
     * @since 6.1.0
     */
        xOffset: 0
    }, {
        pointArrayMap: ['value', 'direction'],
        parallelArrays: ['x', 'value', 'direction'],
        beaufortName: ['Calm', 'Light air', 'Light breeze',
            'Gentle breeze', 'Moderate breeze', 'Fresh breeze',
            'Strong breeze', 'Near gale', 'Gale', 'Strong gale', 'Storm',
            'Violent storm', 'Hurricane'],
        beaufortFloor: [0, 0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8,
            24.5, 28.5, 32.7],
        trackerGroups: ['markerGroup'],

        // Get presentational attributes.
        pointAttribs: function (point, state) {
            var options = this.options,
                stroke = point.color || this.color,
                strokeWidth = this.options.lineWidth;

            if (state) {
                stroke = options.states[state].color || stroke;
                strokeWidth =
                (options.states[state].lineWidth || strokeWidth) +
                (options.states[state].lineWidthPlus || 0);
            }

            return {
                'stroke': stroke,
                'stroke-width': strokeWidth
            };
        },
        markerAttribs: function () {
            return undefined;
        },
        getPlotBox: onSeriesMixin.getPlotBox,
        // Create a single wind arrow. It is later rotated around the zero
        // centerpoint.
        windArrow: function (point) {
            var knots = point.value * 1.943844,
                level = point.beaufortLevel,
                path,
                barbs,
                u = this.options.vectorLength / 20,
                pos = -10;

            if (point.isNull) {
                return [];
            }

            if (level === 0) {
                return this.chart.renderer.symbols.circle(
                    -10 * u,
                    -10 * u,
                    20 * u,
                    20 * u
                );
            }

            // The stem and the arrow head
            path = [
                'M', 0, 7 * u, // base of arrow
                'L', -1.5 * u, 7 * u,
                0, 10 * u,
                1.5 * u, 7 * u,
                0, 7 * u,
                0, -10 * u// top
            ];

            // For each full 50 knots, add a pennant
            barbs = (knots - knots % 50) / 50; // pennants
            if (barbs > 0) {
                while (barbs--) {
                    path.push(
                        pos === -10 ? 'L' : 'M',
                        0,
                        pos * u,
                        'L',
                        5 * u,
                        pos * u + 2,
                        'L',
                        0,
                        pos * u + 4

                    );

                    // Substract from the rest and move position for next
                    knots -= 50;
                    pos += 7;
                }
            }

            // For each full 10 knots, add a full barb
            barbs = (knots - knots % 10) / 10;
            if (barbs > 0) {
                while (barbs--) {
                    path.push(
                        pos === -10 ? 'L' : 'M',
                        0,
                        pos * u,
                        'L',
                        7 * u,
                        pos * u
                    );
                    knots -= 10;
                    pos += 3;
                }
            }

            // For each full 5 knots, add a half barb
            barbs = (knots - knots % 5) / 5; // half barbs
            if (barbs > 0) {
                while (barbs--) {
                    path.push(
                        pos === -10 ? 'L' : 'M',
                        0,
                        pos * u,
                        'L',
                        4 * u,
                        pos * u
                    );
                    knots -= 5;
                    pos += 3;
                }
            }
            return path;
        },

        translate: function () {
            var beaufortFloor = this.beaufortFloor,
                beaufortName = this.beaufortName;

            onSeriesMixin.translate.call(this);

            this.points.forEach(function (point) {
                var level = 0;

                // Find the beaufort level (zero based)
                for (; level < beaufortFloor.length; level++) {
                    if (beaufortFloor[level] > point.value) {
                        break;
                    }
                }
                point.beaufortLevel = level - 1;
                point.beaufort = beaufortName[level - 1];

            });

        },

        drawPoints: function () {
            var chart = this.chart,
                yAxis = this.yAxis,
                inverted = chart.inverted,
                shapeOffset = this.options.vectorLength / 2;

            this.points.forEach(function (point) {
                var plotX = point.plotX,
                    plotY = point.plotY;

                // Check if it's inside the plot area, but only for the X
                // dimension.
                if (chart.isInsidePlot(plotX, 0, false)) {

                    // Create the graphic the first time
                    if (!point.graphic) {
                        point.graphic = this.chart.renderer
                            .path()
                            .add(this.markerGroup);
                    }

                    // Position the graphic
                    point.graphic
                        .attr({
                            d: this.windArrow(point),
                            translateX: plotX + this.options.xOffset,
                            translateY: plotY + this.options.yOffset,
                            rotation: point.direction
                        })
                        .attr(this.pointAttribs(point));

                } else if (point.graphic) {
                    point.graphic = point.graphic.destroy();
                }

                // Set the tooltip anchor position
                point.tooltipPos = [
                    plotX + this.options.xOffset + (inverted && !this.onSeries ?
                        shapeOffset : 0),
                    plotY + this.options.yOffset - (inverted ? 0 :
                        shapeOffset + yAxis.pos - chart.plotTop)
                ]; // #6327
            }, this);
        },

        // Fade in the arrows on initializing series.
        animate: function (init) {
            if (init) {
                this.markerGroup.attr({
                    opacity: 0.01
                });
            } else {
                this.markerGroup.animate({
                    opacity: 1
                }, H.animObject(this.options.animation));

                this.animate = null;
            }
        },

        // Don't invert the marker group (#4960)
        invertGroups: noop
    }, {
        isValid: function () {
            return H.isNumber(this.value) && this.value >= 0;
        }
    });


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

/**
 * An array of data points for the series. For the `windbarb` series type,
 * points can be given in the following ways:
 *
 * 1. An array of arrays with 3 values. In this case, the values correspond to
 *    `x,value,direction`. If the first value is a string, it is applied as the
 *    name of the point, and the `x` value is inferred.
 *    ```js
 *       data: [
 *           [Date.UTC(2017, 0, 1, 0), 3.3, 90],
 *           [Date.UTC(2017, 0, 1, 1), 12.1, 180],
 *           [Date.UTC(2017, 0, 1, 2), 11.1, 270]
 *       ]
 *    ```
 *
 * 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.area.turboThreshold), this option is not
 *    available.
 *    ```js
 *       data: [{
 *           x: Date.UTC(2017, 0, 1, 0),
 *           value: 12.1,
 *           direction: 90
 *       }, {
 *           x: Date.UTC(2017, 0, 1, 1),
 *           value: 11.1,
 *           direction: 270
 *       }]
 *    ```
 *
 * @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|string),number,number>|*>}
 * @extends   series.line.data
 * @product   highcharts highstock
 * @apioption series.windbarb.data
 */

/**
 * The wind speed in meters per second.
 *
 * @type      {number}
 * @product   highcharts highstock
 * @apioption series.windbarb.data.value
 */

/**
 * The wind direction in degrees, where 0 is north (pointing towards south).
 *
 * @type      {number}
 * @product   highcharts highstock
 * @apioption series.windbarb.data.direction
 */
