import { Timestamp } from '../../../../json-crdt-patch/clock';
import { ClockTable } from '../../../../json-crdt-patch/codec/clock/ClockTable';
import { CrdtWriter } from '../../../../json-crdt-patch/util/binary/CrdtWriter';
import { CborEncoder } from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder';
import * as nodes from '../../../nodes';
import { CRDT_MAJOR_OVERLAY } from '../../structural/binary/constants';
export class Encoder {
    enc;
    clockTable;
    constructor(writer) {
        this.enc = new CborEncoder(writer || new CrdtWriter());
    }
    encode(doc, clockTable = ClockTable.from(doc.clock)) {
        this.clockTable = clockTable;
        const writer = this.enc.writer;
        writer.reset();
        clockTable.write(writer);
        const encodedClock = writer.flush();
        const rootValueId = doc.root.val;
        const result = {
            c: encodedClock,
        };
        if (rootValueId.sid !== 0) {
            writer.reset();
            this.ts(rootValueId);
            result.r = writer.flush();
        }
        doc.index.forEach(({ v: node }) => this.onNode(result, node));
        return result;
    }
    onNode = (result, node) => {
        const id = node.id;
        const sid = id.sid;
        const time = id.time;
        const sidIndex = this.clockTable.getBySid(sid).index;
        const field = (sidIndex.toString(36) + '_' + time.toString(36));
        result[field] = this.encodeNode(node);
    };
    encodeNode(node) {
        if (node instanceof nodes.ConNode)
            return this.encodeCon(node);
        else if (node instanceof nodes.ValNode)
            return this.encodeVal(node);
        else if (node instanceof nodes.ObjNode)
            return this.encodeObj(node);
        else if (node instanceof nodes.VecNode)
            return this.encodeVec(node);
        else if (node instanceof nodes.StrNode)
            return this.encodeStr(node);
        else if (node instanceof nodes.BinNode)
            return this.encodeBin(node);
        else if (node instanceof nodes.ArrNode)
            return this.encodeArr(node);
        throw new Error('UNKNOWN_NODE');
    }
    ts(id) {
        const index = this.clockTable.getBySid(id.sid).index;
        this.enc.writer.id(index, id.time);
    }
    writeTL(majorOverlay, length) {
        const writer = this.enc.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);
    }
    encodeCon(node) {
        const encoder = this.enc;
        const writer = encoder.writer;
        const val = node.val;
        writer.reset();
        if (val instanceof Timestamp) {
            this.writeTL(CRDT_MAJOR_OVERLAY.CON, 1);
            this.ts(val);
        }
        else {
            this.writeTL(CRDT_MAJOR_OVERLAY.CON, 0);
            encoder.writeAny(val);
        }
        return writer.flush();
    }
    encodeVal(node) {
        const writer = this.enc.writer;
        const child = node.node();
        writer.reset();
        this.writeTL(CRDT_MAJOR_OVERLAY.VAL, 0);
        this.ts(child.id);
        return writer.flush();
    }
    encodeObj(node) {
        const encoder = this.enc;
        const writer = encoder.writer;
        writer.reset();
        const keys = node.keys;
        this.writeTL(CRDT_MAJOR_OVERLAY.OBJ, keys.size);
        keys.forEach(this.onObjKey);
        return writer.flush();
    }
    onObjKey = (value, key) => {
        this.enc.writeStr(key);
        this.ts(value);
    };
    encodeVec(node) {
        const writer = this.enc.writer;
        writer.reset();
        const length = node.elements.length;
        this.writeTL(CRDT_MAJOR_OVERLAY.VEC, length);
        for (let i = 0; i < length; i++) {
            const childId = node.val(i);
            if (!childId)
                writer.u8(0);
            else {
                writer.u8(1);
                this.ts(childId);
            }
        }
        return writer.flush();
    }
    encodeStr(node) {
        const encoder = this.enc;
        const writer = encoder.writer;
        writer.reset();
        this.writeTL(CRDT_MAJOR_OVERLAY.STR, node.count);
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            this.ts(chunk.id);
            if (chunk.del)
                encoder.writeUInteger(chunk.span);
            else
                encoder.writeStr(chunk.data);
        }
        return writer.flush();
    }
    encodeBin(node) {
        const encoder = this.enc;
        const writer = encoder.writer;
        writer.reset();
        this.writeTL(CRDT_MAJOR_OVERLAY.BIN, node.count);
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            const length = chunk.span;
            const deleted = chunk.del;
            this.ts(chunk.id);
            writer.b1vu56(~~deleted, length);
            if (deleted)
                continue;
            writer.buf(chunk.data, length);
        }
        return writer.flush();
    }
    encodeArr(node) {
        const encoder = this.enc;
        const writer = encoder.writer;
        writer.reset();
        this.writeTL(CRDT_MAJOR_OVERLAY.ARR, node.count);
        for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
            const length = chunk.span;
            const deleted = chunk.del;
            this.ts(chunk.id);
            writer.b1vu56(~~deleted, length);
            if (deleted)
                continue;
            const data = chunk.data;
            for (let i = 0; i < length; i++)
                this.ts(data[i]);
        }
        return writer.flush();
    }
}
//# sourceMappingURL=Encoder.js.map