"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Decoder = void 0;
const tslib_1 = require("tslib");
const ClockDecoder_1 = require("../../../../json-crdt-patch/codec/clock/ClockDecoder");
const CrdtReader_1 = require("../../../../json-crdt-patch/util/binary/CrdtReader");
const Model_1 = require("../../../model/Model");
const CborDecoderBase_1 = require("@jsonjoy.com/json-pack/lib/cbor/CborDecoderBase");
const nodes = tslib_1.__importStar(require("../../../nodes"));
const constants_1 = require("../../structural/binary/constants");
const insertion_1 = require("@jsonjoy.com/util/lib/sort/insertion");
class Decoder {
    constructor() {
        this.clockDecoder = undefined;
        this.time = -1;
        this.decoder = new CborDecoderBase_1.CborDecoderBase(new CrdtReader_1.CrdtReader());
    }
    decode(view, meta) {
        this.clockDecoder = undefined;
        this.time = -1;
        this.decoder.reader.reset(meta);
        this.decodeClockTable();
        const clock = this.clockDecoder.clock;
        this.doc = Model_1.Model.create(void 0, clock);
        this.doc.root = new nodes.RootNode(this.doc, this.cRoot(view).id);
        this.clockDecoder = undefined;
        return this.doc;
    }
    decodeClockTable() {
        const reader = this.decoder.reader;
        const clockTableOffset = reader.u32();
        const offset = reader.x;
        reader.x += clockTableOffset;
        const length = reader.vu57();
        const sessionId = reader.vu57();
        const time = reader.vu57();
        this.clockDecoder = new ClockDecoder_1.ClockDecoder(sessionId, time);
        for (let i = 1; i < length; i++) {
            const sid = reader.vu57();
            const time = reader.vu57();
            this.clockDecoder.pushTuple(sid, time);
        }
        reader.x = offset;
    }
    ts() {
        const [sessionIndex, timeDiff] = this.decoder.reader.id();
        return this.clockDecoder.decodeId(sessionIndex, timeDiff);
    }
    cRoot(view) {
        const reader = this.decoder.reader;
        const peek = reader.uint8[reader.x];
        return !peek ? Model_1.UNDEFINED : this.cNode(view);
    }
    cNode(view) {
        const reader = this.decoder.reader;
        const id = this.ts();
        const octet = reader.u8();
        const major = octet >> 5;
        const minor = octet & 0b11111;
        const length = minor < 24 ? minor : minor === 24 ? reader.u8() : minor === 25 ? reader.u16() : reader.u32();
        switch (major) {
            case constants_1.CRDT_MAJOR.CON:
                return this.cCon(view, id, length);
            case constants_1.CRDT_MAJOR.VAL:
                return this.cVal(view, id);
            case constants_1.CRDT_MAJOR.OBJ:
                return this.cObj(view, id, length);
            case constants_1.CRDT_MAJOR.VEC:
                return this.cVec(view, id, length);
            case constants_1.CRDT_MAJOR.STR:
                return this.cStr(view, id, length);
            case constants_1.CRDT_MAJOR.BIN:
                return this.cBin(view, id, length);
            case constants_1.CRDT_MAJOR.ARR:
                return this.cArr(view, id, length);
        }
        throw new Error('UNKNOWN_NODE');
    }
    cCon(view, id, length) {
        const doc = this.doc;
        const node = new nodes.ConNode(id, length ? this.ts() : view);
        doc.index.set(id, node);
        return node;
    }
    cVal(view, id) {
        const child = this.cNode(view);
        const doc = this.doc;
        const node = new nodes.ValNode(doc, id, child.id);
        doc.index.set(id, node);
        return node;
    }
    cObj(view, id, length) {
        const obj = new nodes.ObjNode(this.doc, id);
        if (!view || typeof view !== 'object')
            throw new Error('INVALID_OBJ');
        const keys = (0, insertion_1.sort)(Object.keys(view));
        if (keys.length !== length)
            throw new Error('INVALID_OBJ');
        const objKeys = obj.keys;
        for (let i = 0; i < length; i++) {
            const key = keys[i];
            const childNode = this.cNode(view[key]);
            objKeys.set(key, childNode.id);
        }
        this.doc.index.set(id, obj);
        return obj;
    }
    cVec(view, id, length) {
        const obj = new nodes.VecNode(this.doc, id);
        if (!Array.isArray(view) || view.length !== length)
            throw new Error('INVALID_VEC');
        const elements = obj.elements;
        for (let i = 0; i < length; i++) {
            const child = this.cNode(view[i]);
            const childId = child.id;
            if (childId.sid === 0 /* SESSION.SYSTEM */)
                elements.push(undefined);
            else
                elements.push(childId);
        }
        this.doc.index.set(id, obj);
        return obj;
    }
    cStr(view, id, length) {
        if (typeof view !== 'string')
            throw new Error('INVALID_STR');
        const node = new nodes.StrNode(id);
        const reader = this.decoder.reader;
        let offset = 0;
        node.ingest(length, () => {
            const id = this.ts();
            const [deleted, span] = reader.b1vu56();
            if (deleted)
                return new nodes.StrChunk(id, span, '');
            const text = view.slice(offset, offset + span);
            offset += span;
            return new nodes.StrChunk(id, text.length, text);
        });
        this.doc.index.set(id, node);
        return node;
    }
    cBin(view, id, length) {
        if (!(view instanceof Uint8Array))
            throw new Error('INVALID_BIN');
        const node = new nodes.BinNode(id);
        const reader = this.decoder.reader;
        let offset = 0;
        node.ingest(length, () => {
            const id = this.ts();
            const [deleted, span] = reader.b1vu56();
            if (deleted)
                return new nodes.BinChunk(id, span, undefined);
            const slice = view.slice(offset, offset + span);
            offset += span;
            return new nodes.BinChunk(id, slice.length, slice);
        });
        this.doc.index.set(id, node);
        return node;
    }
    cArr(view, id, length) {
        if (!Array.isArray(view))
            throw new Error('INVALID_ARR');
        const obj = new nodes.ArrNode(this.doc, id);
        const reader = this.decoder.reader;
        let i = 0;
        obj.ingest(length, () => {
            const id = this.ts();
            const [deleted, span] = reader.b1vu56();
            if (deleted)
                return new nodes.ArrChunk(id, span, undefined);
            const ids = [];
            for (let j = 0; j < span; j++)
                ids.push(this.cNode(view[i++]).id);
            return new nodes.ArrChunk(id, span, ids);
        });
        this.doc.index.set(id, obj);
        return obj;
    }
}
exports.Decoder = Decoder;
//# sourceMappingURL=Decoder.js.map