"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BinaryParser = void 0;
const assert = __importStar(require("assert"));
const enums_1 = require("../enums");
const buffer_1 = require("buffer/");
/**
 * BinaryParser is used to compute fields and values from a HexString
 */
class BinaryParser {
    /**
     * Initialize bytes to a hex string
     *
     * @param hexBytes a hex string
     */
    constructor(hexBytes) {
        this.bytes = buffer_1.Buffer.from(hexBytes, 'hex');
    }
    /**
     * Peek the first byte of the BinaryParser
     *
     * @returns The first byte of the BinaryParser
     */
    peek() {
        assert.ok(this.bytes.byteLength !== 0);
        return this.bytes[0];
    }
    /**
     * Consume the first n bytes of the BinaryParser
     *
     * @param n the number of bytes to skip
     */
    skip(n) {
        assert.ok(n <= this.bytes.byteLength);
        this.bytes = this.bytes.slice(n);
    }
    /**
     * read the first n bytes from the BinaryParser
     *
     * @param n The number of bytes to read
     * @return The bytes
     */
    read(n) {
        assert.ok(n <= this.bytes.byteLength);
        const slice = this.bytes.slice(0, n);
        this.skip(n);
        return slice;
    }
    /**
     * Read an integer of given size
     *
     * @param n The number of bytes to read
     * @return The number represented by those bytes
     */
    readUIntN(n) {
        assert.ok(0 < n && n <= 4, 'invalid n');
        return this.read(n).reduce((a, b) => (a << 8) | b) >>> 0;
    }
    readUInt8() {
        return this.readUIntN(1);
    }
    readUInt16() {
        return this.readUIntN(2);
    }
    readUInt32() {
        return this.readUIntN(4);
    }
    size() {
        return this.bytes.byteLength;
    }
    end(customEnd) {
        const length = this.bytes.byteLength;
        return length === 0 || (customEnd !== undefined && length <= customEnd);
    }
    /**
     * Reads variable length encoded bytes
     *
     * @return The variable length bytes
     */
    readVariableLength() {
        return this.read(this.readVariableLengthLength());
    }
    /**
     * Reads the length of the variable length encoded bytes
     *
     * @return The length of the variable length encoded bytes
     */
    readVariableLengthLength() {
        const b1 = this.readUInt8();
        if (b1 <= 192) {
            return b1;
        }
        else if (b1 <= 240) {
            const b2 = this.readUInt8();
            return 193 + (b1 - 193) * 256 + b2;
        }
        else if (b1 <= 254) {
            const b2 = this.readUInt8();
            const b3 = this.readUInt8();
            return 12481 + (b1 - 241) * 65536 + b2 * 256 + b3;
        }
        throw new Error('Invalid variable length indicator');
    }
    /**
     * Reads the field ordinal from the BinaryParser
     *
     * @return Field ordinal
     */
    readFieldOrdinal() {
        let type = this.readUInt8();
        let nth = type & 15;
        type >>= 4;
        if (type === 0) {
            type = this.readUInt8();
            if (type === 0 || type < 16) {
                throw new Error('Cannot read FieldOrdinal, type_code out of range');
            }
        }
        if (nth === 0) {
            nth = this.readUInt8();
            if (nth === 0 || nth < 16) {
                throw new Error('Cannot read FieldOrdinal, field_code out of range');
            }
        }
        return (type << 16) | nth;
    }
    /**
     * Read the field from the BinaryParser
     *
     * @return The field represented by the bytes at the head of the BinaryParser
     */
    readField() {
        return enums_1.Field.fromString(this.readFieldOrdinal().toString());
    }
    /**
     * Read a given type from the BinaryParser
     *
     * @param type The type that you want to read from the BinaryParser
     * @return The instance of that type read from the BinaryParser
     */
    readType(type) {
        return type.fromParser(this);
    }
    /**
     * Get the type associated with a given field
     *
     * @param field The field that you wan to get the type of
     * @return The type associated with the given field
     */
    typeForField(field) {
        return field.associatedType;
    }
    /**
     * Read value of the type specified by field from the BinaryParser
     *
     * @param field The field that you want to get the associated value for
     * @return The value associated with the given field
     */
    readFieldValue(field) {
        const type = this.typeForField(field);
        if (!type) {
            throw new Error(`unsupported: (${field.name}, ${field.type.name})`);
        }
        const sizeHint = field.isVariableLengthEncoded
            ? this.readVariableLengthLength()
            : undefined;
        const value = type.fromParser(this, sizeHint);
        if (value === undefined) {
            throw new Error(`fromParser for (${field.name}, ${field.type.name}) -> undefined `);
        }
        return value;
    }
    /**
     * Get the next field and value from the BinaryParser
     *
     * @return The field and value
     */
    readFieldAndValue() {
        const field = this.readField();
        return [field, this.readFieldValue(field)];
    }
}
exports.BinaryParser = BinaryParser;
//# sourceMappingURL=binary-parser.js.map