"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TST = exports.IntervalTree = exports.intervalComparer = exports.IntegerRangeTree = exports.integerRangeToString = exports.integerRangeCopy = exports.integerRangeComparer = exports.integerRangeOverlaps = exports.integerRangeUnion = exports.RedBlackTree = exports.RBColor = exports.LinearDictionary = exports.Heap = exports.numberComparer = exports.List = exports.ListMakeHead = exports.ListMakeEntry = exports.ListRemoveEntry = exports.Stack = void 0;
/* eslint-disable @typescript-eslint/consistent-type-assertions, eqeqeq, object-shorthand */
/* eslint-disable no-bitwise */
/* Remove once strictNullCheck is enabled */
const common_utils_1 = require("@fluidframework/common-utils");
const MergeTree = __importStar(require("./mergeTree"));
class Stack {
    constructor() {
        this.items = [];
    }
    push(val) {
        this.items.push(val);
    }
    empty() {
        return this.items.length === 0;
    }
    top() {
        return this.items[this.items.length - 1];
    }
    pop() {
        return this.items.pop();
    }
}
exports.Stack = Stack;
function ListRemoveEntry(entry) {
    if (entry === undefined) {
        return undefined;
    }
    else if (entry.isHead) {
        return undefined;
    }
    else {
        entry.next.prev = entry.prev;
        entry.prev.next = entry.next;
    }
    return (entry);
}
exports.ListRemoveEntry = ListRemoveEntry;
function ListMakeEntry(data) {
    return new List(false, data);
}
exports.ListMakeEntry = ListMakeEntry;
function ListMakeHead() {
    return new List(true, undefined);
}
exports.ListMakeHead = ListMakeHead;
class List {
    constructor(isHead, data) {
        this.isHead = isHead;
        this.data = data;
        this.prev = this;
        this.next = this;
    }
    clear() {
        if (this.isHead) {
            this.prev = this;
            this.next = this;
        }
    }
    add(data) {
        const entry = ListMakeEntry(data);
        this.prev.next = entry;
        entry.next = this;
        entry.prev = this.prev;
        this.prev = entry;
        return (entry);
    }
    dequeue() {
        if (!this.empty()) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const removedEntry = ListRemoveEntry(this.next);
            return removedEntry.data;
        }
    }
    enqueue(data) {
        return this.add(data);
    }
    walk(fn) {
        for (let entry = this.next; !(entry.isHead); entry = entry.next) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            fn(entry.data, entry);
        }
    }
    some(fn, rev) {
        const rtn = [];
        const start = rev ? this.prev : this.next;
        for (let entry = start; !(entry.isHead); entry = rev ? entry.prev : entry.next) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            const data = entry.data;
            if (fn(data, entry)) {
                if (rev) {
                    // preserve list order when in reverse
                    rtn.unshift(data);
                }
                else {
                    rtn.push(data);
                }
            }
        }
        return rtn;
    }
    count() {
        let entry;
        let i;
        entry = this.next;
        for (i = 0; !(entry.isHead); i++) {
            entry = entry.next;
        }
        return (i);
    }
    first() {
        if (!this.empty()) {
            return (this.next.data);
        }
    }
    last() {
        if (!this.empty()) {
            return (this.prev.data);
        }
    }
    empty() {
        return (this.next === this);
    }
    pushEntry(entry) {
        entry.isHead = false;
        entry.next = this.next;
        entry.prev = this;
        this.next = entry;
        entry.next.prev = entry;
    }
    push(data) {
        const entry = ListMakeEntry(data);
        entry.data = data;
        entry.isHead = false;
        entry.next = this.next;
        entry.prev = this;
        this.next = entry;
        entry.next.prev = entry;
    }
    popEntry(head) {
        if (this.next.isHead) {
            return undefined;
        }
        else {
            return ListRemoveEntry(this.next);
        }
    }
    insertEntry(entry) {
        entry.isHead = false;
        this.prev.next = entry;
        entry.next = this;
        entry.prev = this.prev;
        this.prev = entry;
        return entry;
    }
    insertAfter(data) {
        const entry = ListMakeEntry(data);
        entry.next = this.next;
        entry.prev = this;
        this.next = entry;
        entry.next.prev = entry;
        return (entry);
    }
    insertBefore(data) {
        const entry = ListMakeEntry(data);
        return this.insertEntryBefore(entry);
    }
    insertEntryBefore(entry) {
        this.prev.next = entry;
        entry.next = this;
        entry.prev = this.prev;
        this.prev = entry;
        return (entry);
    }
}
exports.List = List;
exports.numberComparer = {
    min: Number.MIN_VALUE,
    compare: (a, b) => a - b,
};
class Heap {
    constructor(a, comp) {
        this.comp = comp;
        this.L = [comp.min];
        for (let i = 0, len = a.length; i < len; i++) {
            this.add(a[i]);
        }
    }
    count() {
        return this.L.length - 1;
    }
    peek() {
        return this.L[1];
    }
    get() {
        const x = this.L[1];
        this.L[1] = this.L[this.count()];
        this.L.pop();
        this.fixdown(1);
        return x;
    }
    add(x) {
        this.L.push(x);
        this.fixup(this.count());
    }
    fixup(k) {
        let _k = k;
        while (_k > 1 && (this.comp.compare(this.L[_k >> 1], this.L[_k]) > 0)) {
            const tmp = this.L[_k >> 1];
            this.L[_k >> 1] = this.L[_k];
            this.L[_k] = tmp;
            _k = _k >> 1;
        }
    }
    fixdown(k) {
        let _k = k;
        while ((_k << 1) <= (this.count())) {
            let j = _k << 1;
            if ((j < this.count()) && (this.comp.compare(this.L[j], this.L[j + 1]) > 0)) {
                j++;
            }
            if (this.comp.compare(this.L[_k], this.L[j]) <= 0) {
                break;
            }
            const tmp = this.L[_k];
            this.L[_k] = this.L[j];
            this.L[j] = tmp;
            _k = j;
        }
    }
}
exports.Heap = Heap;
// For testing
function LinearDictionary(compareKeys) {
    const props = [];
    const compareProps = (a, b) => compareKeys(a.key, b.key);
    function diag() {
        console.log(`size is ${props.length}`);
    }
    function mapRange(action, accum, start, end) {
        let _start = start;
        let _end = end;
        if (props.length !== 0) {
            return;
        }
        if (_start === undefined) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _start = min().key;
        }
        if (_end === undefined) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _end = max().key;
        }
        for (let i = 0, len = props.length; i < len; i++) {
            if (compareKeys(_start, props[i].key) <= 0) {
                const ecmp = compareKeys(_end, props[i].key);
                if (ecmp < 0) {
                    break;
                }
                if (!action(props[i], accum)) {
                    break;
                }
            }
        }
    }
    function map(action, accum) {
        mapRange(action, accum);
    }
    function min() {
        if (props.length > 0) {
            return props[0];
        }
    }
    function max() {
        if (props.length > 0) {
            return props[props.length - 1];
        }
    }
    function get(key) {
        for (let i = 0, len = props.length; i < len; i++) {
            if (props[i].key == key) {
                return props[i];
            }
        }
    }
    function put(key, data) {
        if (key !== undefined) {
            if (data === undefined) {
                remove(key);
            }
            else {
                props.push({ key, data });
                props.sort(compareProps); // Go to insertion sort if too slow
            }
        }
    }
    function remove(key) {
        if (key !== undefined) {
            for (let i = 0, len = props.length; i < len; i++) {
                if (props[i].key == key) {
                    props[i] = props[len - 1];
                    props.length--;
                    props.sort(compareProps);
                    break;
                }
            }
        }
    }
    return {
        min: min,
        max: max,
        map: map,
        mapRange: mapRange,
        remove: remove,
        get: get,
        put: put,
        diag: diag,
    };
}
exports.LinearDictionary = LinearDictionary;
var RBColor;
(function (RBColor) {
    RBColor[RBColor["RED"] = 0] = "RED";
    RBColor[RBColor["BLACK"] = 1] = "BLACK";
})(RBColor = exports.RBColor || (exports.RBColor = {}));
class RedBlackTree {
    constructor(compareKeys, aug) {
        this.compareKeys = compareKeys;
        this.aug = aug;
    }
    makeNode(key, data, color, size) {
        const node = { key, data, color, size };
        if (this.aug && this.aug.init) {
            this.aug.init(node);
        }
        return node;
    }
    isRed(node) {
        return !!node && (node.color == 0 /* RED */);
    }
    nodeSize(node) {
        return node ? node.size : 0;
    }
    size() {
        return this.nodeSize(this.root);
    }
    isEmpty() {
        return !this.root;
    }
    get(key) {
        if (key !== undefined) {
            return this.nodeGet(this.root, key);
        }
    }
    nodeGet(node, key) {
        let _node = node;
        while (_node) {
            const cmp = this.compareKeys(key, _node.key);
            if (cmp < 0) {
                _node = _node.left;
            }
            else if (cmp > 0) {
                _node = _node.right;
            }
            else {
                return _node;
            }
        }
    }
    contains(key) {
        return this.get(key);
    }
    gather(key, matcher) {
        const results = [];
        if (key !== undefined) {
            this.nodeGather(this.root, results, key, matcher);
        }
        return results;
    }
    nodeGather(node, results, key, matcher) {
        if (node) {
            if (matcher.continueSubtree(node.left, key)) {
                this.nodeGather(node.left, results, key, matcher);
            }
            if (matcher.matchNode(node, key)) {
                results.push(node);
            }
            if (matcher.continueSubtree(node.right, key)) {
                this.nodeGather(node.right, results, key, matcher);
            }
        }
    }
    walkExactMatchesForward(compareFn, actionFn, continueLeftFn, continueRightFn) {
        this.nodeWalkExactMatchesForward(this.root, compareFn, actionFn, continueLeftFn, continueRightFn);
    }
    nodeWalkExactMatchesForward(node, compareFn, actionFn, continueLeftFn, continueRightFn) {
        if (!node) {
            return;
        }
        const result = compareFn(node);
        if (continueLeftFn(result)) {
            this.nodeWalkExactMatchesForward(node.left, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
        if (result === 0) {
            actionFn(node);
        }
        if (continueRightFn(result)) {
            this.nodeWalkExactMatchesForward(node.right, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
    }
    walkExactMatchesBackward(compareFn, actionFn, continueLeftFn, continueRightFn) {
        this.nodeWalkExactMatchesBackward(this.root, compareFn, actionFn, continueLeftFn, continueRightFn);
    }
    nodeWalkExactMatchesBackward(node, compareFn, actionFn, continueLeftFn, continueRightFn) {
        if (!node) {
            return;
        }
        const result = compareFn(node);
        if (continueRightFn(result)) {
            this.nodeWalkExactMatchesBackward(node.right, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
        if (result === 0) {
            actionFn(node);
        }
        if (continueLeftFn(result)) {
            this.nodeWalkExactMatchesBackward(node.left, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
    }
    put(key, data, conflict) {
        if (key !== undefined) {
            if (data === undefined) {
                this.remove(key);
            }
            else {
                this.root = this.nodePut(this.root, key, data, conflict);
                this.root.color = 1 /* BLACK */;
            }
        }
    }
    nodePut(node, key, data, conflict) {
        let _node = node;
        if (!_node) {
            return this.makeNode(key, data, 0 /* RED */, 1);
        }
        else {
            const cmp = this.compareKeys(key, _node.key);
            if (cmp < 0) {
                _node.left = this.nodePut(_node.left, key, data, conflict);
            }
            else if (cmp > 0) {
                _node.right = this.nodePut(_node.right, key, data, conflict);
            }
            else {
                if (conflict) {
                    const kd = conflict(key, _node.key, data, _node.data);
                    if (kd.key) {
                        _node.key = kd.key;
                    }
                    if (kd.data) {
                        _node.data = kd.data;
                    }
                    else {
                        _node.data = data;
                    }
                }
                else {
                    _node.data = data;
                }
            }
            if (this.isRed(_node.right) && (!this.isRed(_node.left))) {
                _node = this.rotateLeft(_node);
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            if (this.isRed(_node.left) && this.isRed(_node.left.left)) {
                _node = this.rotateRight(_node);
            }
            if (this.isRed(_node.left) && this.isRed(_node.right)) {
                this.flipColors(_node);
            }
            _node.size = this.nodeSize(_node.left) + this.nodeSize(_node.right) + 1;
            if (this.aug) {
                this.updateLocal(_node);
            }
            return _node;
        }
    }
    updateLocal(node) {
        if (this.aug) {
            if (this.isRed(node.left)) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.aug.update(node.left);
            }
            if (this.isRed(node.right)) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.aug.update(node.right);
            }
            this.aug.update(node);
        }
    }
    removeMin() {
        if (this.root) {
            if ((!this.isRed(this.root.left)) && (!this.isRed(this.root.right))) {
                this.root.color = 0 /* RED */;
            }
            this.root = this.nodeRemoveMin(this.root);
            if (this.root) {
                this.root.color = 1 /* BLACK */;
            }
        }
        // TODO: error on empty
    }
    nodeRemoveMin(node) {
        let _node = node;
        if (_node.left) {
            if ((!this.isRed(_node.left)) && (!this.isRed(_node.left.left))) {
                _node = this.moveRedLeft(_node);
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _node.left = this.nodeRemoveMin(_node.left);
            return this.balance(_node);
        }
    }
    removeMax() {
        if (this.root) {
            if ((!this.isRed(this.root.left)) && (!this.isRed(this.root.right))) {
                this.root.color = 0 /* RED */;
            }
            this.root = this.nodeRemoveMax(this.root);
            if (this.root) {
                this.root.color = 1 /* BLACK */;
            }
        }
        // TODO: error on empty
    }
    nodeRemoveMax(node) {
        let _node = node;
        if (this.isRed(_node.left)) {
            _node = this.rotateRight(_node);
        }
        if (!_node.right) {
            return undefined;
        }
        if ((!this.isRed(_node.right)) && (!this.isRed(_node.right.left))) {
            _node = this.moveRedRight(_node);
        }
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        _node.right = this.nodeRemoveMax(_node.right);
        return this.balance(_node);
    }
    remove(key) {
        if (key !== undefined) {
            if (!this.contains(key)) {
                return;
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            if ((!this.isRed(this.root.left)) && (!this.isRed(this.root.right))) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.root.color = 0 /* RED */;
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            this.root = this.nodeRemove(this.root, key);
        }
        // TODO: error on undefined key
    }
    nodeRemove(node, key) {
        let _node = node;
        if (this.compareKeys(key, _node.key) < 0) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            if ((!this.isRed(_node.left)) && (!this.isRed(_node.left.left))) {
                _node = this.moveRedLeft(_node);
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _node.left = this.nodeRemove(_node.left, key);
        }
        else {
            if (this.isRed(_node.left)) {
                _node = this.rotateRight(_node);
            }
            if ((this.compareKeys(key, _node.key) == 0) && (!_node.right)) {
                return undefined;
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            if ((!this.isRed(_node.right)) && (!this.isRed(_node.right.left))) {
                _node = this.moveRedRight(_node);
            }
            if (this.compareKeys(key, _node.key) == 0) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const subtreeMin = this.nodeMin(_node.right);
                _node.key = subtreeMin.key;
                _node.data = subtreeMin.data;
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                _node.right = this.nodeRemoveMin(_node.right);
            }
            else {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                _node.right = this.nodeRemove(_node.right, key);
            }
        }
        return this.balance(_node);
    }
    height() {
        return this.nodeHeight(this.root);
    }
    nodeHeight(node) {
        if (node === undefined) {
            return -1;
        }
        else {
            return 1 + Math.max(this.nodeHeight(node.left), this.nodeHeight(node.right));
        }
    }
    floor(key) {
        if (!this.isEmpty()) {
            return this.nodeFloor(this.root, key);
        }
    }
    nodeFloor(node, key) {
        if (node) {
            const cmp = this.compareKeys(key, node.key);
            if (cmp == 0) {
                return node;
            }
            else if (cmp < 0) {
                return this.nodeFloor(node.left, key);
            }
            else {
                const rightFloor = this.nodeFloor(node.right, key);
                if (rightFloor) {
                    return rightFloor;
                }
                else {
                    return node;
                }
            }
        }
    }
    ceil(key) {
        if (!this.isEmpty()) {
            return this.nodeCeil(this.root, key);
        }
    }
    nodeCeil(node, key) {
        if (node) {
            const cmp = this.compareKeys(key, node.key);
            if (cmp == 0) {
                return node;
            }
            else if (cmp > 0) {
                return this.nodeCeil(node.right, key);
            }
            else {
                const leftCeil = this.nodeCeil(node.left, key);
                if (leftCeil) {
                    return leftCeil;
                }
                else {
                    return node;
                }
            }
        }
    }
    min() {
        if (this.root) {
            return this.nodeMin(this.root);
        }
    }
    nodeMin(node) {
        if (!node.left) {
            return node;
        }
        else {
            return this.nodeMin(node.left);
        }
    }
    max() {
        if (this.root) {
            return this.nodeMax(this.root);
        }
    }
    nodeMax(node) {
        if (!node.right) {
            return node;
        }
        else {
            return this.nodeMax(node.right);
        }
    }
    rotateRight(node) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const leftChild = node.left;
        node.left = leftChild.right;
        leftChild.right = node;
        leftChild.color = leftChild.right.color;
        leftChild.right.color = 0 /* RED */;
        leftChild.size = node.size;
        node.size = this.nodeSize(node.left) + this.nodeSize(node.right) + 1;
        if (this.aug) {
            this.updateLocal(node);
            this.updateLocal(leftChild);
        }
        return leftChild;
    }
    rotateLeft(node) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const rightChild = node.right;
        node.right = rightChild.left;
        rightChild.left = node;
        rightChild.color = rightChild.left.color;
        rightChild.left.color = 0 /* RED */;
        rightChild.size = node.size;
        node.size = this.nodeSize(node.left) + this.nodeSize(node.right) + 1;
        if (this.aug) {
            this.updateLocal(node);
            this.updateLocal(rightChild);
        }
        return rightChild;
    }
    oppositeColor(c) {
        return (c == 1 /* BLACK */) ? 0 /* RED */ : 1 /* BLACK */;
    }
    flipColors(node) {
        node.color = this.oppositeColor(node.color);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        node.left.color = this.oppositeColor(node.left.color);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        node.right.color = this.oppositeColor(node.right.color);
    }
    moveRedLeft(node) {
        let _node = node;
        this.flipColors(_node);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        if (this.isRed(_node.right.left)) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _node.right = this.rotateRight(_node.right);
            _node = this.rotateLeft(_node);
            this.flipColors(_node);
        }
        return _node;
    }
    moveRedRight(node) {
        let _node = node;
        this.flipColors(_node);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        if (this.isRed(_node.left.left)) {
            _node = this.rotateRight(_node);
            this.flipColors(_node);
        }
        return _node;
    }
    balance(input) {
        let node = input;
        if (this.isRed(node.right)) {
            node = this.rotateLeft(node);
        }
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        if (this.isRed(node.left) && this.isRed(node.left.left)) {
            node = this.rotateRight(node);
        }
        if (this.isRed(node.left) && (this.isRed(node.right))) {
            this.flipColors(node);
        }
        node.size = this.nodeSize(node.left) + this.nodeSize(node.right) + 1;
        if (this.aug) {
            this.aug.update(node);
        }
        return node;
    }
    mapRange(action, accum, start, end) {
        this.nodeMap(this.root, action, start, end);
    }
    map(action, accum) {
        // TODO: optimize to avoid comparisons
        this.nodeMap(this.root, action, accum);
    }
    keys() {
        const keyList = [];
        const actions = {
            showStructure: true,
            infix: (node) => {
                keyList.push(node.key);
                return true;
            },
        };
        this.walk(actions);
        return keyList;
    }
    /**
     * Depth-first traversal with custom action; if action returns
     * false, traversal is halted.
     * @param action - action to apply to each node
     */
    walk(actions) {
        this.nodeWalk(this.root, actions);
    }
    walkBackward(actions) {
        this.nodeWalkBackward(this.root, actions);
    }
    nodeWalk(node, actions) {
        let go = true;
        if (node) {
            if (actions.pre) {
                if (actions.showStructure || (node.color === 1 /* BLACK */)) {
                    go = actions.pre(node);
                }
            }
            if (node.left) {
                go = this.nodeWalk(node.left, actions);
            }
            if (go && actions.infix) {
                if (actions.showStructure || (node.color === 1 /* BLACK */)) {
                    go = actions.infix(node);
                }
            }
            if (go) {
                go = this.nodeWalk(node.right, actions);
            }
            if (go && actions.post) {
                if (actions.showStructure || (node.color === 1 /* BLACK */)) {
                    go = actions.post(node);
                }
            }
        }
        return go;
    }
    nodeWalkBackward(node, actions) {
        let go = true;
        if (node) {
            if (actions.pre) {
                if (actions.showStructure || (node.color === 1 /* BLACK */)) {
                    go = actions.pre(node);
                }
            }
            if (node.right) {
                go = this.nodeWalkBackward(node.right, actions);
            }
            if (go && actions.infix) {
                if (actions.showStructure || (node.color === 1 /* BLACK */)) {
                    go = actions.infix(node);
                }
            }
            if (go) {
                go = this.nodeWalkBackward(node.left, actions);
            }
            if (go && actions.post) {
                if (actions.showStructure || (node.color === 1 /* BLACK */)) {
                    go = actions.post(node);
                }
            }
        }
        return go;
    }
    nodeMap(node, action, accum, start, end) {
        let _start = start;
        let _end = end;
        if (!node) {
            return true;
        }
        if (_start === undefined) {
            _start = this.nodeMin(node).key;
        }
        if (_end === undefined) {
            _end = this.nodeMax(node).key;
        }
        const cmpStart = this.compareKeys(_start, node.key);
        const cmpEnd = this.compareKeys(_end, node.key);
        let go = true;
        if (cmpStart < 0) {
            go = this.nodeMap(node.left, action, accum, _start, _end);
        }
        if (go && (cmpStart <= 0) && (cmpEnd >= 0)) {
            // REVIEW: test for black node here
            go = action(node, accum);
        }
        if (go && (cmpEnd > 0)) {
            go = this.nodeMap(node.right, action, accum, _start, _end);
        }
        return go;
    }
    diag() {
        console.log(`Height is ${this.height()}`);
    }
}
exports.RedBlackTree = RedBlackTree;
/**
 * Union of two ranges; assumes for both ranges start \<= end.
 * @param a - A range
 * @param b - A range
 */
function integerRangeUnion(a, b) {
    return {
        start: Math.min(a.start, b.start),
        end: Math.max(a.end, b.end),
    };
}
exports.integerRangeUnion = integerRangeUnion;
function integerRangeOverlaps(a, b) {
    return (a.start < b.end) && (a.end > b.start);
}
exports.integerRangeOverlaps = integerRangeOverlaps;
function integerRangeComparer(a, b) {
    if (a.start === b.start) {
        return a.end - b.end;
    }
    else {
        return a.start - b.start;
    }
}
exports.integerRangeComparer = integerRangeComparer;
const integerRangeCopy = (r) => ({ start: r.start, end: r.end });
exports.integerRangeCopy = integerRangeCopy;
const integerRangeToString = (range) => `[${range.start},${range.end})`;
exports.integerRangeToString = integerRangeToString;
// TODO: handle duplicate keys
class IntegerRangeTree {
    constructor() {
        this.ranges = new RedBlackTree(integerRangeComparer, this);
        this.diag = false;
    }
    remove(r) {
        this.ranges.remove(r);
    }
    put(r) {
        this.ranges.put(r, { minmax: exports.integerRangeCopy(r) });
    }
    toString() {
        return this.nodeToString(this.ranges.root);
    }
    nodeToString(node) {
        let buf = "";
        let indentAmt = 0;
        const actions = {
            pre: (n) => {
                let red = "";
                if (n.color === 0 /* RED */) {
                    red = "R ";
                }
                buf += MergeTree.internedSpaces(indentAmt);
                buf += `${red}key: ${exports.integerRangeToString(n.key)} minmax: ${exports.integerRangeToString(n.data.minmax)}\n`;
                indentAmt += 2;
                return true;
            },
            post: (n) => {
                indentAmt -= 2;
                return true;
            },
            showStructure: true,
        };
        this.ranges.nodeWalk(node, actions);
        return buf;
    }
    matchPos(pos) {
        return this.match({ start: pos, end: pos + 1 });
    }
    match(r) {
        return this.ranges.gather(r, this);
    }
    matchNode(node, key) {
        return !!node && integerRangeOverlaps(node.key, key);
    }
    continueSubtree(node, key) {
        const cont = !!node && integerRangeOverlaps(node.data.minmax, key);
        if (this.diag && (!cont)) {
            if (node) {
                console.log(`skipping subtree of size ${node.size} key ${exports.integerRangeToString(key)}`);
                console.log(this.nodeToString(node));
            }
        }
        return cont;
    }
    update(node) {
        if (node.left && node.right) {
            node.data.minmax = integerRangeUnion(node.key, integerRangeUnion(node.left.data.minmax, node.right.data.minmax));
        }
        else {
            if (node.left) {
                node.data.minmax = integerRangeUnion(node.key, node.left.data.minmax);
            }
            else if (node.right) {
                node.data.minmax = integerRangeUnion(node.key, node.right.data.minmax);
            }
            else {
                node.data.minmax = exports.integerRangeCopy(node.key);
            }
        }
    }
}
exports.IntegerRangeTree = IntegerRangeTree;
const intervalComparer = (a, b) => a.compare(b);
exports.intervalComparer = intervalComparer;
class IntervalTree {
    constructor() {
        this.intervals = new RedBlackTree(exports.intervalComparer, this);
        this.diag = false;
        this.timePut = false;
        this.putTime = 0;
        this.putCount = 0;
    }
    printTiming() {
        console.log(`put total = ${this.putTime} avg=${(this.putTime / this.putCount).toFixed(2)}`);
    }
    remove(x) {
        this.intervals.remove(x);
    }
    put(x, conflict) {
        let rbConflict;
        if (conflict) {
            rbConflict = (key, currentKey) => {
                const ival = conflict(key, currentKey);
                return {
                    key: ival,
                };
            };
        }
        if (this.timePut) {
            const trace = common_utils_1.Trace.start();
            this.intervals.put(x, { minmax: x.clone() }, rbConflict);
            this.putTime += trace.trace().duration * 1000;
            this.putCount++;
        }
        else {
            this.intervals.put(x, { minmax: x.clone() }, rbConflict);
        }
    }
    map(fn) {
        const actions = {
            infix: (node) => {
                fn(node.key);
                return true;
            },
            showStructure: true,
        };
        this.intervals.walk(actions);
    }
    mapBackward(fn) {
        const actions = {
            infix: (node) => {
                fn(node.key);
                return true;
            },
            showStructure: true,
        };
        this.intervals.walkBackward(actions);
    }
    // TODO: toString()
    match(x) {
        return this.intervals.gather(x, this);
    }
    matchNode(node, key) {
        return !!node && node.key.overlaps(key);
    }
    continueSubtree(node, key) {
        const cont = !!node && node.data.minmax.overlaps(key);
        if (this.diag && (!cont)) {
            if (node) {
                console.log(`skipping subtree of size ${node.size} key ${key.toString()}`);
                // console.log(this.nodeToString(node));
            }
        }
        return cont;
    }
    update(node) {
        if (node.left && node.right) {
            node.data.minmax = node.key.union(node.left.data.minmax.union(node.right.data.minmax));
        }
        else {
            if (node.left) {
                node.data.minmax = node.key.union(node.left.data.minmax);
            }
            else if (node.right) {
                node.data.minmax = node.key.union(node.right.data.minmax);
            }
            else {
                node.data.minmax = node.key.clone();
            }
        }
    }
}
exports.IntervalTree = IntervalTree;
class TST {
    constructor() {
        this.n = 0;
    }
    size() {
        return this.n;
    }
    contains(key) {
        return this.get(key);
    }
    get(key) {
        const x = this.nodeGet(this.root, key, 0);
        if (x === undefined) {
            return undefined;
        }
        return x.val;
    }
    nodeGet(x, key, d) {
        if (x === undefined) {
            return undefined;
        }
        const c = key.charAt(d);
        if (c < x.c) {
            return this.nodeGet(x.left, key, d);
        }
        else if (c > x.c) {
            return this.nodeGet(x.right, key, d);
        }
        else if (d < (key.length - 1)) {
            return this.nodeGet(x.mid, key, d + 1);
        }
        else {
            return x;
        }
    }
    put(key, val) {
        if (!this.contains(key)) {
            this.n++;
        }
        this.root = this.nodePut(this.root, key, val, 0);
        // console.log(`put ${key}`);
    }
    nodePut(x, key, val, d) {
        let _x = x;
        const c = key.charAt(d);
        if (_x === undefined) {
            _x = { c };
        }
        if (c < _x.c) {
            _x.left = this.nodePut(_x.left, key, val, d);
        }
        else if (c > _x.c) {
            _x.right = this.nodePut(_x.right, key, val, d);
        }
        else if (d < (key.length - 1)) {
            _x.mid = this.nodePut(_x.mid, key, val, d + 1);
        }
        else {
            _x.val = val;
        }
        return _x;
    }
    neighbors(text, distance = 2) {
        let q = [];
        this.nodeProximity(this.root, { text: "" }, 0, text, distance, q);
        q = q.filter((value) => (value.text.length > 0));
        return q;
    }
    keysWithPrefix(text) {
        const q = [];
        const x = this.nodeGet(this.root, text, 0);
        if (x === undefined) {
            return q;
        }
        if (x.val !== undefined) {
            q.push(text);
        }
        this.collect(x.mid, { text }, q);
        return q;
    }
    collect(x, prefix, q) {
        if (x === undefined) {
            return;
        }
        this.collect(x.left, prefix, q);
        if (x.val !== undefined) {
            q.push(prefix.text + x.c);
        }
        this.collect(x.mid, { text: prefix.text + x.c }, q);
        this.collect(x.right, prefix, q);
    }
    mapNode(x, prefix, fn) {
        if (x === undefined) {
            return;
        }
        const key = prefix.text + x.c;
        this.mapNode(x.left, prefix, fn);
        if (x.val) {
            fn(key, x.val);
        }
        this.mapNode(x.mid, { text: key }, fn);
        this.mapNode(x.right, prefix, fn);
    }
    map(fn) {
        this.mapNode(this.root, { text: "" }, fn);
    }
    pairsWithPrefix(text) {
        const q = [];
        const x = this.nodeGet(this.root, text, 0);
        if (x === undefined) {
            return q;
        }
        if (x.val !== undefined) {
            q.push({ key: text, val: x.val });
        }
        this.collectPairs(x.mid, { text }, q);
        return q;
    }
    collectPairs(x, prefix, q) {
        if (x === undefined) {
            return;
        }
        this.collectPairs(x.left, prefix, q);
        if (x.val !== undefined) {
            q.push({ key: prefix.text + x.c, val: x.val });
        }
        this.collectPairs(x.mid, { text: prefix.text + x.c }, q);
        this.collectPairs(x.right, prefix, q);
    }
    patternCollect(x, prefix, d, pattern, q) {
        if (x === undefined) {
            return;
        }
        const c = pattern.charAt(d);
        if ((c === ".") || (c < x.c)) {
            this.patternCollect(x.left, prefix, d, pattern, q);
        }
        else if ((c === ".") || (c === x.c)) {
            if ((d === (pattern.length - 1)) && (x.val !== undefined)) {
                q.push(prefix.text + x.c);
            }
            else if (d < (pattern.length - 1)) {
                this.patternCollect(x.mid, { text: prefix.text + x.c }, d + 1, pattern, q);
            }
        }
        if ((c === ".") || (c > x.c)) {
            this.patternCollect(x.right, prefix, d, pattern, q);
        }
    }
    nodeProximity(x, prefix, d, pattern, distance, q) {
        if ((x === undefined) || (distance < 0)) {
            return;
        }
        const c = pattern.charAt(d);
        if ((distance > 0) || (c < x.c)) {
            this.nodeProximity(x.left, prefix, d, pattern, distance, q);
        }
        if (x.val !== undefined) {
            const remD = distance - (pattern.length - d);
            if (remD >= 0) {
                let invD = distance;
                if (c !== x.c) {
                    invD--;
                }
                q.push({ text: prefix.text + x.c, val: x.val, invDistance: invD });
            }
        }
        const recurD = (d < (pattern.length - 1)) ? d + 1 : d;
        if (c === x.c) {
            this.nodeProximity(x.mid, { text: prefix.text + x.c }, recurD, pattern, distance, q);
        }
        else {
            this.nodeProximity(x.mid, { text: prefix.text + x.c }, recurD, pattern, distance - 1, q);
        }
        if ((distance > 0) || (c > x.c)) {
            this.nodeProximity(x.right, prefix, d, pattern, distance, q);
        }
    }
    match(pattern) {
        const q = [];
        this.patternCollect(this.root, { text: "" }, 0, pattern, q);
        return q;
    }
}
exports.TST = TST;
//# sourceMappingURL=collections.js.map