"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module RangeSearch
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.RangeTreeOps = exports.RangeTreeNode = exports.TwoTreeDistanceMinimizationSearchHandler = exports.TwoTreeSearchHandler = exports.SingleTreeSearchHandler = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const Range_1 = require("../../geometry3d/Range");
/**
 * Map an (unchecked) integer to a parameterized type T, where the data argument can be either:
 * * an array of type T
 * * a function which takes an index and returns type T
 * @internal
 */
function evaluateIndexToType(data, index) {
    if (Array.isArray(data))
        return data[index];
    return data(index);
}
/**
 * Get data by index from a source that may be undefined, an array of item of type T, or a singleton of the item type.
 * @internal
 */
function getByIndex(index, data) {
    if (data !== undefined) {
        if (Array.isArray(data)) {
            if (index < data.length)
                return data[index];
        }
        else {
            return data;
        }
    }
    return undefined;
}
/**
 * Return the number of items in an object that can be undefined, an array of item of type T, or a singleton of the item type.
 * @internal
 */
function getFlexDataCount(data) {
    if (data !== undefined) {
        if (Array.isArray(data)) {
            return data.length;
        }
        else {
            return 1;
        }
    }
    return 0;
}
/**
 * Abstract class for handler objects called during traversal of a single range tree.
 * @internal
 */
class SingleTreeSearchHandler {
    /**
     * Query to see if the active search has been aborted.
     * * Default implementation returns false so query runs to completion.
     * * Search processes check this after range tests and child processing.
     */
    getIsAborted() { return false; }
}
exports.SingleTreeSearchHandler = SingleTreeSearchHandler;
/**
 * Abstract class for handler objects called during traversal of two range trees.
 * @internal
 */
class TwoTreeSearchHandler {
    /**
     * Query to see if the active search has been aborted.
     * * Default implementation returns false so query runs to completion.
     * * Search processes check this after range tests and child processing.
     */
    getIsAborted() { return false; }
    /** Whether to ignore z-coordinates in distance computations. */
    getXYOnly() { return false; }
}
exports.TwoTreeSearchHandler = TwoTreeSearchHandler;
/**
 * This class refines the TwoTreeSearchHandler with an implementation of `isRangePairActive` appropriate for computing the minimum distance between trees.
 * * The concrete class must implement `getCurrentDistance()` method to provide the best-so-far distance.
 * * The implementation of `isRangePairActive` returns true if the distance between ranges is less than or equal to the `getCurrentDistance()` value.
 * @internal
 */
class TwoTreeDistanceMinimizationSearchHandler extends TwoTreeSearchHandler {
    /**
     * Method called to decide whether to process subtrees and immediate child appData items from a left tree node and right tree node.
     * @param leftRange range from a node in the left tree
     * @param rightRange range from a node in the right tree.
     * @returns true if the smallest distance from leftRange to rightRange is less than or equal to getCurrentDistance()
     */
    isRangePairActive(leftRange, rightRange) {
        const currentDistance = this.getCurrentDistance();
        const distanceBetweenRanges = this.getXYOnly() ? leftRange.distanceToRangeXY(rightRange) : leftRange.distanceToRange(rightRange);
        return distanceBetweenRanges <= currentDistance;
    }
}
exports.TwoTreeDistanceMinimizationSearchHandler = TwoTreeDistanceMinimizationSearchHandler;
let numNodeCreated = 0;
/**
 * * TREE STRUCTURE
 *   * A RangeTreeNode is part of a range tree.
 *   * TREE is used here in a strictly _structural_ sense, which has no broad promises about data members.
 *   * Each RangeNode points to 0, 1 or many children.
 *   * Each child has (but does not point back to) a single parent.
 *   * The overall tree has a single root.
 *   * Each node is effectively the root of the tree of its children.
 * * NON-TREE DATA MEMBERS
 *   * Data members in each node (in addition to children) are
 *     * _range = the union of ranges below in the heap
 *     * _appData = application data associated with the node.
 *       * Construction methods may place multiple _appData items in each node.
 *       * In common use, only the leaves will have _appData. However, the class definitions allow _appData at all nodes, and search algorithms must include them.
 * * CONSTRUCTION
 *   * The RangeTreeNode.createByIndexSplits method constructs the tree with simple right-left splits within an array of input items.
 *     * The appData is placed entirely in the leaves.
 *     * The caller can specify:
 *       * the number of _appData items per leaf
 *       * the number of children per node within the tree.
 *     * Compared to "shallow" trees with many children per node and many appData per leaf, "deep" trees with 2 children per node and 1 appData per leaf may have:
 *       * faster search because lower nodes have smaller ranges that will be skipped by search algorithms.
 *       * larger memory use because of more nodes
 *   * For future construction methods:
 *      * _appData "above the leaves" may allow nodes below to have smaller ranges, but add complexity to search.
 * @internal
 */
class RangeTreeNode {
    _range;
    _appData;
    _children;
    /** an id assigned sequentially as nodes are created.  For debugging use only. */
    _id;
    /**
     * CONSTRUCTOR
     * CAPTURE (not copy)
     * * range = range for search algorithms
     * * appData = application data relevant to this node.
     * * children = child node reference(s).
     */
    constructor(range, appData, children) {
        this._range = range;
        this._appData = appData;
        this._children = children;
        this._id = numNodeCreated++;
        // const childIds: number[] = [];
        // if (Array.isArray(this._children))
        //   for (const c of this._children) childIds.push(c._id);
        // else if (this._children instanceof RangeTreeNode)
        //   childIds.push(this._children._id);
        // const numAppData = getFlexDataCount(appData);
        // console.log({ id: this._id, childIds, numAppData });
    }
    /** Simplest public create: capture the range, appData, and children. */
    static createCapture(range, appData, children) {
        return new RangeTreeNode(range, appData, children);
    }
    /** copy (not capture) from given data into the range in this RangeEntry */
    setRange(data) {
        this._range.setFrom(data);
    }
    /** return (a copy of) the range in this RangeEntry */
    getRange(data) {
        return this._range.clone(data);
    }
    /** return (a reference to) the range in this RangeEntry */
    getRangeRef() {
        return this._range;
    }
    /**
     * Access a child by index.
     * * If the child data is an array, this dereferences the array.
     * * If the child data is a singleton, treat it as index 0.
     * * return undefined if there are no children.
     * @param index index of item to access.
     */
    getChildByIndex(index) {
        return getByIndex(index, this._children);
    }
    /**
     * * Access an appData by index.
     * * If the appData data is an array, this dereferences the array.
     * * If the appData data is a singleton, treat it as if it is at index 0 in an array
     * * return undefined if there are no appData or for any index out of range.
     * @param index index of item to access.
     */
    getAppDataByIndex(index) {
        return getByIndex(index, this._appData);
    }
    /** Access the children or child (does not clone). */
    getAllChildren() {
        return this._children;
    }
    /** Access the appData array or singleton (does not clone). */
    getAllAppData() {
        return this._appData;
    }
    /**
     * Count the direct children in this node of the tree.
     * * This is not recursive. For a recursive count, use `RangeTreeOps.getRecursiveNodeCount`.
     */
    getNumChildren() {
        return getFlexDataCount(this._children);
    }
    /**
     * Count the appData items in this node of the tree.
     * * This is not recursive. For a recursive count, use `RangeTreeOps.getRecursiveAppDataCount`.
     */
    getNumAppData() {
        return getFlexDataCount(this._appData);
    }
    /**
     * Depth-first tree iteration, calling `announceNode` on each node.
     * @param announceNode callback that returns true to recurse into children
     */
    recurseIntoTree(announceNode) {
        const doChildren = announceNode(this);
        if (doChildren) {
            if (Array.isArray(this._children)) {
                for (const child of this._children)
                    child.recurseIntoTree(announceNode);
            }
            else if (this._children !== undefined) {
                this._children.recurseIntoTree(announceNode);
            }
        }
    }
    /**
     * Depth-first tree iteration via handler.
     * * if handler decides the instance range is active, process appData, then recurse on children
     * * if handler decides to abort after processing an appData, skip processing rest of appData and children
     */
    searchTopDown(handler) {
        if (handler.isRangeActive(this._range)) {
            let itemToProcess;
            for (let i = 0; undefined !== (itemToProcess = this.getAppDataByIndex(i)); i++) {
                // console.log(itemToProcess);
                handler.processAppData(itemToProcess);
                if (handler.getIsAborted())
                    return;
            }
            let child;
            for (let i = 0; undefined !== (child = this.getChildByIndex(i)); i++) {
                child.searchTopDown(handler);
            }
        }
    }
    /**
     * Apply the handler.processAppDataPair method to each pair of appData items from leftAppData and rightStack.
     * @param leftAppData singleton or array with data "from left"
     * @param rightStack stack of nodes to process from right path.
     * @param reverseArgs true if the caller is exchanging the sense of left and right and this should be undone in the actual call to handler.processAppDataPair.
     * @param handler search handler
     */
    static processAppDataAndAppDataStack(leftAppData, rightStack, reverseArgs, handler) {
        if (leftAppData !== undefined) {
            let leftItem;
            let rightItem;
            // hmm.. we ASSUME that if the tip ranges passed, then all parent ranges would pass without further test.
            for (let rangeIndex = rightStack.length; rangeIndex-- > 0;) {
                const rightAppData = rightStack[rangeIndex]._appData;
                for (let rightIndex = 0; undefined !== (rightItem = getByIndex(rightIndex, rightAppData)); rightIndex++) {
                    for (let leftIndex = 0; undefined !== (leftItem = getByIndex(leftIndex, leftAppData)); leftIndex++) {
                        if (!reverseArgs)
                            handler.processAppDataPair(leftItem, rightItem);
                        else
                            handler.processAppDataPair(rightItem, leftItem);
                        if (handler.getIsAborted())
                            return;
                    }
                }
            }
        }
    }
    /**
     * Apply the handler.processAppDataPair method to each pair of appData items from leftAppData and rightAppData.
     * @param leftAppData singleton or array with data "from left"
     * @param rightAppData singleton or array with data "from right"
     * @param reverseArgs true if the caller is exchanging the sense of left and right and this should be undone in the actual call to handler.processAppDataPair.
     * @param handler search handler
     */
    static processAppDataAndAppData(leftAppData, rightAppData, reverseArgs, handler) {
        if (leftAppData !== undefined && rightAppData !== undefined) {
            let leftItem;
            let rightItem;
            for (let rightIndex = 0; undefined !== (rightItem = getByIndex(rightIndex, rightAppData)); rightIndex++) {
                for (let leftIndex = 0; undefined !== (leftItem = getByIndex(leftIndex, leftAppData)); leftIndex++) {
                    if (!reverseArgs)
                        handler.processAppDataPair(leftItem, rightItem);
                    else
                        handler.processAppDataPair(rightItem, leftItem);
                    if (handler.getIsAborted())
                        return;
                }
            }
        }
    }
    /**
     * Push the tip node to stack(s).
     * @param tip new node (to be pushed)
     * @param fullPath complete path, which is always extended
     * @param partialPath partial path, which is only extended if the tip has _appData.
     */
    static pushPaths(tip, fullPath, partialPath) {
        fullPath.push(tip);
        if (tip._appData !== undefined) {
            partialPath.push(tip);
        }
    }
    /**
     * Pop the tip node from stack(s).
     * @param tip should match the fullPath tip.
     * @param fullPath complete path, which is always popped
     * @param partialPath partial path, which is only popped if the tip has _appData.
     */
    static popPaths(tip, fullPath, partialPath) {
        (0, core_bentley_1.assert)(fullPath[fullPath.length - 1] === tip);
        fullPath.pop();
        if (tip._appData !== undefined) {
            partialPath.pop();
        }
    }
    /**
     * Process nodes from left and right trees of dual search.
     * * The separate stacks for nodes that have appData is for efficiency.
     * * If data is entirely in the leaves (or just in a few nodes), the stacks will all be empty (or very small) and no time will be wasted looking up the stacks for appData to process with the other tip.
     * @param leftTip tip node being explored on left
     * @param leftStack stack of prior left nodes
     * @param leftStackWithAppData stack of prior left nodes which have appData.
     * @param rightTip tip node being explored on right.
     * @param rightStack stack of prior right nodes.
     * @param rightStackWithAppData stack of prior right nodes which have appData.
     * @param handler search handler
     */
    static recursivePairSearch(leftTip, leftStack, leftStackWithAppData, rightTip, rightStack, rightStackWithAppData, handler) {
        // console.log({ leftId: leftTip._id, rightId: rightTip._id });
        const leftTipHasAppData = leftTip._appData !== undefined;
        const rightTipHasAppData = rightTip._appData !== undefined;
        let leftChild;
        let rightChild;
        // process immediate appData from each tip node with the entire prior path of the other side (each stack currently lacks the tip).
        if (leftTipHasAppData && rightStackWithAppData.length > 0) {
            this.processAppDataAndAppDataStack(leftTip._appData, rightStackWithAppData, false, handler);
        }
        if (leftStackWithAppData.length > 0 && rightTipHasAppData) {
            this.processAppDataAndAppDataStack(rightTip._appData, leftStackWithAppData, true, handler);
        }
        if (handler.isRangePairActive(leftTip._range, rightTip._range)) {
            this.processAppDataAndAppData(leftTip._appData, rightTip._appData, false, handler);
            if (leftTip._children !== undefined && rightTip._children !== undefined) {
                this.pushPaths(leftTip, leftStack, leftStackWithAppData);
                this.pushPaths(rightTip, rightStack, rightStackWithAppData);
                for (let leftIndex = 0; undefined !== (leftChild = getByIndex(leftIndex, leftTip._children)); leftIndex++) {
                    for (let rightIndex = 0; undefined !== (rightChild = getByIndex(rightIndex, rightTip._children)); rightIndex++) {
                        this.recursivePairSearch(leftChild, leftStack, leftStackWithAppData, rightChild, rightStack, rightStackWithAppData, handler);
                    }
                }
                this.popPaths(leftTip, leftStack, leftStackWithAppData);
                this.popPaths(rightTip, rightStack, rightStackWithAppData);
            }
            else if (leftTip._children !== undefined) {
                this.leftRecursivePairSearch(leftTip, rightTip, rightStackWithAppData, false, handler);
            }
            else if (rightTip._children !== undefined) {
                this.leftRecursivePairSearch(rightTip, leftTip, leftStackWithAppData, true, handler);
            }
        }
    }
    /**
     * Recurse below the tip of leftTip, offering each level's appData to the appData of rightTip and rightStackWithAppData.
     * @param leftTip tip node being explored on left. Its appData is not processed.
     * @param rightTip tip node being explored on right. It has no children.
     * @param rightStackWithAppData stack of prior right nodes which have appData.
     * @param reverseArgs true if the caller is exchanging the sense of left and right
     * @param handler search handler
     */
    static leftRecursivePairSearch(leftTip, rightTip, rightStackWithAppData, reverseArgs, handler) {
        (0, core_bentley_1.assert)(rightTip._children === undefined);
        // The (possibly deep) left children appData needs to be offered to the right path (including tip)
        // Note that there are no stack push/pop operations -- the right stack is already reaching to leaf level, so there are no range or appData nodes to add.
        let leftChild;
        for (let leftIndex = 0; undefined !== (leftChild = getByIndex(leftIndex, leftTip._children)); leftIndex++) {
            if (handler.isRangePairActive(leftChild._range, rightTip._range)) {
                this.processAppDataAndAppData(leftChild._appData, rightTip._appData, reverseArgs, handler);
                this.processAppDataAndAppDataStack(leftChild._appData, rightStackWithAppData, reverseArgs, handler);
                this.leftRecursivePairSearch(leftChild, rightTip, rightStackWithAppData, reverseArgs, handler);
            }
        }
    }
    /**
     * Recursive search down two trees, with range tests and child processing under control of a handler.
     * @param leftRoot root of left tree
     * @param rightRoot root of right tree
     * @param handler handler for range tests and child process
     */
    static searchTwoTreesTopDown(leftRoot, rightRoot, handler) {
        this.recursivePairSearch(leftRoot, [], [], rightRoot, [], [], handler);
    }
}
exports.RangeTreeNode = RangeTreeNode;
/**
 * Utilities for various operations on RangeTree
 * @internal
 */
class RangeTreeOps {
    /** Count nodes in this tree. */
    static getRecursiveNodeCount(root) {
        let count = 0;
        root.recurseIntoTree((_node) => { count++; return true; });
        return count;
    }
    /** Count appData in this tree. */
    static getRecursiveAppDataCount(root) {
        let count = 0;
        root.recurseIntoTree((node) => { count += node.getNumAppData(); return true; });
        return count;
    }
    /**
     * Create a leaf referencing appData items indexed index0<=index<index1 and with combined range of the same indices
     * @param ranges access to ranges
     * @param appData access to AppDataType items
     * @param index0 first index for block of items
     * @param index1 upper limit index for block of items.
     * @param arrayLength one more than the largest range/appData index
     * @returns newly created node.
     */
    static createLeafInIndexRange(ranges, appData, index0, index1, arrayLength) {
        const appDataBlock = [];
        const range = Range_1.Range3d.createNull();
        index1 = Math.min(index1, arrayLength);
        // console.log({ case: "LEAF", index0, index1 });
        for (let i = index0; i < index1; i++) {
            appDataBlock.push(evaluateIndexToType(appData, i));
            range.extendRange(evaluateIndexToType(ranges, i));
        }
        return RangeTreeNode.createCapture(range, appDataBlock, undefined);
    }
    /**
     * Split the array entries appData[index0 <= i < index1] into blocks of at most maxChildPerNode * maxAppDataPerLeaf and assemble into a tree structure.
     * @param ranges access to ranges
     * @param appData access to AppDataType items
     * @param index0 start index of the block to access
     * @param index1 terminal index for the block (one after final)
     * @param arrayLength one more than the largest range/appData index
     * @param maxChildPerNode max number of child nodes in each interior node
     * @param maxAppDataPerLeaf max number of appData items in each leaf.
     * @returns
     */
    static createRecursiveByIndexSplits(ranges, appData, index0, index1, arrayLength, maxChildPerNode, maxAppDataPerLeaf) {
        if (index1 > arrayLength)
            index1 = arrayLength;
        const range = Range_1.Range3d.createNull();
        const children = [];
        const maxChildrenAppData = maxChildPerNode * maxAppDataPerLeaf;
        // console.log({ name: "createRecursive", index0, index1, maxChildrenAppData });
        if (index1 <= index0 + maxChildrenAppData) { // index range is small enough to hold the appData in leaf children
            // console.log({ case: "LEAF GROUP", index0, index1 });
            for (let indexA = index0 + maxAppDataPerLeaf; index0 < index1; index0 = indexA, indexA = Math.min(indexA + maxAppDataPerLeaf, index1)) {
                const leaf = RangeTreeOps.createLeafInIndexRange(ranges, appData, index0, indexA, arrayLength);
                if (leaf !== undefined) {
                    range.extendRange(leaf.getRangeRef());
                    children.push(leaf);
                }
            }
        }
        else { // split the appData among interior and leaf children
            // console.log({ case: "INTERIOR", index0, index1 });
            const numPerGulp = Math.ceil((index1 - index0) / maxChildPerNode);
            for (let indexA = index0 + numPerGulp; index0 < index1; index0 = indexA, indexA = Math.min(indexA + numPerGulp, index1)) {
                const child = this.createRecursiveByIndexSplits(ranges, appData, index0, indexA, arrayLength, maxChildPerNode, maxAppDataPerLeaf);
                if (child !== undefined) {
                    range.extendRange(child.getRangeRef());
                    children.push(child);
                }
            }
        }
        return (children.length > 0) ? RangeTreeNode.createCapture(range, undefined, children) : undefined;
    }
    /**
     * Create a range tree by simple left-right split of given ranges.
     * * Leaves carry the inputs in left-to-right order.
     * * Each leaf range is labeled by its corresponding object(s) in the appData array.
     * @param ranges access to ranges.
     * @param appData access to AppDataType items (for leaves).
     * @param arrayLength one more than the largest range/appData index
     * @param maxChildrenPerNode max number of child nodes allowed for each interior node.
     * @param maxAppDataPerLeaf max number of appData items allowed in each leaf.
     * @returns the root of the new tree, or undefined if array lengths differ or are zero.
     */
    static createByIndexSplits(ranges, appData, arrayLength, maxChildrenPerNode = 2, maxAppDataPerLeaf = 2) {
        // console.log();
        // const numData = getFlexDataCount(appData);
        // console.log({ numData });
        if (arrayLength <= 0
            || (Array.isArray(ranges) && ranges.length !== arrayLength)
            || (Array.isArray(appData) && appData.length !== arrayLength))
            return undefined;
        if (maxChildrenPerNode < 2)
            maxChildrenPerNode = 2;
        return RangeTreeOps.createRecursiveByIndexSplits(ranges, appData, 0, arrayLength, arrayLength, maxChildrenPerNode, maxAppDataPerLeaf);
    }
}
exports.RangeTreeOps = RangeTreeOps;
//# sourceMappingURL=RangeTreeNode.js.map