"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Encoder = void 0;
const tslib_1 = require("tslib");
const nodes = tslib_1.__importStar(require("../../../nodes"));
const ClockEncoder_1 = require("../../../../json-crdt-patch/codec/clock/ClockEncoder");
const CrdtWriter_1 = require("../../../../json-crdt-patch/util/binary/CrdtWriter");
const clock_1 = require("../../../../json-crdt-patch/clock");
const CborEncoder_1 = require("@jsonjoy.com/json-pack/lib/cbor/CborEncoder");
const constants_1 = require("../../structural/binary/constants");
const insertion_1 = require("@jsonjoy.com/util/lib/sort/insertion");
const Model_1 = require("../../../model/Model");
class Encoder {
    constructor() {
        this.clockEncoder = new ClockEncoder_1.ClockEncoder();
        this.time = 0;
        this.viewEncoder = new CborEncoder_1.CborEncoder(new CrdtWriter_1.CrdtWriter());
        this.metaEncoder = new CborEncoder_1.CborEncoder(new CrdtWriter_1.CrdtWriter());
        this.index = 0;
        this.cTableEntry = (entry) => {
            const clock = entry.clock;
            const writer = this.metaEncoder.writer;
            writer.vu57(clock.sid);
            writer.vu57(clock.time);
        };
        this.ts = (ts) => {
            const relativeId = this.clockEncoder.append(ts);
            this.metaEncoder.writer.id(relativeId.sessionIndex, relativeId.timeDiff);
        };
    }
    encode(doc) {
        this.doc = doc;
        this.index = 0;
        this.metaEncoder.writer.reset();
        this.encodeLogical(doc);
        return [this.viewEncoder.writer.flush(), this.metaEncoder.writer.flush()];
    }
    encodeLogical(model) {
        const writer = this.metaEncoder.writer;
        this.clockEncoder.reset(model.clock);
        writer.ensureCapacity(4);
        const x0 = writer.x0;
        const x = writer.x;
        writer.x += 4;
        this.cRoot(model.root);
        this.encodeClockTable(x0, x);
    }
    encodeClockTable(x0, x) {
        const writer = this.metaEncoder.writer;
        const shift = writer.x0 - x0;
        writer.view.setUint32(writer.x0 + (x - x0), writer.x - x - shift - 4);
        const clockEncoder = this.clockEncoder;
        const table = clockEncoder.table;
        const length = table.size;
        writer.vu57(length);
        table.forEach(this.cTableEntry);
    }
    cRoot(root) {
        const val = root.val;
        if (val.sid === 0 /* SESSION.SYSTEM */)
            this.metaEncoder.writer.u8(0);
        else
            this.cNode(root.node());
    }
    writeTL(majorOverlay, length) {
        const writer = this.metaEncoder.writer;
        if (length < 24)
            writer.u8(majorOverlay + length);
        else if (length <= 0xff)
            writer.u16(((majorOverlay + 24) << 8) + length);
        else if (length <= 0xffff)
            writer.u8u16(majorOverlay + 25, length);
        else
            writer.u8u32(majorOverlay + 26, length);
    }
    cNode(node) {
        // TODO: PERF: use a switch?
        if (node instanceof nodes.ConNode)
            this.cCon(node);
        else if (node instanceof nodes.ValNode)
            this.cVal(node);
        else if (node instanceof nodes.StrNode)
            this.cStr(node);
        else if (node instanceof nodes.ObjNode)
            this.cObj(node);
        else if (node instanceof nodes.VecNode)
            this.cVec(node);
        else if (node instanceof nodes.ArrNode)
            this.cArr(node);
        else if (node instanceof nodes.BinNode)
            this.cBin(node);
    }
    cCon(node) {
        const val = node.val;
        this.ts(node.id);
        const metaEncoder = this.metaEncoder;
        if (val instanceof clock_1.Timestamp) {
            this.viewEncoder.writeNull();
            metaEncoder.writer.u8(1); // this.writeTL(CRDT_MAJOR_OVERLAY.CON, 1);
            this.ts(val);
        }
        else {
            this.viewEncoder.writeAny(val);
            metaEncoder.writer.u8(0); // this.writeTL(CRDT_MAJOR_OVERLAY.CON, 0);
        }
    }
    cVal(node) {
        this.ts(node.id);
        this.metaEncoder.writer.u8(0b00100000); // this.writeTL(CRDT_MAJOR_OVERLAY.VAL, 0);
        this.cNode(node.node());
    }
    cObj(node) {
        this.ts(node.id);
        const keys = (0, insertion_1.sort)([...node.keys.keys()]);
        const size = keys.length;
        this.writeTL(constants_1.CRDT_MAJOR_OVERLAY.OBJ, size);
        const viewEncoder = this.viewEncoder;
        viewEncoder.writeObjHdr(size);
        for (let i = 0; i < size; i++) {
            const key = keys[i];
            viewEncoder.writeStr(key);
            this.cNode(node.get(key));
        }
    }
    cVec(node) {
        this.ts(node.id);
        const elements = node.elements;
        const length = elements.length;
        this.writeTL(constants_1.CRDT_MAJOR_OVERLAY.VEC, length);
        this.viewEncoder.writeArrHdr(length);
        const index = this.doc.index;
        for (let i = 0; i < length; i++) {
            const elementId = elements[i];
            if (!elementId)
                this.cCon(Model_1.UNDEFINED);
            else
                this.cNode(index.get(elementId));
        }
    }
    cStr(node) {
        const ts = this.ts;
        ts(node.id);
        this.writeTL(constants_1.CRDT_MAJOR_OVERLAY.STR, node.count);
        this.viewEncoder.writeStr(node.view());
        const writer = this.metaEncoder.writer;
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            ts(chunk.id);
            writer.b1vu56(~~chunk.del, chunk.span);
        }
    }
    cBin(node) {
        const ts = this.ts;
        ts(node.id);
        this.writeTL(constants_1.CRDT_MAJOR_OVERLAY.BIN, node.count);
        this.viewEncoder.writeBin(node.view());
        const writer = this.metaEncoder.writer;
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            ts(chunk.id);
            writer.b1vu56(~~chunk.del, chunk.span);
        }
    }
    cArr(node) {
        const ts = this.ts;
        ts(node.id);
        this.writeTL(constants_1.CRDT_MAJOR_OVERLAY.ARR, node.count);
        this.viewEncoder.writeArrHdr(node.length());
        const writer = this.metaEncoder.writer;
        const index = this.doc.index;
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            ts(chunk.id);
            const deleted = chunk.del;
            const span = chunk.span;
            writer.b1vu56(~~deleted, span);
            if (!deleted) {
                const elements = chunk.data;
                const elementsLength = elements.length;
                for (let i = 0; i < elementsLength; i++)
                    this.cNode(index.get(elements[i]));
            }
        }
    }
}
exports.Encoder = Encoder;
//# sourceMappingURL=Encoder.js.map