import * as nodes from '../../../nodes';
import { ClockDecoder } from '../../../../json-crdt-patch/codec/clock/ClockDecoder';
import { Timestamp } from '../../../../json-crdt-patch/clock';
import { Model, UNDEFINED } from '../../../model/Model';
export class Decoder {
    time;
    clockDecoder;
    decode(doc) {
        const [time, root] = doc;
        const isServerTime = typeof time === 'number';
        if (isServerTime) {
            this.time = time;
        }
        else {
            this.clockDecoder = ClockDecoder.fromArr(time);
        }
        const model = isServerTime
            ? Model.withServerClock(void 0, time)
            : Model.create(void 0, this.clockDecoder.clock);
        const val = root ? this.decNode(model, root) : UNDEFINED;
        model.root = new nodes.RootNode(model, val.id);
        return model;
    }
    ts(x) {
        if (typeof x === 'number') {
            return new Timestamp(1 /* SESSION.SERVER */, this.time - x);
        }
        else {
            const [sid, time] = x;
            if (sid < 0) {
                return this.clockDecoder.decodeId(-sid, time);
            }
            else {
                return new Timestamp(sid, time);
            }
        }
    }
    decNode(model, node) {
        switch (node[0]) {
            case 0 /* JsonCrdtDataType.con */:
                return this.decCon(model, node);
            case 1 /* JsonCrdtDataType.val */:
                return this.decVal(model, node);
            case 2 /* JsonCrdtDataType.obj */:
                return this.decObj(model, node);
            case 3 /* JsonCrdtDataType.vec */:
                return this.decVec(model, node);
            case 4 /* JsonCrdtDataType.str */:
                return this.decStr(model, node);
            case 5 /* JsonCrdtDataType.bin */:
                return this.decBin(model, node);
            case 6 /* JsonCrdtDataType.arr */:
                return this.decArr(model, node);
        }
        throw new Error('UNKNOWN_NODE');
    }
    decCon(doc, node) {
        const id = this.ts(node[1]);
        let data = node[2];
        if (node.length > 3) {
            const specialData = node[3];
            if (!specialData)
                data = undefined;
            else
                data = this.ts(specialData);
        }
        const obj = new nodes.ConNode(id, data);
        doc.index.set(id, obj);
        return obj;
    }
    decVal(doc, node) {
        const id = this.ts(node[1]);
        const child = this.decNode(doc, node[2]);
        const obj = new nodes.ValNode(doc, id, child.id);
        doc.index.set(id, obj);
        return obj;
    }
    decObj(model, node) {
        const id = this.ts(node[1]);
        const obj = new nodes.ObjNode(model, id);
        const map = node[2];
        const keys = Object.keys(map);
        const length = keys.length;
        for (let i = 0; i < length; i++) {
            const key = keys[i];
            const val = this.decNode(model, map[key]);
            obj.put(key, val.id);
        }
        model.index.set(id, obj);
        return obj;
    }
    decVec(model, node) {
        const id = this.ts(node[1]);
        const obj = new nodes.VecNode(model, id);
        const map = node[2];
        const elements = obj.elements;
        const length = map.length;
        for (let i = 0; i < length; i++) {
            const item = map[i];
            if (!item)
                elements.push(undefined);
            else {
                const child = this.decNode(model, item);
                elements.push(child.id);
            }
        }
        model.index.set(id, obj);
        return obj;
    }
    decStr(doc, node) {
        const id = this.ts(node[1]);
        const obj = new nodes.StrNode(id);
        const chunks = node[2];
        const size = chunks.length;
        let i = 0;
        obj.ingest(size, () => {
            const chunk = chunks[i++];
            const chunkId = this.ts(chunk[0]);
            const content = chunk[1];
            if (typeof content === 'number')
                return new nodes.StrChunk(chunkId, content, '');
            return new nodes.StrChunk(chunkId, content.length, content);
        });
        doc.index.set(id, obj);
        return obj;
    }
    decBin(doc, node) {
        const id = this.ts(node[1]);
        const obj = new nodes.BinNode(id);
        const chunks = node[2];
        const size = chunks.length;
        let i = 0;
        obj.ingest(size, () => {
            const chunk = chunks[i++];
            const chunkId = this.ts(chunk[0]);
            const content = chunk[1];
            if (typeof content === 'number')
                return new nodes.BinChunk(chunkId, content, undefined);
            return new nodes.BinChunk(chunkId, content.length, content);
        });
        doc.index.set(id, obj);
        return obj;
    }
    decArr(doc, node) {
        const id = this.ts(node[1]);
        const obj = new nodes.ArrNode(doc, id);
        const chunks = node[2];
        const size = chunks.length;
        let i = 0;
        obj.ingest(size, () => {
            const chunk = chunks[i++];
            const chunkId = this.ts(chunk[0]);
            const content = chunk[1];
            if (typeof content === 'number')
                return new nodes.ArrChunk(chunkId, content, undefined);
            const ids = content.map((c) => this.decNode(doc, c).id);
            return new nodes.ArrChunk(chunkId, content.length, ids);
        });
        doc.index.set(id, obj);
        return obj;
    }
}
//# sourceMappingURL=Decoder.js.map