/**
 * @license Highstock JS v8.2.0 (2020-08-20)
 *
 * Indicator series type for Highstock
 *
 * (c) 2010-2019 Paweł Dalek
 *
 * License: www.highcharts.com/license
 */
'use strict';
(function (factory) {
    if (typeof module === 'object' && module.exports) {
        factory['default'] = factory;
        module.exports = factory;
    } else if (typeof define === 'function' && define.amd) {
        define('highcharts/indicators/volume-by-price', ['highcharts', 'highcharts/modules/stock'], function (Highcharts) {
            factory(Highcharts);
            factory.Highcharts = Highcharts;
            return factory;
        });
    } else {
        factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
    }
}(function (Highcharts) {
    var _modules = Highcharts ? Highcharts._modules : {};
    function _registerModule(obj, path, args, fn) {
        if (!obj.hasOwnProperty(path)) {
            obj[path] = fn.apply(null, args);
        }
    }
    _registerModule(_modules, 'Stock/Indicators/VBPIndicator.js', [_modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (H, Point, U) {
        /* *
         *
         *  (c) 2010-2020 Paweł Dalek
         *
         *  Volume By Price (VBP) indicator for Highstock
         *
         *  License: www.highcharts.com/license
         *
         *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
         *
         * */
        var addEvent = U.addEvent,
            animObject = U.animObject,
            arrayMax = U.arrayMax,
            arrayMin = U.arrayMin,
            correctFloat = U.correctFloat,
            error = U.error,
            extend = U.extend,
            isArray = U.isArray,
            seriesType = U.seriesType;
        /* eslint-disable require-jsdoc */
        // Utils
        function arrayExtremesOHLC(data) {
            var dataLength = data.length,
                min = data[0][3],
                max = min,
                i = 1,
                currentPoint;
            for (; i < dataLength; i++) {
                currentPoint = data[i][3];
                if (currentPoint < min) {
                    min = currentPoint;
                }
                if (currentPoint > max) {
                    max = currentPoint;
                }
            }
            return {
                min: min,
                max: max
            };
        }
        /* eslint-enable require-jsdoc */
        var abs = Math.abs,
            noop = H.noop,
            columnPrototype = H.seriesTypes.column.prototype;
        /**
         * The Volume By Price (VBP) series type.
         *
         * @private
         * @class
         * @name Highcharts.seriesTypes.vbp
         *
         * @augments Highcharts.Series
         */
        seriesType('vbp', 'sma', 
        /**
         * Volume By Price indicator.
         *
         * This series requires `linkedTo` option to be set.
         *
         * @sample stock/indicators/volume-by-price
         *         Volume By Price indicator
         *
         * @extends      plotOptions.sma
         * @since        6.0.0
         * @product      highstock
         * @requires     stock/indicators/indicators
         * @requires     stock/indicators/volume-by-price
         * @optionparent plotOptions.vbp
         */
        {
            /**
             * @excluding index, period
             */
            params: {
                /**
                 * The number of price zones.
                 */
                ranges: 12,
                /**
                 * The id of volume series which is mandatory. For example using
                 * OHLC data, volumeSeriesID='volume' means the indicator will be
                 * calculated using OHLC and volume values.
                 */
                volumeSeriesID: 'volume'
            },
            /**
             * The styles for lines which determine price zones.
             */
            zoneLines: {
                /**
                 * Enable/disable zone lines.
                 */
                enabled: true,
                /**
                 * Specify the style of zone lines.
                 *
                 * @type    {Highcharts.CSSObject}
                 * @default {"color": "#0A9AC9", "dashStyle": "LongDash", "lineWidth": 1}
                 */
                styles: {
                    /** @ignore-options */
                    color: '#0A9AC9',
                    /** @ignore-options */
                    dashStyle: 'LongDash',
                    /** @ignore-options */
                    lineWidth: 1
                }
            },
            /**
             * The styles for bars when volume is divided into positive/negative.
             */
            volumeDivision: {
                /**
                 * Option to control if volume is divided.
                 */
                enabled: true,
                styles: {
                    /**
                     * Color of positive volume bars.
                     *
                     * @type {Highcharts.ColorString}
                     */
                    positiveColor: 'rgba(144, 237, 125, 0.8)',
                    /**
                     * Color of negative volume bars.
                     *
                     * @type {Highcharts.ColorString}
                     */
                    negativeColor: 'rgba(244, 91, 91, 0.8)'
                }
            },
            // To enable series animation; must be animationLimit > pointCount
            animationLimit: 1000,
            enableMouseTracking: false,
            pointPadding: 0,
            zIndex: -1,
            crisp: true,
            dataGrouping: {
                enabled: false
            },
            dataLabels: {
                allowOverlap: true,
                enabled: true,
                format: 'P: {point.volumePos:.2f} | N: {point.volumeNeg:.2f}',
                padding: 0,
                style: {
                    /** @internal */
                    fontSize: '7px'
                },
                verticalAlign: 'top'
            }
        }, 
        /**
         * @lends Highcharts.Series#
         */
        {
            nameBase: 'Volume by Price',
            bindTo: {
                series: false,
                eventName: 'afterSetExtremes'
            },
            calculateOn: 'render',
            markerAttribs: noop,
            drawGraph: noop,
            getColumnMetrics: columnPrototype.getColumnMetrics,
            crispCol: columnPrototype.crispCol,
            init: function (chart) {
                var indicator = this,
                    params,
                    baseSeries,
                    volumeSeries;
                H.seriesTypes.sma.prototype.init.apply(indicator, arguments);
                params = indicator.options.params;
                baseSeries = indicator.linkedParent;
                volumeSeries = chart.get(params.volumeSeriesID);
                indicator.addCustomEvents(baseSeries, volumeSeries);
                return indicator;
            },
            // Adds events related with removing series
            addCustomEvents: function (baseSeries, volumeSeries) {
                var indicator = this;
                /* eslint-disable require-jsdoc */
                function toEmptyIndicator() {
                    indicator.chart.redraw();
                    indicator.setData([]);
                    indicator.zoneStarts = [];
                    if (indicator.zoneLinesSVG) {
                        indicator.zoneLinesSVG.destroy();
                        delete indicator.zoneLinesSVG;
                    }
                }
                /* eslint-enable require-jsdoc */
                // If base series is deleted, indicator series data is filled with
                // an empty array
                indicator.dataEventsToUnbind.push(addEvent(baseSeries, 'remove', function () {
                    toEmptyIndicator();
                }));
                // If volume series is deleted, indicator series data is filled with
                // an empty array
                if (volumeSeries) {
                    indicator.dataEventsToUnbind.push(addEvent(volumeSeries, 'remove', function () {
                        toEmptyIndicator();
                    }));
                }
                return indicator;
            },
            // Initial animation
            animate: function (init) {
                var series = this,
                    inverted = series.chart.inverted,
                    group = series.group,
                    attr = {},
                    translate,
                    position;
                if (!init && group) {
                    translate = inverted ? 'translateY' : 'translateX';
                    position = inverted ? series.yAxis.top : series.xAxis.left;
                    group['forceAnimate:' + translate] = true;
                    attr[translate] = position;
                    group.animate(attr, extend(animObject(series.options.animation), {
                        step: function (val, fx) {
                            series.group.attr({
                                scaleX: Math.max(0.001, fx.pos)
                            });
                        }
                    }));
                }
            },
            drawPoints: function () {
                var indicator = this;
                if (indicator.options.volumeDivision.enabled) {
                    indicator.posNegVolume(true, true);
                    columnPrototype.drawPoints.apply(indicator, arguments);
                    indicator.posNegVolume(false, false);
                }
                columnPrototype.drawPoints.apply(indicator, arguments);
            },
            // Function responsible for dividing volume into positive and negative
            posNegVolume: function (initVol, pos) {
                var indicator = this, signOrder = pos ?
                        ['positive', 'negative'] :
                        ['negative', 'positive'], volumeDivision = indicator.options.volumeDivision, pointLength = indicator.points.length, posWidths = [], negWidths = [], i = 0, pointWidth, priceZone, wholeVol, point;
                if (initVol) {
                    indicator.posWidths = posWidths;
                    indicator.negWidths = negWidths;
                }
                else {
                    posWidths = indicator.posWidths;
                    negWidths = indicator.negWidths;
                }
                for (; i < pointLength; i++) {
                    point = indicator.points[i];
                    point[signOrder[0] + 'Graphic'] = point.graphic;
                    point.graphic = point[signOrder[1] + 'Graphic'];
                    if (initVol) {
                        pointWidth = point.shapeArgs.width;
                        priceZone = indicator.priceZones[i];
                        wholeVol = priceZone.wholeVolumeData;
                        if (wholeVol) {
                            posWidths.push(pointWidth / wholeVol * priceZone.positiveVolumeData);
                            negWidths.push(pointWidth / wholeVol * priceZone.negativeVolumeData);
                        }
                        else {
                            posWidths.push(0);
                            negWidths.push(0);
                        }
                    }
                    point.color = pos ?
                        volumeDivision.styles.positiveColor :
                        volumeDivision.styles.negativeColor;
                    point.shapeArgs.width = pos ?
                        indicator.posWidths[i] :
                        indicator.negWidths[i];
                    point.shapeArgs.x = pos ?
                        point.shapeArgs.x :
                        indicator.posWidths[i];
                }
            },
            translate: function () {
                var indicator = this,
                    options = indicator.options,
                    chart = indicator.chart,
                    yAxis = indicator.yAxis,
                    yAxisMin = yAxis.min,
                    zoneLinesOptions = indicator.options.zoneLines,
                    priceZones = (indicator.priceZones),
                    yBarOffset = 0,
                    indicatorPoints,
                    volumeDataArray,
                    maxVolume,
                    primalBarWidth,
                    barHeight,
                    barHeightP,
                    oldBarHeight,
                    barWidth,
                    pointPadding,
                    chartPlotTop,
                    barX,
                    barY;
                columnPrototype.translate.apply(indicator);
                indicatorPoints = indicator.points;
                // Do translate operation when points exist
                if (indicatorPoints.length) {
                    pointPadding = options.pointPadding < 0.5 ?
                        options.pointPadding :
                        0.1;
                    volumeDataArray = indicator.volumeDataArray;
                    maxVolume = arrayMax(volumeDataArray);
                    primalBarWidth = chart.plotWidth / 2;
                    chartPlotTop = chart.plotTop;
                    barHeight = abs(yAxis.toPixels(yAxisMin) -
                        yAxis.toPixels(yAxisMin + indicator.rangeStep));
                    oldBarHeight = abs(yAxis.toPixels(yAxisMin) -
                        yAxis.toPixels(yAxisMin + indicator.rangeStep));
                    if (pointPadding) {
                        barHeightP = abs(barHeight * (1 - 2 * pointPadding));
                        yBarOffset = abs((barHeight - barHeightP) / 2);
                        barHeight = abs(barHeightP);
                    }
                    indicatorPoints.forEach(function (point, index) {
                        barX = point.barX = point.plotX = 0;
                        barY = point.plotY = (yAxis.toPixels(priceZones[index].start) -
                            chartPlotTop -
                            (yAxis.reversed ?
                                (barHeight - oldBarHeight) :
                                barHeight) -
                            yBarOffset);
                        barWidth = correctFloat(primalBarWidth *
                            priceZones[index].wholeVolumeData / maxVolume);
                        point.pointWidth = barWidth;
                        point.shapeArgs = indicator.crispCol.apply(// eslint-disable-line no-useless-call
                        indicator, [barX, barY, barWidth, barHeight]);
                        point.volumeNeg = priceZones[index].negativeVolumeData;
                        point.volumePos = priceZones[index].positiveVolumeData;
                        point.volumeAll = priceZones[index].wholeVolumeData;
                    });
                    if (zoneLinesOptions.enabled) {
                        indicator.drawZones(chart, yAxis, indicator.zoneStarts, zoneLinesOptions.styles);
                    }
                }
            },
            getValues: function (series, params) {
                var indicator = this,
                    xValues = series.processedXData,
                    yValues = series.processedYData,
                    chart = indicator.chart,
                    ranges = params.ranges,
                    VBP = [],
                    xData = [],
                    yData = [],
                    isOHLC,
                    volumeSeries,
                    priceZones;
                // Checks if base series exists
                if (!series.chart) {
                    error('Base series not found! In case it has been removed, add ' +
                        'a new one.', true, chart);
                    return;
                }
                // Checks if volume series exists
                if (!(volumeSeries = (chart.get(params.volumeSeriesID)))) {
                    error('Series ' +
                        params.volumeSeriesID +
                        ' not found! Check `volumeSeriesID`.', true, chart);
                    return;
                }
                // Checks if series data fits the OHLC format
                isOHLC = isArray(yValues[0]);
                if (isOHLC && yValues[0].length !== 4) {
                    error('Type of ' +
                        series.name +
                        ' series is different than line, OHLC or candlestick.', true, chart);
                    return;
                }
                // Price zones contains all the information about the zones (index,
                // start, end, volumes, etc.)
                priceZones = indicator.priceZones = indicator.specifyZones(isOHLC, xValues, yValues, ranges, volumeSeries);
                priceZones.forEach(function (zone, index) {
                    VBP.push([zone.x, zone.end]);
                    xData.push(VBP[index][0]);
                    yData.push(VBP[index][1]);
                });
                return {
                    values: VBP,
                    xData: xData,
                    yData: yData
                };
            },
            // Specifing where each zone should start ans end
            specifyZones: function (isOHLC, xValues, yValues, ranges, volumeSeries) {
                var indicator = this,
                    rangeExtremes = (isOHLC ? arrayExtremesOHLC(yValues) : false),
                    lowRange = rangeExtremes ?
                        rangeExtremes.min :
                        arrayMin(yValues),
                    highRange = rangeExtremes ?
                        rangeExtremes.max :
                        arrayMax(yValues),
                    zoneStarts = indicator.zoneStarts = [],
                    priceZones = [],
                    i = 0,
                    j = 1,
                    rangeStep,
                    zoneStartsLength;
                if (!lowRange || !highRange) {
                    if (this.points.length) {
                        this.setData([]);
                        this.zoneStarts = [];
                        this.zoneLinesSVG.destroy();
                    }
                    return [];
                }
                rangeStep = indicator.rangeStep =
                    correctFloat(highRange - lowRange) / ranges;
                zoneStarts.push(lowRange);
                for (; i < ranges - 1; i++) {
                    zoneStarts.push(correctFloat(zoneStarts[i] + rangeStep));
                }
                zoneStarts.push(highRange);
                zoneStartsLength = zoneStarts.length;
                //    Creating zones
                for (; j < zoneStartsLength; j++) {
                    priceZones.push({
                        index: j - 1,
                        x: xValues[0],
                        start: zoneStarts[j - 1],
                        end: zoneStarts[j]
                    });
                }
                return indicator.volumePerZone(isOHLC, priceZones, volumeSeries, xValues, yValues);
            },
            // Calculating sum of volume values for a specific zone
            volumePerZone: function (isOHLC, priceZones, volumeSeries, xValues, yValues) {
                var indicator = this,
                    volumeXData = volumeSeries.processedXData,
                    volumeYData = volumeSeries.processedYData,
                    lastZoneIndex = priceZones.length - 1,
                    baseSeriesLength = yValues.length,
                    volumeSeriesLength = volumeYData.length,
                    previousValue,
                    startFlag,
                    endFlag,
                    value,
                    i;
                // Checks if each point has a corresponding volume value
                if (abs(baseSeriesLength - volumeSeriesLength)) {
                    // If the first point don't have volume, add 0 value at the
                    // beggining of the volume array
                    if (xValues[0] !== volumeXData[0]) {
                        volumeYData.unshift(0);
                    }
                    // If the last point don't have volume, add 0 value at the end
                    // of the volume array
                    if (xValues[baseSeriesLength - 1] !==
                        volumeXData[volumeSeriesLength - 1]) {
                        volumeYData.push(0);
                    }
                }
                indicator.volumeDataArray = [];
                priceZones.forEach(function (zone) {
                    zone.wholeVolumeData = 0;
                    zone.positiveVolumeData = 0;
                    zone.negativeVolumeData = 0;
                    for (i = 0; i < baseSeriesLength; i++) {
                        startFlag = false;
                        endFlag = false;
                        value = isOHLC ? yValues[i][3] : yValues[i];
                        previousValue = i ?
                            (isOHLC ?
                                yValues[i - 1][3] :
                                yValues[i - 1]) :
                            value;
                        // Checks if this is the point with the
                        // lowest close value and if so, adds it calculations
                        if (value <= zone.start && zone.index === 0) {
                            startFlag = true;
                        }
                        // Checks if this is the point with the highest
                        // close value and if so, adds it calculations
                        if (value >= zone.end && zone.index === lastZoneIndex) {
                            endFlag = true;
                        }
                        if ((value > zone.start || startFlag) &&
                            (value < zone.end || endFlag)) {
                            zone.wholeVolumeData += volumeYData[i];
                            if (previousValue > value) {
                                zone.negativeVolumeData += volumeYData[i];
                            }
                            else {
                                zone.positiveVolumeData += volumeYData[i];
                            }
                        }
                    }
                    indicator.volumeDataArray.push(zone.wholeVolumeData);
                });
                return priceZones;
            },
            // Function responsoble for drawing additional lines indicating zones
            drawZones: function (chart, yAxis, zonesValues, zonesStyles) {
                var indicator = this,
                    renderer = chart.renderer,
                    zoneLinesSVG = indicator.zoneLinesSVG,
                    zoneLinesPath = [],
                    leftLinePos = 0,
                    rightLinePos = chart.plotWidth,
                    verticalOffset = chart.plotTop,
                    verticalLinePos;
                zonesValues.forEach(function (value) {
                    verticalLinePos = yAxis.toPixels(value) - verticalOffset;
                    zoneLinesPath = zoneLinesPath.concat(chart.renderer.crispLine([[
                            'M',
                            leftLinePos,
                            verticalLinePos
                        ], [
                            'L',
                            rightLinePos,
                            verticalLinePos
                        ]], zonesStyles.lineWidth));
                });
                // Create zone lines one path or update it while animating
                if (zoneLinesSVG) {
                    zoneLinesSVG.animate({
                        d: zoneLinesPath
                    });
                }
                else {
                    zoneLinesSVG = indicator.zoneLinesSVG =
                        renderer.path(zoneLinesPath).attr({
                            'stroke-width': zonesStyles.lineWidth,
                            'stroke': zonesStyles.color,
                            'dashstyle': zonesStyles.dashStyle,
                            'zIndex': indicator.group.zIndex + 0.1
                        })
                            .add(indicator.group);
                }
            }
        }, 
        /**
         * @lends Highcharts.Point#
         */
        {
            // Required for destroying negative part of volume
            destroy: function () {
                // @todo: this.negativeGraphic doesn't seem to be used anywhere
                if (this.negativeGraphic) {
                    this.negativeGraphic = this.negativeGraphic.destroy();
                }
                return Point.prototype.destroy.apply(this, arguments);
            }
        });
        /**
         * A `Volume By Price (VBP)` series. If the [type](#series.vbp.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         *
         * @extends   series,plotOptions.vbp
         * @since     6.0.0
         * @product   highstock
         * @excluding dataParser, dataURL
         * @requires  stock/indicators/indicators
         * @requires  stock/indicators/volume-by-price
         * @apioption series.vbp
         */
        ''; // to include the above in the js output

    });
    _registerModule(_modules, 'masters/indicators/volume-by-price.src.js', [], function () {


    });
}));