"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReflectionBinaryReader = void 0;
const binary_format_contract_1 = require("./binary-format-contract");
const reflection_info_1 = require("./reflection-info");
const reflection_long_convert_1 = require("./reflection-long-convert");
const reflection_scalar_default_1 = require("./reflection-scalar-default");
/**
 * Reads proto3 messages in binary format using reflection information.
 *
 * https://developers.google.com/protocol-buffers/docs/encoding
 */
class ReflectionBinaryReader {
    constructor(info) {
        this.info = info;
    }
    prepare() {
        var _a;
        if (!this.fieldNoToField) {
            const fieldsInput = (_a = this.info.fields) !== null && _a !== void 0 ? _a : [];
            this.fieldNoToField = new Map(fieldsInput.map(field => [field.no, field]));
        }
    }
    /**
     * Reads a message from binary format into the target message.
     *
     * Repeated fields are appended. Map entries are added, overwriting
     * existing keys.
     *
     * If a message field is already present, it will be merged with the
     * new data.
     */
    read(reader, message, options, length) {
        this.prepare();
        const end = length === undefined ? reader.len : reader.pos + length;
        while (reader.pos < end) {
            // read the tag and find the field
            const [fieldNo, wireType] = reader.tag(), field = this.fieldNoToField.get(fieldNo);
            if (!field) {
                let u = options.readUnknownField;
                if (u == "throw")
                    throw new Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.info.typeName}`);
                let d = reader.skip(wireType);
                if (u !== false)
                    (u === true ? binary_format_contract_1.UnknownFieldHandler.onRead : u)(this.info.typeName, message, fieldNo, wireType, d);
                continue;
            }
            // target object for the field we are reading
            let target = message, repeated = field.repeat, localName = field.localName;
            // if field is member of oneof ADT, use ADT as target
            if (field.oneof) {
                target = target[field.oneof];
                // if other oneof member selected, set new ADT
                if (target.oneofKind !== localName)
                    target = message[field.oneof] = {
                        oneofKind: localName
                    };
            }
            // we have handled oneof above, we just have read the value into `target[localName]`
            switch (field.kind) {
                case "scalar":
                case "enum":
                    let T = field.kind == "enum" ? reflection_info_1.ScalarType.INT32 : field.T;
                    let L = field.kind == "scalar" ? field.L : undefined;
                    if (repeated) {
                        let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values
                        if (wireType == binary_format_contract_1.WireType.LengthDelimited && T != reflection_info_1.ScalarType.STRING && T != reflection_info_1.ScalarType.BYTES) {
                            let e = reader.uint32() + reader.pos;
                            while (reader.pos < e)
                                arr.push(this.scalar(reader, T, L));
                        }
                        else
                            arr.push(this.scalar(reader, T, L));
                    }
                    else
                        target[localName] = this.scalar(reader, T, L);
                    break;
                case "message":
                    if (repeated) {
                        let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values
                        let msg = field.T().internalBinaryRead(reader, reader.uint32(), options);
                        arr.push(msg);
                    }
                    else
                        target[localName] = field.T().internalBinaryRead(reader, reader.uint32(), options, target[localName]);
                    break;
                case "map":
                    let [mapKey, mapVal] = this.mapEntry(field, reader, options);
                    // safe to assume presence of map object, oneof cannot contain repeated values
                    target[localName][mapKey] = mapVal;
                    break;
            }
        }
    }
    /**
     * Read a map field, expecting key field = 1, value field = 2
     */
    mapEntry(field, reader, options) {
        let length = reader.uint32();
        let end = reader.pos + length;
        let key = undefined; // javascript only allows number or string for object properties
        let val = undefined;
        while (reader.pos < end) {
            let [fieldNo, wireType] = reader.tag();
            switch (fieldNo) {
                case 1:
                    if (field.K == reflection_info_1.ScalarType.BOOL)
                        key = reader.bool().toString();
                    else
                        // long types are read as string, number types are okay as number
                        key = this.scalar(reader, field.K, reflection_info_1.LongType.STRING);
                    break;
                case 2:
                    switch (field.V.kind) {
                        case "scalar":
                            val = this.scalar(reader, field.V.T, field.V.L);
                            break;
                        case "enum":
                            val = reader.int32();
                            break;
                        case "message":
                            val = field.V.T().internalBinaryRead(reader, reader.uint32(), options);
                            break;
                    }
                    break;
                default:
                    throw new Error(`Unknown field ${fieldNo} (wire type ${wireType}) in map entry for ${this.info.typeName}#${field.name}`);
            }
        }
        if (key === undefined) {
            let keyRaw = reflection_scalar_default_1.reflectionScalarDefault(field.K);
            key = field.K == reflection_info_1.ScalarType.BOOL ? keyRaw.toString() : keyRaw;
        }
        if (val === undefined)
            switch (field.V.kind) {
                case "scalar":
                    val = reflection_scalar_default_1.reflectionScalarDefault(field.V.T, field.V.L);
                    break;
                case "enum":
                    val = 0;
                    break;
                case "message":
                    val = field.V.T().create();
                    break;
            }
        return [key, val];
    }
    scalar(reader, type, longType) {
        switch (type) {
            case reflection_info_1.ScalarType.INT32:
                return reader.int32();
            case reflection_info_1.ScalarType.STRING:
                return reader.string();
            case reflection_info_1.ScalarType.BOOL:
                return reader.bool();
            case reflection_info_1.ScalarType.DOUBLE:
                return reader.double();
            case reflection_info_1.ScalarType.FLOAT:
                return reader.float();
            case reflection_info_1.ScalarType.INT64:
                return reflection_long_convert_1.reflectionLongConvert(reader.int64(), longType);
            case reflection_info_1.ScalarType.UINT64:
                return reflection_long_convert_1.reflectionLongConvert(reader.uint64(), longType);
            case reflection_info_1.ScalarType.FIXED64:
                return reflection_long_convert_1.reflectionLongConvert(reader.fixed64(), longType);
            case reflection_info_1.ScalarType.FIXED32:
                return reader.fixed32();
            case reflection_info_1.ScalarType.BYTES:
                return reader.bytes();
            case reflection_info_1.ScalarType.UINT32:
                return reader.uint32();
            case reflection_info_1.ScalarType.SFIXED32:
                return reader.sfixed32();
            case reflection_info_1.ScalarType.SFIXED64:
                return reflection_long_convert_1.reflectionLongConvert(reader.sfixed64(), longType);
            case reflection_info_1.ScalarType.SINT32:
                return reader.sint32();
            case reflection_info_1.ScalarType.SINT64:
                return reflection_long_convert_1.reflectionLongConvert(reader.sint64(), longType);
        }
    }
}
exports.ReflectionBinaryReader = ReflectionBinaryReader;
