import * as nodes from '../../../nodes';
import { ClockEncoder } from '../../../../json-crdt-patch/codec/clock/ClockEncoder';
import { CrdtWriter } from '../../../../json-crdt-patch/util/binary/CrdtWriter';
import { Timestamp } from '../../../../json-crdt-patch/clock';
import { CborEncoder } from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder';
import { CRDT_MAJOR_OVERLAY } from './constants';
export class Encoder extends CborEncoder {
    clockEncoder = new ClockEncoder();
    time = 0;
    doc;
    constructor(writer) {
        super(writer || new CrdtWriter());
    }
    encode(doc) {
        this.doc = doc;
        const writer = this.writer;
        writer.reset();
        if (doc.clock.sid === 1 /* SESSION.SERVER */)
            this.encodeServer(doc);
        else
            this.encodeLogical(doc);
        return writer.flush();
    }
    encodeLogical(model) {
        const writer = this.writer;
        this.ts = this.tsLogical;
        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);
    }
    encodeServer(model) {
        this.ts = this.tsServer;
        const writer = this.writer;
        writer.u8(0b10000000);
        writer.vu57((this.time = model.clock.time));
        this.cRoot(model.root);
    }
    encodeClockTable(x0, x) {
        const writer = this.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);
    }
    cTableEntry = (entry) => {
        const clock = entry.clock;
        const writer = this.writer;
        writer.vu57(clock.sid);
        writer.vu57(clock.time);
    };
    tsLogical = (ts) => {
        const relativeId = this.clockEncoder.append(ts);
        this.writer.id(relativeId.sessionIndex, relativeId.timeDiff);
    };
    tsServer = (ts) => {
        this.writer.vu57(ts.time);
    };
    ts = this.tsLogical;
    cRoot(root) {
        const val = root.val;
        if (val.sid === 0 /* SESSION.SYSTEM */)
            this.writer.u8(0);
        else
            this.cNode(root.node());
    }
    writeTL(majorOverlay, length) {
        const writer = this.writer;
        if (length < 0b11111)
            writer.u8(majorOverlay | length);
        else {
            writer.u8(majorOverlay | 0b11111);
            writer.vu57(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);
        if (val instanceof Timestamp) {
            this.writer.u8(1); // this.writeTL(CRDT_MAJOR_OVERLAY.CON, 1);
            this.ts(val);
        }
        else {
            this.writer.u8(0); // this.writeTL(CRDT_MAJOR_OVERLAY.CON, 0);
            this.writeAny(val);
        }
    }
    cVal(node) {
        this.ts(node.id);
        this.writer.u8(0b00100000); // this.writeTL(CRDT_MAJOR_OVERLAY.VAL, 0);
        this.cNode(node.node());
    }
    cObj(node) {
        this.ts(node.id);
        const keys = node.keys;
        this.writeTL(CRDT_MAJOR_OVERLAY.OBJ, keys.size);
        keys.forEach(this.cKey);
    }
    cKey = (val, key) => {
        this.writeStr(key);
        this.cNode(this.doc.index.get(val));
    };
    cVec(node) {
        const elements = node.elements;
        const length = elements.length;
        this.ts(node.id);
        this.writeTL(CRDT_MAJOR_OVERLAY.VEC, length);
        const index = this.doc.index;
        for (let i = 0; i < length; i++) {
            const elementId = elements[i];
            if (!elementId)
                this.writer.u8(0);
            else
                this.cNode(index.get(elementId));
        }
    }
    cStr(node) {
        const ts = this.ts;
        ts(node.id);
        this.writeTL(CRDT_MAJOR_OVERLAY.STR, node.count);
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            ts(chunk.id);
            if (chunk.del)
                this.writeUInteger(chunk.span);
            else
                this.writeStr(chunk.data);
        }
    }
    cBin(node) {
        const ts = this.ts;
        const writer = this.writer;
        ts(node.id);
        this.writeTL(CRDT_MAJOR_OVERLAY.BIN, node.count);
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            ts(chunk.id);
            const length = chunk.span;
            const deleted = chunk.del;
            writer.b1vu56(~~deleted, length);
            if (deleted)
                continue;
            writer.buf(chunk.data, length);
        }
    }
    cArr(node) {
        const ts = this.ts;
        const writer = this.writer;
        ts(node.id);
        this.writeTL(CRDT_MAJOR_OVERLAY.ARR, node.count);
        const index = this.doc.index;
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            ts(chunk.id);
            const span = chunk.span;
            const deleted = chunk.del;
            writer.b1vu56(~~deleted, span);
            if (deleted)
                continue;
            const nodes = chunk.data;
            for (let i = 0; i < span; i++)
                this.cNode(index.get(nodes[i]));
        }
    }
}
//# sourceMappingURL=Encoder.js.map