"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AVLTreeCounter = exports.AVLTreeCounterNode = void 0;
const avl_tree_1 = require("./avl-tree");
class AVLTreeCounterNode extends avl_tree_1.AVLTreeNode {
    /**
     * The constructor function initializes a BinaryTreeNode object with a key, value, and count.
     * @param {K} key - The `key` parameter is of type `K` and represents the unique identifier
     * of the binary tree node.
     * @param {V} [value] - The `value` parameter is an optional parameter of type `V`. It represents the value of the binary
     * tree node. If no value is provided, it will be `undefined`.
     * @param {number} [count=1] - The `count` parameter is a number that represents the number of times a particular value
     * occurs in a binary tree node. It has a default value of 1, which means that if no value is provided for the `count`
     * parameter when creating a new instance of the `BinaryTreeNode` class.
     */
    constructor(key, value, count = 1) {
        super(key, value);
        this.parent = undefined;
        this._left = undefined;
        this._right = undefined;
        this.count = count;
    }
    get left() {
        return this._left;
    }
    set left(v) {
        if (v) {
            v.parent = this;
        }
        this._left = v;
    }
    get right() {
        return this._right;
    }
    set right(v) {
        if (v) {
            v.parent = this;
        }
        this._right = v;
    }
}
exports.AVLTreeCounterNode = AVLTreeCounterNode;
/**
 * The only distinction between a AVLTreeCounter and a AVLTree lies in the ability of the former to store duplicate nodes through the utilization of counters.
 */
class AVLTreeCounter extends avl_tree_1.AVLTree {
    /**
     * The constructor initializes a new AVLTreeCounter object with optional initial elements.
     * @param keysNodesEntriesOrRaws - The `keysNodesEntriesOrRaws` parameter is an
     * iterable object that can contain either keys, nodes, entries, or raw elements.
     * @param [options] - The `options` parameter is an optional object that can be used to customize the
     * behavior of the AVLTreeCounter. It can include properties such as `compareKeys` and
     * `compareValues` functions to define custom comparison logic for keys and values, respectively.
     */
    constructor(keysNodesEntriesOrRaws = [], options) {
        super([], options);
        this._count = 0;
        if (keysNodesEntriesOrRaws)
            this.addMany(keysNodesEntriesOrRaws);
    }
    /**
     * The function calculates the sum of the count property of all nodes in a tree using depth-first
     * search.
     * @returns the sum of the count property of all nodes in the tree.
     */
    get count() {
        return this._count;
    }
    /**
     * Time Complexity: O(n)
     * Space Complexity: O(1)
     *
     * The function calculates the sum of the count property of all nodes in a tree using depth-first
     * search.
     * @returns the sum of the count property of all nodes in the tree.
     */
    getComputedCount() {
        let sum = 0;
        this.dfs(node => (sum += node.count));
        return sum;
    }
    /**
     * The function creates a new AVLTreeCounterNode with the specified key, value, and count.
     * @param {K} key - The key parameter represents the key of the node being created. It is of type K,
     * which is a generic type that can be replaced with any specific type when using the function.
     * @param {V} [value] - The `value` parameter is an optional parameter that represents the value
     * associated with the key in the node. It is of type `V`, which can be any data type.
     * @param {number} [count] - The `count` parameter represents the number of occurrences of a
     * key-value pair in the AVLTreeCounterNode. It is an optional parameter, so it can be omitted when
     * calling the `createNode` method. If provided, it specifies the initial count for the node.
     * @returns a new instance of the AVLTreeCounterNode class, casted as AVLTreeCounterNode<K, V>.
     */
    createNode(key, value, count) {
        return new AVLTreeCounterNode(key, this._isMapMode ? undefined : value, count);
    }
    /**
     * The function creates a new AVLTreeCounter object with the specified options and returns it.
     * @param [options] - The `options` parameter is an optional object that contains additional
     * configuration options for creating the AVLTreeCounter. It can have the following properties:
     * @returns a new instance of the AVLTreeCounter class, with the specified options, as a TREE
     * object.
     */
    createTree(options) {
        return new AVLTreeCounter([], Object.assign({ iterationType: this.iterationType, isMapMode: this._isMapMode, specifyComparable: this._specifyComparable, toEntryFn: this._toEntryFn, isReverse: this._isReverse }, options));
    }
    /**
     * The function checks if the input is an instance of AVLTreeCounterNode.
     * @param {K | AVLTreeCounterNode<K, V> | [K | null | undefined, V | undefined] | null | undefined} keyNodeOrEntry - The parameter
     * `keyNodeOrEntry` can be of type `R` or `K | AVLTreeCounterNode<K, V> | [K | null | undefined, V | undefined] | null | undefined`.
     * @returns a boolean value indicating whether the input parameter `keyNodeOrEntry` is
     * an instance of the `AVLTreeCounterNode` class.
     */
    isNode(keyNodeOrEntry) {
        return keyNodeOrEntry instanceof AVLTreeCounterNode;
    }
    /**
     * Time Complexity: O(log n)
     * Space Complexity: O(1)
     *
     * The function overrides the add method of a TypeScript class to add a new node to a data structure
     * and update the count.
     * @param {K | AVLTreeCounterNode<K, V> | [K | null | undefined, V | undefined] | null | undefined} keyNodeOrEntry - The
     * `keyNodeOrEntry` parameter can accept a value of type `R`, which can be any type. It
     * can also accept a value of type `K | AVLTreeCounterNode<K, V> | [K | null | undefined, V | undefined] | null | undefined`, which represents a key, node,
     * entry, or raw element
     * @param {V} [value] - The `value` parameter represents the value associated with the key in the
     * data structure. It is an optional parameter, so it can be omitted if not needed.
     * @param [count=1] - The `count` parameter represents the number of times the key-value pair should
     * be added to the data structure. By default, it is set to 1, meaning that the key-value pair will
     * be added once. However, you can specify a different value for `count` if you want to add
     * @returns a boolean value.
     */
    add(keyNodeOrEntry, value, count = 1) {
        const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value, count);
        if (newNode === undefined)
            return false;
        const orgNodeCount = (newNode === null || newNode === void 0 ? void 0 : newNode.count) || 0;
        const inserted = super.add(newNode, newValue);
        if (inserted) {
            this._count += orgNodeCount;
        }
        return true;
    }
    /**
     * Time Complexity: O(log n)
     * Space Complexity: O(1)
     *
     * The function overrides the delete method in a binary tree data structure, handling deletion of
     * nodes and maintaining balance in the tree.
     * @param {K | AVLTreeCounterNode<K, V> | [K | null | undefined, V | undefined] | null | undefined} keyNodeOrEntry - The `predicate`
     * parameter in the `delete` method is used to specify the condition for deleting a node from the
     * binary tree. It can be a key, node, or entry that determines which
     * node(s) should be deleted.
     * @param [ignoreCount=false] - The `ignoreCount` parameter in the `override delete` method is a
     * boolean flag that determines whether to ignore the count of the node being deleted. If
     * `ignoreCount` is set to `true`, the method will delete the node regardless of its count. If
     * `ignoreCount` is set to
     * @returns The `delete` method overrides the default delete behavior in a binary tree data
     * structure. It takes a predicate or node to be deleted and an optional flag to ignore count. The
     * method returns an array of `BinaryTreeDeleteResult` objects, each containing information about the
     * deleted node and whether balancing is needed in the tree.
     */
    delete(keyNodeOrEntry, ignoreCount = false) {
        var _a;
        const deletedResult = [];
        if (!this.root)
            return deletedResult;
        const curr = (_a = this.getNode(keyNodeOrEntry)) !== null && _a !== void 0 ? _a : undefined;
        if (!curr)
            return deletedResult;
        const parent = (curr === null || curr === void 0 ? void 0 : curr.parent) ? curr.parent : undefined;
        let needBalanced = undefined, orgCurrent = curr;
        if (curr.count > 1 && !ignoreCount) {
            curr.count--;
            this._count--;
        }
        else {
            if (!curr.left) {
                if (!parent) {
                    if (curr.right !== undefined && curr.right !== null)
                        this._setRoot(curr.right);
                }
                else {
                    const { familyPosition: fp } = curr;
                    if (fp === 'LEFT' || fp === 'ROOT_LEFT') {
                        parent.left = curr.right;
                    }
                    else if (fp === 'RIGHT' || fp === 'ROOT_RIGHT') {
                        parent.right = curr.right;
                    }
                    needBalanced = parent;
                }
            }
            else {
                const leftSubTreeRightMost = curr.left ? this.getRightMost(node => node, curr.left) : undefined;
                if (leftSubTreeRightMost) {
                    const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent;
                    orgCurrent = this._swapProperties(curr, leftSubTreeRightMost);
                    if (parentOfLeftSubTreeMax) {
                        if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) {
                            parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left;
                        }
                        else {
                            parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left;
                        }
                        needBalanced = parentOfLeftSubTreeMax;
                    }
                }
            }
            this._size = this._size - 1;
            // TODO How to handle when the count of target node is lesser than current node's count
            if (orgCurrent)
                this._count -= orgCurrent.count;
        }
        deletedResult.push({ deleted: orgCurrent, needBalanced });
        if (needBalanced) {
            this._balancePath(needBalanced);
        }
        return deletedResult;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The "clear" function overrides the parent class's "clear" function and also resets the count to
     * zero.
     */
    clear() {
        super.clear();
        this._count = 0;
    }
    /**
     * Time Complexity: O(n log n)
     * Space Complexity: O(log n)
     *
     * The `perfectlyBalance` function takes a sorted array of nodes and builds a balanced binary search
     * tree using either a recursive or iterative approach.
     * @param {IterationType} iterationType - The `iterationType` parameter is an optional parameter that
     * specifies the type of iteration to use when building the balanced binary search tree. It has a
     * default value of `this.iterationType`, which means it will use the iteration type currently set in
     * the object.
     * @returns The function `perfectlyBalance` returns a boolean value. It returns `true` if the
     * balancing operation is successful, and `false` if there are no nodes to balance.
     */
    perfectlyBalance(iterationType = this.iterationType) {
        const sorted = this.dfs(node => node, 'IN'), n = sorted.length;
        if (sorted.length < 1)
            return false;
        this.clear();
        if (iterationType === 'RECURSIVE') {
            const buildBalanceBST = (l, r) => {
                if (l > r)
                    return;
                const m = l + Math.floor((r - l) / 2);
                const midNode = sorted[m];
                if (this._isMapMode)
                    this.add(midNode.key, undefined, midNode.count);
                else
                    this.add(midNode.key, midNode.value, midNode.count);
                buildBalanceBST(l, m - 1);
                buildBalanceBST(m + 1, r);
            };
            buildBalanceBST(0, n - 1);
            return true;
        }
        else {
            const stack = [[0, n - 1]];
            while (stack.length > 0) {
                const popped = stack.pop();
                if (popped) {
                    const [l, r] = popped;
                    if (l <= r) {
                        const m = l + Math.floor((r - l) / 2);
                        const midNode = sorted[m];
                        if (this._isMapMode)
                            this.add(midNode.key, undefined, midNode.count);
                        else
                            this.add(midNode.key, midNode.value, midNode.count);
                        stack.push([m + 1, r]);
                        stack.push([l, m - 1]);
                    }
                }
            }
            return true;
        }
    }
    /**
     * Time complexity: O(n)
     * Space complexity: O(n)
     *
     * The function overrides the clone method to create a deep copy of a tree object.
     * @returns The `clone()` method is returning a cloned instance of the `TREE` object.
     */
    clone() {
        const cloned = this.createTree();
        if (this._isMapMode)
            this.bfs(node => cloned.add(node.key, undefined, node.count));
        else
            this.bfs(node => cloned.add(node.key, node.value, node.count));
        if (this._isMapMode)
            cloned._store = this._store;
        return cloned;
    }
    /**
     * The `map` function in TypeScript overrides the default behavior to create a new AVLTreeCounter
     * with modified entries based on a provided callback.
     * @param callback - The `callback` parameter is a function that will be called for each entry in the
     * AVLTreeCounter. It takes four arguments:
     * @param [options] - The `options` parameter in the `override map` function is of type
     * `AVLTreeCounterOptions<MK, MV, MR>`. This parameter allows you to provide additional
     * configuration options when creating a new `AVLTreeCounter` instance within the `map` function.
     * These options
     * @param {any} [thisArg] - The `thisArg` parameter in the `override map` function is used to specify
     * the value of `this` when executing the `callback` function. It allows you to set the context
     * (value of `this`) for the callback function. This can be useful when you want to access properties
     * or
     * @returns The `map` method is returning a new `AVLTreeCounter` instance with the entries
     * transformed by the provided `callback` function. Each entry in the original tree is passed to the
     * `callback` function along with the index and the original tree itself. The transformed entries are
     * then added to the new `AVLTreeCounter` instance, which is returned at the end.
     */
    map(callback, options, thisArg) {
        const newTree = new AVLTreeCounter([], options);
        let index = 0;
        for (const [key, value] of this) {
            newTree.add(callback.call(thisArg, key, value, index++, this));
        }
        return newTree;
    }
    /**
     * The function `keyValueNodeEntryRawToNodeAndValue` converts a key, value, entry, or raw element into
     * a node object.
     * @param {K | AVLTreeCounterNode<K, V> | [K | null | undefined, V | undefined] | null | undefined} keyNodeOrEntry - The
     * `keyNodeOrEntry` parameter can be of type `R` or `K | AVLTreeCounterNode<K, V> | [K | null | undefined, V | undefined] | null | undefined`.
     * @param {V} [value] - The `value` parameter is an optional value that can be passed to the
     * `override` function. It represents the value associated with the key in the data structure. If no
     * value is provided, it will default to `undefined`.
     * @param [count=1] - The `count` parameter is an optional parameter that specifies the number of
     * times the key-value pair should be added to the data structure. If not provided, it defaults to 1.
     * @returns either a AVLTreeCounterNode<K, V> object or undefined.
     */
    _keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value, count = 1) {
        if (keyNodeOrEntry === undefined || keyNodeOrEntry === null)
            return [undefined, undefined];
        if (this.isNode(keyNodeOrEntry))
            return [keyNodeOrEntry, value];
        if (this.isEntry(keyNodeOrEntry)) {
            const [key, entryValue] = keyNodeOrEntry;
            if (key === undefined || key === null)
                return [undefined, undefined];
            const finalValue = value !== null && value !== void 0 ? value : entryValue;
            return [this.createNode(key, finalValue, count), finalValue];
        }
        return [this.createNode(keyNodeOrEntry, value, count), value];
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The `_swapProperties` function swaps the properties (key, value, count, height) between two nodes
     * in a binary search tree.
     * @param {BSTNOptKeyOrNode<K, AVLTreeCounterNode<K, V>>} srcNode - The `srcNode` parameter represents the source node
     * that will be swapped with the `destNode`.
     * @param {BSTNOptKeyOrNode<K, AVLTreeCounterNode<K, V>>} destNode - The `destNode` parameter represents the destination
     * node where the properties will be swapped with the source node.
     * @returns The method is returning the `destNode` after swapping its properties with the `srcNode`.
     * If either `srcNode` or `destNode` is undefined, it returns `undefined`.
     */
    _swapProperties(srcNode, destNode) {
        srcNode = this.ensureNode(srcNode);
        destNode = this.ensureNode(destNode);
        if (srcNode && destNode) {
            const { key, value, count, height } = destNode;
            const tempNode = this.createNode(key, value, count);
            if (tempNode) {
                tempNode.height = height;
                destNode.key = srcNode.key;
                if (!this._isMapMode)
                    destNode.value = srcNode.value;
                destNode.count = srcNode.count;
                destNode.height = srcNode.height;
                srcNode.key = tempNode.key;
                if (!this._isMapMode)
                    srcNode.value = tempNode.value;
                srcNode.count = tempNode.count;
                srcNode.height = tempNode.height;
            }
            return destNode;
        }
        return undefined;
    }
    /**
     * Time Complexity: O(1)
     * Space Complexity: O(1)
     *
     * The function replaces an old node with a new node and updates the count property of the new node.
     * @param {AVLTreeCounterNode<K, V>} oldNode - The oldNode parameter represents the node that needs to be replaced in the
     * data structure. It is of type AVLTreeCounterNode<K, V>.
     * @param {AVLTreeCounterNode<K, V>} newNode - The `newNode` parameter is an instance of the `AVLTreeCounterNode<K, V>` class.
     * @returns The method is returning the result of calling the `_replaceNode` method from the
     * superclass, which is of type `AVLTreeCounterNode<K, V>`.
     */
    _replaceNode(oldNode, newNode) {
        newNode.count = oldNode.count + newNode.count;
        return super._replaceNode(oldNode, newNode);
    }
}
exports.AVLTreeCounter = AVLTreeCounter;
//# sourceMappingURL=avl-tree-counter.js.map