"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Decoder = void 0;
const op_1 = require("../../op");
const MsgPackDecoderFast_1 = require("@jsonjoy.com/json-pack/lib/msgpack/MsgPackDecoderFast");
const constants_1 = require("../../constants");
const util_1 = require("../../util");
class Decoder extends MsgPackDecoderFast_1.MsgPackDecoderFast {
    constructor(options) {
        super();
        this.options = options;
    }
    decode(uint8) {
        this.reader.reset(uint8);
        return this.decodePatch();
    }
    decodePatch() {
        const len = this.decodeArrayHeader();
        const ops = [];
        for (let i = 0; i < len; i++)
            ops.push(this.decodeOp(undefined));
        return ops;
    }
    decodeOp(parent) {
        const length = this.decodeArrayHeader();
        const opcode = this.reader.u8();
        switch (opcode) {
            case constants_1.OPCODE.add: {
                const path = this.decodeArray();
                const value = this.val();
                return new op_1.OpAdd(path, value);
            }
            case constants_1.OPCODE.and: {
                const path = this.decodePath(parent);
                const length = this.decodeArrayHeader();
                const ops = [];
                const op = new op_1.OpAnd(path, ops);
                for (let i = 0; i < length; i++)
                    ops.push(this.decodeOp(op));
                return op;
            }
            case constants_1.OPCODE.contains: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                const ignoreCase = length > 3;
                return new op_1.OpContains(path, value, ignoreCase);
            }
            case constants_1.OPCODE.copy: {
                const path = this.decodeArray();
                const from = this.decodeArray();
                return new op_1.OpCopy(path, from);
            }
            case constants_1.OPCODE.defined: {
                const path = this.decodePath(parent);
                return new op_1.OpDefined(path);
            }
            case constants_1.OPCODE.ends: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                const ignoreCase = length > 3;
                return new op_1.OpEnds(path, value, ignoreCase);
            }
            case constants_1.OPCODE.extend: {
                const path = this.decodeArray();
                const props = this.decodeObject();
                const deleteNull = length > 3;
                return new op_1.OpExtend(path, props, deleteNull);
            }
            case constants_1.OPCODE.flip: {
                const path = this.decodeArray();
                return new op_1.OpFlip(path);
            }
            case constants_1.OPCODE.in: {
                const path = this.decodePath(parent);
                const value = this.decodeArray();
                return new op_1.OpIn(path, value);
            }
            case constants_1.OPCODE.inc: {
                const path = this.decodePath(parent);
                const inc = this.val();
                return new op_1.OpInc(path, inc);
            }
            case constants_1.OPCODE.less: {
                const path = this.decodePath(parent);
                const value = this.val();
                return new op_1.OpLess(path, value);
            }
            case constants_1.OPCODE.matches: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                const ignoreCase = length > 3;
                return new op_1.OpMatches(path, value, ignoreCase, this.options.createMatcher || util_1.createMatcherDefault);
            }
            case constants_1.OPCODE.merge: {
                const hasProps = length > 3;
                const path = this.decodeArray();
                const pos = this.val();
                const props = hasProps ? this.decodeObject() : null;
                return new op_1.OpMerge(path, pos, props);
            }
            case constants_1.OPCODE.more: {
                const path = this.decodePath(parent);
                const value = this.val();
                return new op_1.OpMore(path, value);
            }
            case constants_1.OPCODE.move: {
                const path = this.decodeArray();
                const from = this.decodeArray();
                return new op_1.OpMove(path, from);
            }
            case constants_1.OPCODE.not: {
                const path = this.decodePath(parent);
                const length = this.decodeArrayHeader();
                const ops = [];
                const op = new op_1.OpNot(path, ops);
                for (let i = 0; i < length; i++)
                    ops.push(this.decodeOp(op));
                return op;
            }
            case constants_1.OPCODE.or: {
                const path = this.decodePath(parent);
                const length = this.decodeArrayHeader();
                const ops = [];
                const op = new op_1.OpOr(path, ops);
                for (let i = 0; i < length; i++)
                    ops.push(this.decodeOp(op));
                return op;
            }
            case constants_1.OPCODE.remove: {
                const path = this.decodeArray();
                const hasOldValue = length > 2;
                const oldValue = hasOldValue ? this.val() : undefined;
                return new op_1.OpRemove(path, oldValue);
            }
            case constants_1.OPCODE.replace: {
                const path = this.decodeArray();
                const value = this.val();
                const hasOldValue = length > 3;
                const oldValue = hasOldValue ? this.val() : undefined;
                return new op_1.OpReplace(path, value, oldValue);
            }
            case constants_1.OPCODE.split: {
                const path = this.decodeArray();
                const pos = this.val();
                const hasProps = length > 3;
                const props = hasProps ? this.decodeObject() : null;
                return new op_1.OpSplit(path, pos, props);
            }
            case constants_1.OPCODE.starts: {
                const ignoreCase = length > 3;
                const path = this.decodePath(parent);
                const value = this.decodeString();
                return new op_1.OpStarts(path, value, ignoreCase);
            }
            case constants_1.OPCODE.str_del: {
                const hasStr = length < 5;
                const path = this.decodeArray();
                const pos = this.val();
                if (hasStr) {
                    const str = this.decodeString();
                    return new op_1.OpStrDel(path, pos, str, undefined);
                }
                else {
                    this.reader.u8();
                    const len = this.val();
                    return new op_1.OpStrDel(path, pos, undefined, len);
                }
            }
            case constants_1.OPCODE.str_ins: {
                const path = this.decodeArray();
                const pos = this.val();
                const str = this.decodeString();
                return new op_1.OpStrIns(path, pos, str);
            }
            case constants_1.OPCODE.test: {
                const not = length > 3;
                const path = this.decodePath(parent);
                const value = this.val();
                return new op_1.OpTest(path, value, not);
            }
            case constants_1.OPCODE.test_string: {
                const not = length > 4;
                const path = this.decodePath(parent);
                const pos = this.val();
                const str = this.decodeString();
                return new op_1.OpTestString(path, pos, str, not);
            }
            case constants_1.OPCODE.test_string_len: {
                const not = length > 3;
                const path = this.decodePath(parent);
                const len = this.val();
                return new op_1.OpTestStringLen(path, len, not);
            }
            case constants_1.OPCODE.test_type: {
                const path = this.decodePath(parent);
                const type = this.decodeArray();
                return new op_1.OpTestType(path, type);
            }
            case constants_1.OPCODE.type: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                return new op_1.OpType(path, value);
            }
            case constants_1.OPCODE.undefined: {
                const path = this.decodePath(parent);
                return new op_1.OpUndefined(path);
            }
        }
        throw new Error('OP_UNKNOWN');
    }
    decodePath(parent) {
        const path = this.decodeArray();
        if (!parent)
            return path;
        return [...parent.path, ...path];
    }
    decodeObject() {
        const reader = this.reader;
        const byte = reader.u8();
        if (byte <= 0xbf)
            return this.obj(byte & 0b1111);
        else if (byte === 0xde)
            return this.obj(reader.u16());
        /* 0xdf */ else
            return this.obj(reader.u32());
    }
    decodeArray() {
        const reader = this.reader;
        const byte = reader.u8();
        if (byte < 0b10011111)
            return this.arr(byte & 0b1111);
        else if (byte === 0xdc)
            return this.arr(reader.u16());
        else
            return this.arr(reader.u32());
    }
    decodeArrayHeader() {
        const reader = this.reader;
        const byte = reader.u8();
        if (byte < 0b10011111)
            return byte & 0b1111;
        else if (byte === 0xdc)
            return reader.u16();
        else
            return reader.u32();
    }
    decodeString() {
        const reader = this.reader;
        const byte = reader.u8();
        if (byte <= 0xbf)
            return reader.utf8(byte & 0b11111);
        else if (byte === 0xd9)
            return reader.utf8(reader.u8());
        else if (byte === 0xda)
            return reader.utf8(reader.u16());
        /* 0xDB */ else
            return reader.utf8(reader.u32());
    }
}
exports.Decoder = Decoder;
//# sourceMappingURL=Decoder.js.map