"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.StackedXyCollection = void 0;
var Deleter_1 = require("../../../Core/Deleter");
var NumberRange_1 = require("../../../Core/NumberRange");
var SearchMode_1 = require("../../../types/SearchMode");
var copyVector_1 = require("../../../utils/copyVector");
var performance_1 = require("../../../utils/performance");
var vectorToArray_1 = require("../../../utils/vectorToArray");
var BaseDataSeries_1 = require("../../Model/BaseDataSeries");
var XyyPointSeriesWrapped_1 = require("../../Model/PointSeries/XyyPointSeriesWrapped");
var RenderPassData_1 = require("../../Services/RenderPassData");
var BaseStackedCollection_1 = require("./BaseStackedCollection");
var constants_1 = require("./constants");
var StackedXyCollection = /** @class */ (function (_super) {
    __extends(StackedXyCollection, _super);
    /**
     * Creates an instance of the {@link StackedMountainCollection}
     * @param webAssemblyContext The {@link TSciChart | SciChart WebAssembly Context} containing
     * native methods and access to our WebGL2 WebAssembly Drawing Engine
     * @param options Optional parameters of type {@link IStackedXyCollectionOptions} to configure the series
     */
    function StackedXyCollection(webAssemblyContext, options) {
        var _this = this;
        var _a;
        _this = _super.call(this, webAssemblyContext, options) || this;
        _this.separatePositiveNegativeStacksProperty = true;
        _this.separatePositiveNegativeStacksProperty = (_a = options === null || options === void 0 ? void 0 : options.separatePositiveNegativeStacks) !== null && _a !== void 0 ? _a : _this.separatePositiveNegativeStacksProperty;
        _this.detachChildSeries = _this.detachChildSeries.bind(_this);
        _this.attachChildSeries = _this.attachChildSeries.bind(_this);
        _this.collectionChanged.subscribe(function (arg) {
            var _a, _b;
            (_a = arg.getOldItems()) === null || _a === void 0 ? void 0 : _a.forEach(_this.detachChildSeries);
            (_b = arg.getNewItems()) === null || _b === void 0 ? void 0 : _b.forEach(_this.attachChildSeries);
        });
        return _this;
    }
    /** @inheritDoc */
    StackedXyCollection.prototype.updateAccumulatedVectors = function () {
        var _this = this;
        var dataValuesCount = this.getDataSeriesValuesCount();
        if (!this.isAccumulatedVectorDirty || !dataValuesCount) {
            return;
        }
        this.checkXValuesCorrect();
        this.isAccumulatedVectorDirty = false;
        this.clearAccumulatedVectors(dataValuesCount);
        var visibleSeries = this.getVisibleSeries();
        var yValueArrayViews = visibleSeries.map(function (cur) {
            return (0, vectorToArray_1.vectorToArrayViewF64)(cur.dataSeries.getNativeYValues(), _this.webAssemblyContext);
        });
        // vectorToArrayViewF64 returns the raw circular buffer for a FIFO vector, so logical index
        // i maps to raw index (i + fifoStartIndex) % count. Build a per-series mapper. Sweeping
        // FIFO is deliberately left in raw order (the band drawing provider draws it in place).
        var rawIndexers = visibleSeries.map(function (cur) {
            var ds = cur.dataSeries;
            var unwind = ds.fifoCapacity > 0 && !ds.fifoSweeping;
            var startIndex = ds.fifoStartIndex;
            return unwind ? function (i) { return (i + startIndex) % dataValuesCount; } : function (i) { return i; };
        });
        this.buildUnwoundXValues(dataValuesCount);
        var _loop_1 = function (i) {
            this_1.accumulatedValues0.push_back(0);
            if (this_1.separatePositiveNegativeStacksProperty) {
                // Compute separate positive/negative totals for 100% normalization.
                var positiveTotal_1 = 0;
                var negativeTotal_1 = 0;
                if (this_1.isOneHundredPercent) {
                    visibleSeries.forEach(function (rs, index) {
                        var y = yValueArrayViews[index][rawIndexers[index](i)];
                        if (y >= 0)
                            positiveTotal_1 += y;
                        else
                            negativeTotal_1 += y;
                    });
                }
                // Positive values stack upward from zero; negative values stack downward independently.
                var positiveAccum_1 = 0;
                var negativeAccum_1 = 0;
                visibleSeries.forEach(function (rs, index) {
                    var currentY = yValueArrayViews[index][rawIndexers[index](i)];
                    if (_this.isOneHundredPercent) {
                        currentY = currentY >= 0
                            ? (positiveTotal_1 !== 0 ? (currentY / positiveTotal_1) * 100 : 0)
                            : (negativeTotal_1 !== 0 ? (currentY / Math.abs(negativeTotal_1)) * 100 : 0);
                    }
                    if (currentY >= 0) {
                        rs.bottomAccumulatedValues.push_back(positiveAccum_1);
                        var top_1 = positiveAccum_1 + currentY;
                        rs.accumulatedValues.push_back(top_1);
                        positiveAccum_1 = top_1;
                    }
                    else {
                        var bottom = negativeAccum_1 + currentY;
                        rs.bottomAccumulatedValues.push_back(bottom);
                        rs.accumulatedValues.push_back(negativeAccum_1);
                        negativeAccum_1 = bottom;
                    }
                    if (rs.renderDataTransform) {
                        rs.renderDataTransform.requiresTransform = true;
                    }
                });
            }
            else {
                var totalSum_1;
                if (this_1.isOneHundredPercent) {
                    totalSum_1 = visibleSeries.reduce(function (prev, cur, index) { return prev + yValueArrayViews[index][rawIndexers[index](i)]; }, 0);
                }
                var previous_1 = 0;
                visibleSeries.forEach(function (rs, index) {
                    var currentY = yValueArrayViews[index][rawIndexers[index](i)];
                    if (_this.isOneHundredPercent) {
                        currentY = (currentY * 100) / totalSum_1;
                    }
                    var current = previous_1 + currentY;
                    rs.accumulatedValues.push_back(current);
                    previous_1 = current;
                    if (rs.renderDataTransform) {
                        rs.renderDataTransform.requiresTransform = true;
                    }
                });
            }
        };
        var this_1 = this;
        for (var i = 0; i < dataValuesCount; i++) {
            _loop_1(i);
        }
    };
    Object.defineProperty(StackedXyCollection.prototype, "isSourceFifo", {
        /**
         * True when the stacked source series are FIFO and not sweeping, in which case the circular
         * buffer must be unwound into logical order for drawing.
         */
        get: function () {
            var _a;
            var ds = (_a = this.getFirstSeries()) === null || _a === void 0 ? void 0 : _a.dataSeries;
            return !!ds && ds.fifoCapacity > 0 && !ds.fifoSweeping;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Rebuilds {@link unwoundXValuesProperty} in logical order from the (shared) source X values
     * when the source is a non-sweeping FIFO buffer. Cleared/ignored otherwise.
     */
    StackedXyCollection.prototype.buildUnwoundXValues = function (dataValuesCount) {
        if (!this.isSourceFifo) {
            return;
        }
        var ds = this.getFirstSeries().dataSeries;
        var xView = (0, vectorToArray_1.vectorToArrayViewF64)(ds.getNativeXValues(), this.webAssemblyContext);
        var startIndex = ds.fifoStartIndex;
        if (!this.unwoundXValuesProperty) {
            this.unwoundXValuesProperty = new this.webAssemblyContext.SCRTDoubleVector();
        }
        this.unwoundXValuesProperty.clear();
        this.unwoundXValuesProperty.reserve(dataValuesCount);
        for (var i = 0; i < dataValuesCount; i++) {
            this.unwoundXValuesProperty.push_back(xView[(i + startIndex) % dataValuesCount]);
        }
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.getXValuesForRange = function () {
        return this.isSourceFifo ? this.unwoundXValuesProperty : this.getNativeXValues();
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.delete = function () {
        this.unwoundXValuesProperty = (0, Deleter_1.deleteSafe)(this.unwoundXValuesProperty);
        _super.prototype.delete.call(this);
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.draw = function (renderContext, renderPassData) {
        var _this = this;
        var _a, _b, _c;
        if (this.canDraw) {
            this.updateHitTestProviders(renderPassData);
            if (!this.isEnoughDataToDraw()) {
                return;
            }
            var mark = performance_1.PerformanceDebugHelper.mark(performance_1.EPerformanceMarkType.DrawCollectionSeriesStart, {
                contextId: this.id,
                parentContextId: (_a = this.parentSurface) === null || _a === void 0 ? void 0 : _a.id,
                level: performance_1.EPerformanceDebugLevel.Verbose
            });
            this.updateAccumulatedVectors();
            var xAxis_1 = this.xAxis;
            var visibleSeries_1 = this.getVisibleSeries();
            // draw Stacked Series in reverse order to prevent overlapping with Point Markers
            visibleSeries_1.reduceRight(function (nextSeries, series, index, collection) {
                var accumulatedValues1 = series.accumulatedValues;
                var accumulatedValues = _this.separatePositiveNegativeStacksProperty
                    ? series.bottomAccumulatedValues
                    : index === 0 ? _this.accumulatedValues0 : collection[index - 1].accumulatedValues;
                // wrap XyDataSeries as XyyDataSeries. When the source is a non-sweeping FIFO buffer,
                // supply the unwound (logical-order) X values so they line up with the accumulated
                // value vectors, which were also built in logical order in updateAccumulatedVectors.
                var xyyPointSeries = new XyyPointSeriesWrapped_1.XyyPointSeriesWrapped(series.dataSeries, accumulatedValues, accumulatedValues1, _this.isSourceFifo ? _this.unwoundXValuesProperty : undefined);
                var renderData = new RenderPassData_1.RenderPassData(series.getIndicesRange(xAxis_1.visibleRange, xAxis_1.isCategoryAxis), renderPassData.getxCoordinateCalculator, renderPassData.getyCoordinateCalculator, xAxis_1.isVerticalChart, xyyPointSeries);
                if (series.renderDataTransform) {
                    var transRenderData = new RenderPassData_1.RenderPassData(series.getIndicesRange(xAxis_1.visibleRange, xAxis_1.isCategoryAxis), renderPassData.getxCoordinateCalculator, renderPassData.getyCoordinateCalculator, xAxis_1.isVerticalChart, xyyPointSeries);
                    transRenderData = series.renderDataTransform.runTransform(transRenderData);
                    if (index < visibleSeries_1.length - 1) {
                        (0, copyVector_1.copyDoubleVector)(collection[index + 1].renderDataTransform.pointSeries.yValues, transRenderData.pointSeries.y1Values, _this.webAssemblyContext);
                    }
                    if ("strokeY1" in series) {
                        // @ts-ignore
                        series.strokeY1 = index === 0 ? "transparent" : collection[index - 1].stroke;
                        // @ts-ignore
                        series.strokeY1DashArray = index === 0 ? [] : collection[index - 1].strokeDashArray;
                    }
                    series.drawingProviders[0].draw(renderContext, transRenderData);
                    return series;
                }
                if ("strokeY1" in series) {
                    // @ts-ignore
                    series.strokeY1 = index === 0 ? "transparent" : collection[index - 1].stroke;
                    // @ts-ignore
                    series.strokeY1DashArray = index === 0 ? [] : collection[index - 1].strokeDashArray;
                }
                series.draw(renderContext, renderData);
                return series;
            }, undefined);
            performance_1.PerformanceDebugHelper.mark(performance_1.EPerformanceMarkType.DrawCollectionSeriesEnd, {
                contextId: this.id,
                parentContextId: (_b = this.parentSurface) === null || _b === void 0 ? void 0 : _b.id,
                relatedId: (_c = mark === null || mark === void 0 ? void 0 : mark.detail) === null || _c === void 0 ? void 0 : _c.relatedId,
                level: performance_1.EPerformanceDebugLevel.Verbose
            });
        }
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.getXRange = function () {
        if (!this.isEnoughDataToDraw()) {
            return new NumberRange_1.NumberRange();
        }
        return this.getFirstSeries().dataSeries.xRange;
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.getYRange = function (xVisibleRange, isXCategoryAxis) {
        var _this = this;
        var yRange = _super.prototype.getYRange.call(this, xVisibleRange, isXCategoryAxis);
        if (this.separatePositiveNegativeStacksProperty) {
            // accumulatedValues only contains the tops; also union with bottomAccumulatedValues
            // so that negatively-stacked bands are included in the visible range.
            var xValues_1 = this.getXValuesForRange();
            this.getVisibleSeries().forEach(function (rs) {
                var bottomSize = rs.bottomAccumulatedValues.size();
                if (bottomSize > 0 && bottomSize === (xValues_1 === null || xValues_1 === void 0 ? void 0 : xValues_1.size())) {
                    var bottomRange = (0, BaseDataSeries_1.getWindowedYRange)(_this.webAssemblyContext, xValues_1, rs.bottomAccumulatedValues, xVisibleRange, true, isXCategoryAxis, rs.dataSeries.dataDistributionCalculator.isSortedAscending, true, SearchMode_1.ESearchMode.RoundUp, SearchMode_1.ESearchMode.RoundDown);
                    if (bottomRange) {
                        yRange = yRange.union(bottomRange);
                    }
                }
            });
        }
        return yRange;
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.onAttach = function (scs) {
        _super.prototype.onAttach.call(this, scs);
        this.getVisibleSeries().forEach(function (series) {
            series.onAttach(scs);
        });
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.onDetach = function () {
        this.getVisibleSeries().forEach(function (series) {
            series.onDetach();
        });
        _super.prototype.onDetach.call(this);
    };
    /** @inheritDoc */
    StackedXyCollection.prototype.notifyPropertyChanged = function (propertyName) {
        _super.prototype.notifyPropertyChanged.call(this, propertyName);
        if (propertyName === constants_1.PROPERTY.DATA_SERIES ||
            propertyName === constants_1.PROPERTY.IS_VISIBLE ||
            propertyName === constants_1.PROPERTY.IS_ONE_HUNDRED_PERCENT ||
            propertyName === constants_1.PROPERTY.SEPARATE_POSITIVE_NEGATIVE_STACKS) {
            this.isAccumulatedVectorDirty = true;
        }
    };
    Object.defineProperty(StackedXyCollection.prototype, "separatePositiveNegativeStacks", {
        /**
         * When true (default), positive values stack upward from the zero line and negative values stack downward independently.
         * When false, all values accumulate continuously from the previous series top, preserving legacy behaviour.
         */
        get: function () {
            return this.separatePositiveNegativeStacksProperty;
        },
        /**
         * When true (default), positive values stack upward from the zero line and negative values stack downward independently.
         * When false, all values accumulate continuously from the previous series top, preserving legacy behaviour.
         */
        set: function (value) {
            this.separatePositiveNegativeStacksProperty = value;
            this.notifyPropertyChanged(constants_1.PROPERTY.SEPARATE_POSITIVE_NEGATIVE_STACKS);
        },
        enumerable: false,
        configurable: true
    });
    /** @inheritDoc */
    StackedXyCollection.prototype.hasDataSeriesValues = function () {
        return this.isEnoughDataToDraw();
    };
    // PROTECTED
    // PRIVATE
    StackedXyCollection.prototype.detachChildSeries = function (series) {
        series.onDetachFromParentCollection();
        this.isAccumulatedVectorDirty = true;
        this.invalidateParent();
    };
    StackedXyCollection.prototype.attachChildSeries = function (series) {
        series.onAttachToParentCollection(this, this.getParentSurface, this.notifyPropertyChanged);
        if (this.parentSurface) {
            series.onAttach(this.parentSurface);
        }
        this.isAccumulatedVectorDirty = true;
        this.invalidateParent();
    };
    StackedXyCollection.prototype.checkXValuesCorrect = function () {
        var length = this.getDataSeriesValuesCount();
        this.getVisibleSeries().forEach(function (el) {
            if (!(el.dataSeries.count() === length)) {
                throw Error("All stacked series in on collection should have the same amount of X Values");
            }
        });
    };
    /**
     * @param numberOfElements - number of element expected is used for performance to reserve memory
     */
    StackedXyCollection.prototype.clearAccumulatedVectors = function (numberOfElements) {
        this.accumulatedValues0.clear();
        this.accumulatedValues0.reserve(numberOfElements);
        this.asArray().forEach(function (el) {
            el.accumulatedValues.clear();
            el.accumulatedValues.reserve(numberOfElements);
            el.bottomAccumulatedValues.clear();
            el.bottomAccumulatedValues.reserve(numberOfElements);
        });
    };
    StackedXyCollection.prototype.getLastVisibleSeries = function () {
        var lastItem = this.getVisibleSeries().slice(-1)[0];
        return lastItem;
    };
    return StackedXyCollection;
}(BaseStackedCollection_1.BaseStackedCollection));
exports.StackedXyCollection = StackedXyCollection;
