"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BinaryIndexedTree = void 0;
/**
 * data-structure-typed
 *
 * @author Pablo Zeng
 * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
 * @license MIT License
 */
const utils_1 = require("../../utils");
/**
 *
 */
class BinaryIndexedTree {
    /**
     * The constructor initializes the properties of an object, including default frequency, maximum
     * value, a freqMap data structure, the most significant bit, and the count of negative frequencies.
     * @param  - - `frequency`: The default frequency value. It is optional and has a default
     * value of 0.
     */
    constructor({ frequency = 0, max }) {
        this._freq = frequency;
        this._max = max;
        this._freqMap = { 0: 0 };
        this._msb = (0, utils_1.getMSB)(max);
        this._negativeCount = frequency < 0 ? max : 0;
    }
    /**
     * The function returns the frequency map of numbers.
     * @returns The `_freqMap` property, which is a record with number keys and number values, is being
     * returned.
     */
    get freqMap() {
        return this._freqMap;
    }
    /**
     * The function returns the value of the _msb property.
     * @returns The `_msb` property of the object.
     */
    get msb() {
        return this._msb;
    }
    /**
     * The function returns the value of the _negativeCount property.
     * @returns The method is returning the value of the variable `_negativeCount`, which is of type
     * `number`.
     */
    get negativeCount() {
        return this._negativeCount;
    }
    /**
     * The above function returns the value of the protected variable `_freq`.
     * @returns The frequency value stored in the protected variable `_freq`.
     */
    get freq() {
        return this._freq;
    }
    /**
     * The above function returns the maximum value.
     * @returns The maximum value stored in the variable `_max`.
     */
    get max() {
        return this._max;
    }
    /**
     * The function "readSingle" reads a single number from a specified index.
     * @param {number} index - The `index` parameter is a number that represents the index of an element in a
     * collection or array.
     * @returns a number.
     */
    readSingle(index) {
        this._checkIndex(index);
        return this._readSingle(index);
    }
    /**
     * The "update" function updates the value at a given index by adding a delta and triggers a callback
     * to notify of the change.
     * @param {number} position - The `index` parameter represents the index of the element that needs to be
     * updated in the data structure.
     * @param {number} change - The "delta" parameter represents the change in value that needs to be
     * applied to the frequency at the specified index.
     */
    update(position, change) {
        this._checkIndex(position);
        const freqCur = this._readSingle(position);
        this._update(position, change);
        this._updateNegativeCount(freqCur, freqCur + change);
    }
    /**
     * The function "writeSingle" checks the index and writes a single value with a given frequency.
     * @param {number} index - The `index` parameter is a number that represents the index of an element. It
     * is used to identify the specific element that needs to be written.
     * @param {number} freq - The `freq` parameter represents the frequency value that needs to be
     * written.
     */
    writeSingle(index, freq) {
        this._checkIndex(index);
        this._writeSingle(index, freq);
    }
    /**
     * The read function takes a count parameter, checks if it is an integer, and returns the result of
     * calling the _read function with the count parameter clamped between 0 and the maximum value.
     * @param {number} count - The `count` parameter is a number that represents the number of items to
     * read.
     * @returns a number.
     */
    read(count) {
        if (!Number.isInteger(count)) {
            throw new Error('Invalid count');
        }
        return this._read(Math.max(Math.min(count, this.max), 0));
    }
    /**
     * The function returns the lower bound of a non-descending sequence that sums up to a given number.
     * @param {number} sum - The `sum` parameter is a number that represents the target sum that we want
     * to find in the sequence.
     * @returns The lowerBound function is returning a number.
     */
    lowerBound(sum) {
        if (this.negativeCount > 0) {
            throw new Error('Sequence is not non-descending');
        }
        return this._binarySearch(sum, (x, y) => x < y);
    }
    /**
     * The upperBound function returns the index of the first element in a sequence that is greater than
     * or equal to a given sum.
     * @param {number} sum - The "sum" parameter is a number that represents the target sum that we want
     * to find in the sequence.
     * @returns The upperBound function is returning a number.
     */
    upperBound(sum) {
        if (this.negativeCount > 0) {
            throw new Error('Must not be descending');
        }
        return this._binarySearch(sum, (x, y) => x <= y);
    }
    /**
     * The function calculates the prefix sum of an array using a binary indexed tree.
     * @param {number} i - The parameter "i" in the function "getPrefixSum" represents the index of the element in the
     * array for which we want to calculate the prefix sum.
     * @returns The function `getPrefixSum` returns the prefix sum of the elements in the binary indexed tree up to index
     * `i`.
     */
    getPrefixSum(i) {
        this._checkIndex(i);
        i++; // Convert to 1-based index
        let sum = 0;
        while (i > 0) {
            sum += this._getFrequency(i);
            i -= i & -i;
        }
        return sum;
    }
    /**
     * The function returns the value of a specific index in a freqMap data structure, or a default value if
     * the index is not found.
     * @param {number} index - The `index` parameter is a number that represents the index of a node in a
     * freqMap data structure.
     * @returns a number.
     */
    _getFrequency(index) {
        if (index in this.freqMap) {
            return this.freqMap[index];
        }
        return this.freq * (index & -index);
    }
    /**
     * The function _updateFrequency adds a delta value to the element at the specified index in the freqMap array.
     * @param {number} index - The index parameter is a number that represents the index of the freqMap
     * element that needs to be updated.
     * @param {number} delta - The `delta` parameter represents the change in value that needs to be
     * added to the freqMap at the specified `index`.
     */
    _updateFrequency(index, delta) {
        this.freqMap[index] = this._getFrequency(index) + delta;
    }
    /**
     * The function checks if the given index is valid and within the range.
     * @param {number} index - The parameter "index" is of type number and represents the index value
     * that needs to be checked.
     */
    _checkIndex(index) {
        if (!Number.isInteger(index)) {
            throw new Error('Invalid index: Index must be an integer.');
        }
        if (index < 0 || index >= this.max) {
            throw new Error('Index out of range: Index must be within the range [0, this.max).');
        }
    }
    /**
     * The function calculates the sum of elements in an array up to a given index using a binary indexed
     * freqMap.
     * @param {number} index - The `index` parameter is a number that represents the index of an element in a
     * data structure.
     * @returns a number.
     */
    _readSingle(index) {
        index = index + 1;
        let sum = this._getFrequency(index);
        const z = index - (index & -index);
        index--;
        while (index !== z) {
            sum -= this._getFrequency(index);
            index -= index & -index;
        }
        return sum;
    }
    /**
     * The function `_updateNegativeCount` updates a counter based on changes in frequency values.
     * @param {number} freqCur - The current frequency value.
     * @param {number} freqNew - The freqNew parameter represents the new frequency value.
     */
    _updateNegativeCount(freqCur, freqNew) {
        if (freqCur < 0 && freqNew >= 0) {
            this._negativeCount--;
        }
        else if (freqCur >= 0 && freqNew < 0) {
            this._negativeCount++;
        }
    }
    /**
     * The `_update` function updates the values in a binary indexed freqMap starting from a given index and
     * propagating the changes to its parent nodes.
     * @param {number} index - The `index` parameter is a number that represents the index of the element in
     * the data structure that needs to be updated.
     * @param {number} delta - The `delta` parameter represents the change in value that needs to be
     * applied to the elements in the data structure.
     */
    _update(index, delta) {
        index = index + 1;
        while (index <= this.max) {
            this._updateFrequency(index, delta);
            index += index & -index;
        }
    }
    /**
     * The `_writeSingle` function updates the frequency at a specific index and triggers a callback if
     * the frequency has changed.
     * @param {number} index - The `index` parameter is a number that represents the index of the element
     * being modified or accessed.
     * @param {number} freq - The `freq` parameter represents the new frequency value that needs to be
     * written to the specified index `index`.
     */
    _writeSingle(index, freq) {
        const freqCur = this._readSingle(index);
        this._update(index, freq - freqCur);
        this._updateNegativeCount(freqCur, freq);
    }
    /**
     * The `_read` function calculates the sum of values in a binary freqMap up to a given count.
     * @param {number} count - The `count` parameter is a number that represents the number of elements
     * to read from the freqMap.
     * @returns the sum of the values obtained from calling the `_getFrequency` method for each index in the
     * range from `count` to 1.
     */
    _read(count) {
        let index = count;
        let sum = 0;
        while (index) {
            sum += this._getFrequency(index);
            index -= index & -index;
        }
        return sum;
    }
    /**
     * The function `_binarySearch` performs a binary search to find the largest number that satisfies a given
     * condition.
     * @param {number} sum - The sum parameter is a number that represents the target sum value.
     * @param before - The `before` parameter is a function that takes two numbers `x` and `y` as
     * arguments and returns a boolean value. It is used to determine if `x` is less than or equal to
     * `y`. The purpose of this function is to compare two numbers and determine their order.
     * @returns the value of the variable "left".
     */
    _binarySearch(sum, before) {
        let left = 0;
        let right = this.msb << 1;
        let sumT = sum;
        while (right > left + 1) {
            const middle = (left + right) >> 1;
            const sumM = this._getFrequency(middle);
            if (middle <= this.max && before(sumM, sumT)) {
                sumT -= sumM;
                left = middle;
            }
            else {
                right = middle;
            }
        }
        return left;
    }
}
exports.BinaryIndexedTree = BinaryIndexedTree;
//# sourceMappingURL=binary-indexed-tree.js.map