import { OpAdd, OpAnd, OpContains, OpCopy, OpDefined, OpEnds, OpExtend, OpFlip, OpIn, OpInc, OpLess, OpMatches, OpMerge, OpMore, OpMove, OpNot, OpOr, OpRemove, OpReplace, OpSplit, OpStarts, OpStrDel, OpStrIns, OpTest, OpTestString, OpTestStringLen, OpTestType, OpType, OpUndefined, } from '../../op';
import { MsgPackDecoderFast } from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackDecoderFast';
import { OPCODE } from '../../constants';
import { createMatcherDefault } from '../../util';
export class Decoder extends MsgPackDecoderFast {
    options;
    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 OPCODE.add: {
                const path = this.decodeArray();
                const value = this.val();
                return new OpAdd(path, value);
            }
            case OPCODE.and: {
                const path = this.decodePath(parent);
                const length = this.decodeArrayHeader();
                const ops = [];
                const op = new OpAnd(path, ops);
                for (let i = 0; i < length; i++)
                    ops.push(this.decodeOp(op));
                return op;
            }
            case OPCODE.contains: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                const ignoreCase = length > 3;
                return new OpContains(path, value, ignoreCase);
            }
            case OPCODE.copy: {
                const path = this.decodeArray();
                const from = this.decodeArray();
                return new OpCopy(path, from);
            }
            case OPCODE.defined: {
                const path = this.decodePath(parent);
                return new OpDefined(path);
            }
            case OPCODE.ends: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                const ignoreCase = length > 3;
                return new OpEnds(path, value, ignoreCase);
            }
            case OPCODE.extend: {
                const path = this.decodeArray();
                const props = this.decodeObject();
                const deleteNull = length > 3;
                return new OpExtend(path, props, deleteNull);
            }
            case OPCODE.flip: {
                const path = this.decodeArray();
                return new OpFlip(path);
            }
            case OPCODE.in: {
                const path = this.decodePath(parent);
                const value = this.decodeArray();
                return new OpIn(path, value);
            }
            case OPCODE.inc: {
                const path = this.decodePath(parent);
                const inc = this.val();
                return new OpInc(path, inc);
            }
            case OPCODE.less: {
                const path = this.decodePath(parent);
                const value = this.val();
                return new OpLess(path, value);
            }
            case OPCODE.matches: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                const ignoreCase = length > 3;
                return new OpMatches(path, value, ignoreCase, this.options.createMatcher || createMatcherDefault);
            }
            case OPCODE.merge: {
                const hasProps = length > 3;
                const path = this.decodeArray();
                const pos = this.val();
                const props = hasProps ? this.decodeObject() : null;
                return new OpMerge(path, pos, props);
            }
            case OPCODE.more: {
                const path = this.decodePath(parent);
                const value = this.val();
                return new OpMore(path, value);
            }
            case OPCODE.move: {
                const path = this.decodeArray();
                const from = this.decodeArray();
                return new OpMove(path, from);
            }
            case OPCODE.not: {
                const path = this.decodePath(parent);
                const length = this.decodeArrayHeader();
                const ops = [];
                const op = new OpNot(path, ops);
                for (let i = 0; i < length; i++)
                    ops.push(this.decodeOp(op));
                return op;
            }
            case OPCODE.or: {
                const path = this.decodePath(parent);
                const length = this.decodeArrayHeader();
                const ops = [];
                const op = new OpOr(path, ops);
                for (let i = 0; i < length; i++)
                    ops.push(this.decodeOp(op));
                return op;
            }
            case OPCODE.remove: {
                const path = this.decodeArray();
                const hasOldValue = length > 2;
                const oldValue = hasOldValue ? this.val() : undefined;
                return new OpRemove(path, oldValue);
            }
            case OPCODE.replace: {
                const path = this.decodeArray();
                const value = this.val();
                const hasOldValue = length > 3;
                const oldValue = hasOldValue ? this.val() : undefined;
                return new OpReplace(path, value, oldValue);
            }
            case OPCODE.split: {
                const path = this.decodeArray();
                const pos = this.val();
                const hasProps = length > 3;
                const props = hasProps ? this.decodeObject() : null;
                return new OpSplit(path, pos, props);
            }
            case OPCODE.starts: {
                const ignoreCase = length > 3;
                const path = this.decodePath(parent);
                const value = this.decodeString();
                return new OpStarts(path, value, ignoreCase);
            }
            case OPCODE.str_del: {
                const hasStr = length < 5;
                const path = this.decodeArray();
                const pos = this.val();
                if (hasStr) {
                    const str = this.decodeString();
                    return new OpStrDel(path, pos, str, undefined);
                }
                else {
                    this.reader.u8();
                    const len = this.val();
                    return new OpStrDel(path, pos, undefined, len);
                }
            }
            case OPCODE.str_ins: {
                const path = this.decodeArray();
                const pos = this.val();
                const str = this.decodeString();
                return new OpStrIns(path, pos, str);
            }
            case OPCODE.test: {
                const not = length > 3;
                const path = this.decodePath(parent);
                const value = this.val();
                return new OpTest(path, value, not);
            }
            case OPCODE.test_string: {
                const not = length > 4;
                const path = this.decodePath(parent);
                const pos = this.val();
                const str = this.decodeString();
                return new OpTestString(path, pos, str, not);
            }
            case OPCODE.test_string_len: {
                const not = length > 3;
                const path = this.decodePath(parent);
                const len = this.val();
                return new OpTestStringLen(path, len, not);
            }
            case OPCODE.test_type: {
                const path = this.decodePath(parent);
                const type = this.decodeArray();
                return new OpTestType(path, type);
            }
            case OPCODE.type: {
                const path = this.decodePath(parent);
                const value = this.decodeString();
                return new OpType(path, value);
            }
            case OPCODE.undefined: {
                const path = this.decodePath(parent);
                return new 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());
    }
}
//# sourceMappingURL=Decoder.js.map