"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ArrNode = exports.ArrChunk = void 0;
const AbstractRga_1 = require("../rga/AbstractRga");
const clock_1 = require("../../../json-crdt-patch/clock");
const printBinary_1 = require("tree-dump/lib/printBinary");
const printTree_1 = require("tree-dump/lib/printTree");
/**
 * @ignore
 * @category CRDT Node
 */
class ArrChunk {
    constructor(id, span, data) {
        this.id = id;
        this.span = span;
        this.len = data ? span : 0;
        this.del = !data;
        this.p = undefined;
        this.l = undefined;
        this.r = undefined;
        this.s = undefined;
        this.data = data;
    }
    merge(data) {
        this.data.push(...data);
        this.span = this.data.length;
    }
    split(ticks) {
        const span = this.span;
        this.span = ticks;
        if (!this.del) {
            const data = this.data;
            const rightData = data.splice(ticks);
            const chunk = new ArrChunk((0, clock_1.tick)(this.id, ticks), span - ticks, rightData);
            return chunk;
        }
        return new ArrChunk((0, clock_1.tick)(this.id, ticks), span - ticks, undefined);
    }
    delete() {
        this.del = true;
        this.data = undefined;
    }
    clone() {
        return new ArrChunk(this.id, this.span, this.data ? [...this.data] : undefined);
    }
    view() {
        return this.data ? [...this.data] : [];
    }
}
exports.ArrChunk = ArrChunk;
/**
 * Represents the `arr` JSON CRDT type, which is a Replicated Growable Array
 * (RGA). Each element ot the array is a reference to another JSON CRDT node.
 *
 * @category CRDT Node
 */
class ArrNode extends AbstractRga_1.AbstractRga {
    constructor(doc, id) {
        super(id);
        this.doc = doc;
        /** @ignore */
        this._tick = 0;
        /** @ignore */
        this._view = [];
        /** @ignore */
        this.api = undefined;
    }
    /**
     * Returns a reference to an element at a given position in the array.
     *
     * @param position The position of the element to get.
     * @returns An element of the array, if any.
     */
    get(position) {
        const pair = this.findChunk(position);
        if (!pair)
            return undefined;
        return pair[0].data[pair[1]];
    }
    /**
     * Returns a JSON node at a given position in the array.
     *
     * @param position The position of the element to get.
     * @returns A JSON node, if any.
     */
    getNode(position) {
        const id = this.get(position);
        if (!id)
            return undefined;
        return this.doc.index.get(id);
    }
    /**
     * Returns ID of the RGA slot (not the referenced JSON node) at a given position
     * in the array. The ID is a timestamp the unique slot of the element in the RGA.
     * To retrieve the JSON node ID referenced by the slot, use {@link get} method.
     *
     * @todo Rename to `getRef`.
     *
     * @param position The position of the element to get.
     * @returns ID of the RGA slot.
     */
    getId(position) {
        const pair = this.findChunk(position);
        if (!pair)
            return undefined;
        const [chunk, offset] = pair;
        const id = chunk.id;
        return !offset ? id : (0, clock_1.tick)(id, offset);
    }
    getById(id) {
        const chunk = this.findById(id);
        if (!chunk || chunk.del)
            return undefined;
        const offset = id.time - chunk.id.time;
        return chunk.data[offset];
    }
    /**
     * Updates an array element in-place. Used by the "upd_arr" operation.
     *
     * @todo Verify that the new ID is greater than the old ID.
     *
     * @param ref A reference to the element slot in the array.
     * @param val A new value to set in the slot.
     * @returns The old value of the slot, if any.
     */
    upd(ref, val) {
        const chunk = this.findById(ref);
        if (!chunk)
            return;
        const data = chunk.data;
        if (!data)
            return;
        const offset = ref.time - chunk.id.time;
        const currentVal = data[offset];
        if (currentVal && (0, clock_1.compare)(currentVal, val) >= 0)
            return;
        data[offset] = val;
        this.onChange();
        return currentVal;
    }
    // -------------------------------------------------------------- AbstractRga
    /** @ignore */
    createChunk(id, data) {
        return new ArrChunk(id, data ? data.length : 0, data);
    }
    /** @ignore */
    onChange() { }
    toStringName() {
        return this.name();
    }
    // ----------------------------------------------------------------- JsonNode
    /** @ignore */
    child() {
        return undefined;
    }
    /** @ignore */
    container() {
        return this;
    }
    view() {
        const doc = this.doc;
        const tick = doc.clock.time + doc.tick;
        const _view = this._view;
        if (this._tick === tick)
            return _view;
        const view = [];
        const index = doc.index;
        let useCache = true;
        for (let chunk = this.first(); chunk; chunk = this.next(chunk)) {
            if (chunk.del)
                continue;
            for (const node of chunk.data) {
                const elementNode = index.get(node);
                if (!elementNode) {
                    useCache = false;
                    continue;
                }
                const element = elementNode.view();
                if (_view[view.length] !== element)
                    useCache = false;
                view.push(element);
            }
        }
        if (_view.length !== view.length)
            useCache = false;
        const result = useCache ? _view : ((this._tick = tick), (this._view = view));
        return result;
    }
    /** @ignore */
    children(callback) {
        const index = this.doc.index;
        for (let chunk = this.first(); chunk; chunk = this.next(chunk)) {
            const data = chunk.data;
            if (!data)
                continue;
            const length = data.length;
            for (let i = 0; i < length; i++)
                callback(index.get(data[i]));
        }
    }
    name() {
        return 'arr';
    }
    // ---------------------------------------------------------------- Printable
    /** @ignore */
    printChunk(tab, chunk) {
        const pos = this.pos(chunk);
        let valueTree = '';
        if (!chunk.del) {
            const index = this.doc.index;
            valueTree = (0, printTree_1.printTree)(tab, chunk
                .data.map((id) => index.get(id))
                .filter((node) => !!node)
                .map((node, i) => (tab) => `[${pos + i}]: ${node.toString(tab + '    ' + ' '.repeat(String(i).length))}`));
        }
        return (this.formatChunk(chunk) +
            valueTree +
            (0, printBinary_1.printBinary)(tab, [
                chunk.l ? (tab) => this.printChunk(tab, chunk.l) : null,
                chunk.r ? (tab) => this.printChunk(tab, chunk.r) : null,
            ]));
    }
}
exports.ArrNode = ArrNode;
//# sourceMappingURL=ArrNode.js.map