"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.walkList = exports.List = void 0;
const container_utils_1 = require("@fluidframework/container-utils");
class HeadNode {
    constructor(list) {
        this._next = this;
        this._prev = this;
        this.headNode = this;
        if (list) {
            this._list = list;
        }
    }
    get next() {
        return this._next === this.headNode
            ? undefined
            : this._next;
    }
    get prev() {
        return this._prev === this.headNode
            ? undefined
            : this._prev;
    }
    get list() {
        return this.headNode._list;
    }
}
const DeadHead = new HeadNode(undefined);
class DataNode extends HeadNode {
    constructor(headNode, data) {
        super(undefined);
        this.data = data;
        this.headNode = headNode;
    }
}
function insertAfter(node, items) {
    let previousNode = node;
    const oldNext = previousNode._next;
    let newRange;
    items.forEach((n) => {
        const newNode = new DataNode(node.headNode, n);
        if (newRange === undefined) {
            newRange = { first: newNode, last: newNode };
        }
        else {
            newRange.last = newNode;
        }
        newNode._prev = previousNode;
        previousNode._next = newNode;
        previousNode = newNode;
    });
    oldNext._prev = previousNode;
    previousNode._next = oldNext;
    // explicitly prevent newRange from being undefined without casting,
    // and without additional conditionals, as this is used in some perf critical areas.
    // i could have just asserted, but that throws a non-user friendly error,
    // so i went with a more user-friendly error, which describes the
    // only condition that could lead to this being undefined in the current code.
    if (newRange === undefined) {
        throw new container_utils_1.UsageError("items must not be empty");
    }
    return newRange;
}
class List {
    constructor() {
        this._len = 0;
        this.headNode = new HeadNode(this);
    }
    map(callbackfn) {
        let node = this.first;
        const iterator = {
            next() {
                if (node === undefined) {
                    return { done: true, value: undefined };
                }
                const rtn = { value: callbackfn(node), done: false };
                node = node.next;
                return rtn;
            },
            [Symbol.iterator]() {
                return this;
            },
        };
        return iterator;
    }
    insertAfter(preceding, ...items) {
        if (!this._includes(preceding)) {
            throw new Error("preceding not in list");
        }
        this._len += items.length;
        return insertAfter(preceding, items);
    }
    pop() {
        return this.remove(this.last);
    }
    push(...items) {
        this._len += items.length;
        const start = this.headNode._prev;
        return insertAfter(start, items);
    }
    shift() {
        return this.remove(this.first);
    }
    unshift(...items) {
        this._len += items.length;
        return insertAfter(this.headNode, items);
    }
    includes(node) {
        return this._includes(node);
    }
    _includes(node) {
        return node instanceof DataNode && node.headNode === this.headNode;
    }
    remove(node) {
        if (this._includes(node)) {
            node._prev._next = node._next;
            node._next._prev = node._prev;
            node.headNode = node._next = node._prev = DeadHead;
            this._len--;
            return node;
        }
        return undefined;
    }
    [Symbol.iterator]() {
        let value = this.first;
        const iterator = {
            next() {
                if (value !== undefined) {
                    const rtn = { value, done: false };
                    value = value.next;
                    return rtn;
                }
                return { value: undefined, done: true };
            },
            [Symbol.iterator]() {
                return this;
            },
        };
        return iterator;
    }
    get length() { return this._len; }
    get empty() { return this._len === 0; }
    get first() {
        return this.headNode.next;
    }
    get last() {
        return this.headNode.prev;
    }
}
exports.List = List;
function walkList(list, visitor, start, forward = true) {
    let current;
    if (start) {
        if (!list.includes(start)) {
            throw new container_utils_1.UsageError("start must be in the provided list");
        }
        current = start;
    }
    else {
        current = forward ? list.first : list.last;
    }
    while (current !== undefined) {
        if (visitor(current) === false) {
            return false;
        }
        current = forward ? current.next : current.prev;
    }
    return true;
}
exports.walkList = walkList;
//# sourceMappingURL=list.js.map