"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 __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.XyMovingAverageFilter = void 0;
var DataFilterType_1 = require("../../../types/DataFilterType");
var XyFilterBase_1 = require("./XyFilterBase");
var vectorToArray_1 = require("../../../utils/vectorToArray");
/**
 * An XyDataSeries that is the moving average of the original series
 */
var XyMovingAverageFilter = /** @class */ (function (_super) {
    __extends(XyMovingAverageFilter, _super);
    function XyMovingAverageFilter(originalSeries, options) {
        var _this = this;
        var _a;
        _this = _super.call(this, originalSeries, options) || this;
        _this.lengthProperty = 30;
        _this.lengthProperty = (_a = options === null || options === void 0 ? void 0 : options.length) !== null && _a !== void 0 ? _a : _this.lengthProperty;
        if (!originalSeries.dataDistributionCalculator.isSortedAscending) {
            throw new Error("XyMovingAverageFilter requires original data to be sorted in X");
        }
        if (_this.getOriginalCount() > 0) {
            _this.calculate(0);
        }
        return _this;
    }
    Object.defineProperty(XyMovingAverageFilter.prototype, "length", {
        /**
         * Gets or Sets the length of the moving average
         */
        get: function () {
            return this.lengthProperty;
        },
        /**
         * Gets or Sets the length of the moving average
         */
        set: function (value) {
            this.lengthProperty = value;
            this.calculate(0);
        },
        enumerable: false,
        configurable: true
    });
    // @ts-ignore
    XyMovingAverageFilter.prototype.toJSON = function (excludeData) {
        if (excludeData === void 0) { excludeData = false; }
        var json = _super.prototype.toJSON.call(this, excludeData);
        var options = json.options;
        if (!excludeData) {
            options.xValues = (0, vectorToArray_1.vectorToArray)(this.getOriginalXValues(), this.webAssemblyContext);
            options.yValues = (0, vectorToArray_1.vectorToArray)(this.getOriginalYValues(), this.webAssemblyContext);
        }
        options.filter = {
            type: DataFilterType_1.EDataFilterType.XyMovingAverage,
            options: { field: this.field, xField: this.xField, length: this.length }
        };
        return __assign(__assign({}, json), { options: options });
    };
    XyMovingAverageFilter.prototype.onOriginalPropertyChanged = function (name) { };
    XyMovingAverageFilter.prototype.filterOnAppend = function (count) {
        if (this.originalSeries.fifoCapacity) {
            // FIFO source: compute MA for the latest `count` source points only.
            // The filter (FIFO or not) is append-only here; if FIFO, it will roll automatically.
            this.appendLatestMA(count);
        }
        else {
            this.calculate(this.count());
        }
    };
    XyMovingAverageFilter.prototype.filterOnUpdate = function (index) {
        var y = this.getOriginalYValues().get(index) || 0;
        this.calculateUpdate(index, y);
    };
    XyMovingAverageFilter.prototype.filterOnInsert = function (startIndex, count) {
        this.calculate(startIndex);
    };
    XyMovingAverageFilter.prototype.filterOnRemove = function (startIndex, count) {
        this.calculate(startIndex);
    };
    XyMovingAverageFilter.prototype.filterAll = function () {
        this.calculate(0);
    };
    XyMovingAverageFilter.prototype.calculateUpdate = function (index, y) {
        if (index < this.count() - 1) {
            this.calculate(index);
        }
        else {
            if (index >= this.length - 1) {
                // Just update latest
                var bufferTotal = 0;
                var yValuesView = (0, vectorToArray_1.vectorToArrayViewF64)(this.getOriginalYValues(), this.webAssemblyContext);
                var fifoCapacity = this.originalSeries.fifoCapacity;
                var fifoStartIndex = fifoCapacity ? this.originalSeries.fifoStartIndex : 0;
                for (var i = index - (this.length - 1); i <= index; i++) {
                    var rawIdx = fifoCapacity ? (i + fifoStartIndex) % fifoCapacity : i;
                    bufferTotal += yValuesView[rawIdx];
                }
                this.update(index, bufferTotal / this.length);
            }
        }
    };
    /**
     * Computes the moving average for the latest `count` source points and appends them
     * to this filter. Used when the source is a FIFO data series so that newly appended
     * points continue to be processed once the source has reached capacity. If this filter
     * is also FIFO, the appendRange below will cause it to roll naturally.
     */
    XyMovingAverageFilter.prototype.appendLatestMA = function (count) {
        var originalCount = this.getOriginalCount();
        var newCount = Math.min(count, originalCount);
        if (newCount <= 0)
            return;
        var xValuesView = (0, vectorToArray_1.vectorToArrayViewF64)(this.getOriginalXValues(), this.webAssemblyContext);
        var yValuesView = (0, vectorToArray_1.vectorToArrayViewF64)(this.getOriginalYValues(), this.webAssemblyContext);
        var fifoCapacity = this.originalSeries.fifoCapacity;
        var fifoStartIndex = fifoCapacity ? this.originalSeries.fifoStartIndex : 0;
        var length = this.length;
        var containsNaN = this.originalSeries.dataDistributionCalculator.containsNaN;
        var rawIdx = function (i) { return (fifoCapacity ? (i + fifoStartIndex) % fifoCapacity : i); };
        var readY = function (i) {
            if (i < 0 || i >= originalCount)
                return 0;
            var v = yValuesView[rawIdx(i)];
            return containsNaN ? v || 0 : v;
        };
        var startIdx = originalCount - newCount;
        var bufferTotal = 0;
        for (var j = startIdx - length + 1; j < startIdx; j++) {
            bufferTotal += readY(j);
        }
        var newX = new Array(newCount);
        var newY = new Array(newCount);
        for (var k = 0; k < newCount; k++) {
            var i = startIdx + k;
            newX[k] = xValuesView[rawIdx(i)];
            bufferTotal += readY(i);
            newY[k] = i >= length - 1 ? bufferTotal / length : NaN;
            bufferTotal -= readY(i - length + 1);
        }
        this.appendRange(newX, newY);
    };
    XyMovingAverageFilter.prototype.calculate = function (start) {
        var xValuesView = (0, vectorToArray_1.vectorToArrayViewF64)(this.getOriginalXValues(), this.webAssemblyContext);
        var yValuesView = (0, vectorToArray_1.vectorToArrayViewF64)(this.getOriginalYValues(), this.webAssemblyContext);
        var fifoCapacity = this.originalSeries.fifoCapacity;
        var fifoStartIndex = fifoCapacity ? this.originalSeries.fifoStartIndex : 0;
        var rawIdx = function (i) { return (fifoCapacity ? (i + fifoStartIndex) % fifoCapacity : i); };
        var bufferTotal = 0;
        if (start < this.count()) {
            if (start === 0) {
                this.clear();
            }
            else {
                this.removeRange(start, this.count() - start);
            }
        }
        var s = Math.max(0, start - this.length);
        for (var i = s; i < start; i++) {
            bufferTotal += yValuesView[rawIdx(i)] || 0;
        }
        var originalCount = this.getOriginalCount();
        var newX = [];
        var newY = [];
        if (this.originalSeries.dataDistributionCalculator.containsNaN) {
            // Nan checks here add about 5%
            for (var i = start; i < originalCount; i++) {
                var x = xValuesView[rawIdx(i)];
                var y = yValuesView[rawIdx(i)] || 0;
                newX.push(x);
                if (i > this.length - 1) {
                    var startPointer = i - this.length;
                    var oldY = yValuesView[rawIdx(startPointer)] || 0;
                    bufferTotal = bufferTotal + y - oldY;
                    newY.push(bufferTotal / this.length);
                }
                else if (i === this.length - 1) {
                    bufferTotal = bufferTotal + y;
                    newY.push(bufferTotal / this.length);
                }
                else {
                    bufferTotal = bufferTotal + y;
                    newY.push(NaN);
                }
            }
        }
        else {
            for (var i = start; i < originalCount; i++) {
                var x = xValuesView[rawIdx(i)];
                var y = yValuesView[rawIdx(i)];
                newX.push(x);
                if (i > this.length - 1) {
                    var startPointer = i - this.length;
                    var oldY = yValuesView[rawIdx(startPointer)];
                    bufferTotal = bufferTotal + y - oldY;
                    newY.push(bufferTotal / this.length);
                }
                else if (i === this.length - 1) {
                    bufferTotal = bufferTotal + y;
                    newY.push(bufferTotal / this.length);
                }
                else {
                    bufferTotal = bufferTotal + y;
                    newY.push(NaN);
                }
            }
        }
        this.appendRange(newX, newY);
    };
    return XyMovingAverageFilter;
}(XyFilterBase_1.XyFilterBase));
exports.XyMovingAverageFilter = XyMovingAverageFilter;
