"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Unpacker = exports.Packer = void 0;
/**
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var channel_1 = require("../channel");
var lang_1 = require("../lang");
var structure_1 = require("./structure");
var neo4j_driver_core_1 = require("neo4j-driver-core");
var PROTOCOL_ERROR = neo4j_driver_core_1.error.PROTOCOL_ERROR;
var TINY_STRING = 0x80;
var TINY_LIST = 0x90;
var TINY_MAP = 0xa0;
var TINY_STRUCT = 0xb0;
var NULL = 0xc0;
var FLOAT_64 = 0xc1;
var FALSE = 0xc2;
var TRUE = 0xc3;
var INT_8 = 0xc8;
var INT_16 = 0xc9;
var INT_32 = 0xca;
var INT_64 = 0xcb;
var STRING_8 = 0xd0;
var STRING_16 = 0xd1;
var STRING_32 = 0xd2;
var LIST_8 = 0xd4;
var LIST_16 = 0xd5;
var LIST_32 = 0xd6;
var BYTES_8 = 0xcc;
var BYTES_16 = 0xcd;
var BYTES_32 = 0xce;
var MAP_8 = 0xd8;
var MAP_16 = 0xd9;
var MAP_32 = 0xda;
var STRUCT_8 = 0xdc;
var STRUCT_16 = 0xdd;
/**
 * Class to pack
 * @access private
 */
var Packer = /** @class */ (function () {
    /**
     * @constructor
     * @param {Chunker} channel the chunker backed by a network channel.
     */
    function Packer(channel) {
        this._ch = channel;
        this._byteArraysSupported = true;
    }
    /**
     * Creates a packable function out of the provided value
     * @param x the value to pack
     * @returns Function
     */
    Packer.prototype.packable = function (x, dehydrateStruct) {
        var _this = this;
        if (dehydrateStruct === void 0) { dehydrateStruct = lang_1.functional.identity; }
        try {
            x = dehydrateStruct(x);
        }
        catch (ex) {
            return function () { throw ex; };
        }
        if (x === null) {
            return function () { return _this._ch.writeUInt8(NULL); };
        }
        else if (x === true) {
            return function () { return _this._ch.writeUInt8(TRUE); };
        }
        else if (x === false) {
            return function () { return _this._ch.writeUInt8(FALSE); };
        }
        else if (typeof x === 'number') {
            return function () { return _this.packFloat(x); };
        }
        else if (typeof x === 'string') {
            return function () { return _this.packString(x); };
        }
        else if (typeof x === 'bigint') {
            return function () { return _this.packInteger((0, neo4j_driver_core_1.int)(x)); };
        }
        else if ((0, neo4j_driver_core_1.isInt)(x)) {
            return function () { return _this.packInteger(x); };
        }
        else if (x instanceof Int8Array) {
            return function () { return _this.packBytes(x); };
        }
        else if (x instanceof Array) {
            return function () {
                _this.packListHeader(x.length);
                for (var i = 0; i < x.length; i++) {
                    _this.packable(x[i] === undefined ? null : x[i], dehydrateStruct)();
                }
            };
        }
        else if (isIterable(x)) {
            return this.packableIterable(x, dehydrateStruct);
        }
        else if (x instanceof structure_1.Structure) {
            var packableFields_1 = [];
            for (var i = 0; i < x.fields.length; i++) {
                packableFields_1[i] = this.packable(x.fields[i], dehydrateStruct);
            }
            return function () { return _this.packStruct(x.signature, packableFields_1); };
        }
        else if (typeof x === 'object') {
            return function () {
                var keys = Object.keys(x);
                var count = 0;
                for (var i = 0; i < keys.length; i++) {
                    if (x[keys[i]] !== undefined) {
                        count++;
                    }
                }
                _this.packMapHeader(count);
                for (var i = 0; i < keys.length; i++) {
                    var key = keys[i];
                    if (x[key] !== undefined) {
                        _this.packString(key);
                        _this.packable(x[key], dehydrateStruct)();
                    }
                }
            };
        }
        else {
            return this._nonPackableValue("Unable to pack the given value: ".concat(x));
        }
    };
    Packer.prototype.packableIterable = function (iterable, dehydrateStruct) {
        try {
            var array = Array.from(iterable);
            return this.packable(array, dehydrateStruct);
        }
        catch (e) {
            // handle errors from iterable to array conversion
            throw (0, neo4j_driver_core_1.newError)("Cannot pack given iterable, ".concat(e.message, ": ").concat(iterable));
        }
    };
    /**
     * Packs a struct
     * @param signature the signature of the struct
     * @param packableFields the fields of the struct, make sure you call `packable on all fields`
     */
    Packer.prototype.packStruct = function (signature, packableFields) {
        packableFields = packableFields || [];
        this.packStructHeader(packableFields.length, signature);
        for (var i = 0; i < packableFields.length; i++) {
            packableFields[i]();
        }
    };
    Packer.prototype.packInteger = function (x) {
        var high = x.high;
        var low = x.low;
        if (x.greaterThanOrEqual(-0x10) && x.lessThan(0x80)) {
            this._ch.writeInt8(low);
        }
        else if (x.greaterThanOrEqual(-0x80) && x.lessThan(-0x10)) {
            this._ch.writeUInt8(INT_8);
            this._ch.writeInt8(low);
        }
        else if (x.greaterThanOrEqual(-0x8000) && x.lessThan(0x8000)) {
            this._ch.writeUInt8(INT_16);
            this._ch.writeInt16(low);
        }
        else if (x.greaterThanOrEqual(-0x80000000) && x.lessThan(0x80000000)) {
            this._ch.writeUInt8(INT_32);
            this._ch.writeInt32(low);
        }
        else {
            this._ch.writeUInt8(INT_64);
            this._ch.writeInt32(high);
            this._ch.writeInt32(low);
        }
    };
    Packer.prototype.packFloat = function (x) {
        this._ch.writeUInt8(FLOAT_64);
        this._ch.writeFloat64(x);
    };
    Packer.prototype.packString = function (x) {
        var bytes = channel_1.utf8.encode(x);
        var size = bytes.length;
        if (size < 0x10) {
            this._ch.writeUInt8(TINY_STRING | size);
            this._ch.writeBytes(bytes);
        }
        else if (size < 0x100) {
            this._ch.writeUInt8(STRING_8);
            this._ch.writeUInt8(size);
            this._ch.writeBytes(bytes);
        }
        else if (size < 0x10000) {
            this._ch.writeUInt8(STRING_16);
            this._ch.writeUInt8((size / 256) >> 0);
            this._ch.writeUInt8(size % 256);
            this._ch.writeBytes(bytes);
        }
        else if (size < 0x100000000) {
            this._ch.writeUInt8(STRING_32);
            this._ch.writeUInt8(((size / 16777216) >> 0) % 256);
            this._ch.writeUInt8(((size / 65536) >> 0) % 256);
            this._ch.writeUInt8(((size / 256) >> 0) % 256);
            this._ch.writeUInt8(size % 256);
            this._ch.writeBytes(bytes);
        }
        else {
            throw (0, neo4j_driver_core_1.newError)('UTF-8 strings of size ' + size + ' are not supported');
        }
    };
    Packer.prototype.packListHeader = function (size) {
        if (size < 0x10) {
            this._ch.writeUInt8(TINY_LIST | size);
        }
        else if (size < 0x100) {
            this._ch.writeUInt8(LIST_8);
            this._ch.writeUInt8(size);
        }
        else if (size < 0x10000) {
            this._ch.writeUInt8(LIST_16);
            this._ch.writeUInt8(((size / 256) >> 0) % 256);
            this._ch.writeUInt8(size % 256);
        }
        else if (size < 0x100000000) {
            this._ch.writeUInt8(LIST_32);
            this._ch.writeUInt8(((size / 16777216) >> 0) % 256);
            this._ch.writeUInt8(((size / 65536) >> 0) % 256);
            this._ch.writeUInt8(((size / 256) >> 0) % 256);
            this._ch.writeUInt8(size % 256);
        }
        else {
            throw (0, neo4j_driver_core_1.newError)('Lists of size ' + size + ' are not supported');
        }
    };
    Packer.prototype.packBytes = function (array) {
        if (this._byteArraysSupported) {
            this.packBytesHeader(array.length);
            for (var i = 0; i < array.length; i++) {
                this._ch.writeInt8(array[i]);
            }
        }
        else {
            throw (0, neo4j_driver_core_1.newError)('Byte arrays are not supported by the database this driver is connected to');
        }
    };
    Packer.prototype.packBytesHeader = function (size) {
        if (size < 0x100) {
            this._ch.writeUInt8(BYTES_8);
            this._ch.writeUInt8(size);
        }
        else if (size < 0x10000) {
            this._ch.writeUInt8(BYTES_16);
            this._ch.writeUInt8(((size / 256) >> 0) % 256);
            this._ch.writeUInt8(size % 256);
        }
        else if (size < 0x100000000) {
            this._ch.writeUInt8(BYTES_32);
            this._ch.writeUInt8(((size / 16777216) >> 0) % 256);
            this._ch.writeUInt8(((size / 65536) >> 0) % 256);
            this._ch.writeUInt8(((size / 256) >> 0) % 256);
            this._ch.writeUInt8(size % 256);
        }
        else {
            throw (0, neo4j_driver_core_1.newError)('Byte arrays of size ' + size + ' are not supported');
        }
    };
    Packer.prototype.packMapHeader = function (size) {
        if (size < 0x10) {
            this._ch.writeUInt8(TINY_MAP | size);
        }
        else if (size < 0x100) {
            this._ch.writeUInt8(MAP_8);
            this._ch.writeUInt8(size);
        }
        else if (size < 0x10000) {
            this._ch.writeUInt8(MAP_16);
            this._ch.writeUInt8((size / 256) >> 0);
            this._ch.writeUInt8(size % 256);
        }
        else if (size < 0x100000000) {
            this._ch.writeUInt8(MAP_32);
            this._ch.writeUInt8(((size / 16777216) >> 0) % 256);
            this._ch.writeUInt8(((size / 65536) >> 0) % 256);
            this._ch.writeUInt8(((size / 256) >> 0) % 256);
            this._ch.writeUInt8(size % 256);
        }
        else {
            throw (0, neo4j_driver_core_1.newError)('Maps of size ' + size + ' are not supported');
        }
    };
    Packer.prototype.packStructHeader = function (size, signature) {
        if (size < 0x10) {
            this._ch.writeUInt8(TINY_STRUCT | size);
            this._ch.writeUInt8(signature);
        }
        else if (size < 0x100) {
            this._ch.writeUInt8(STRUCT_8);
            this._ch.writeUInt8(size);
            this._ch.writeUInt8(signature);
        }
        else if (size < 0x10000) {
            this._ch.writeUInt8(STRUCT_16);
            this._ch.writeUInt8((size / 256) >> 0);
            this._ch.writeUInt8(size % 256);
        }
        else {
            throw (0, neo4j_driver_core_1.newError)('Structures of size ' + size + ' are not supported');
        }
    };
    Packer.prototype.disableByteArrays = function () {
        this._byteArraysSupported = false;
    };
    Packer.prototype._nonPackableValue = function (message) {
        return function () {
            throw (0, neo4j_driver_core_1.newError)(message, PROTOCOL_ERROR);
        };
    };
    return Packer;
}());
exports.Packer = Packer;
/**
 * Class to unpack
 * @access private
 */
var Unpacker = /** @class */ (function () {
    /**
     * @constructor
     * @param {boolean} disableLosslessIntegers if this unpacker should convert all received integers to native JS numbers.
     * @param {boolean} useBigInt if this unpacker should convert all received integers to Bigint
     */
    function Unpacker(disableLosslessIntegers, useBigInt) {
        if (disableLosslessIntegers === void 0) { disableLosslessIntegers = false; }
        if (useBigInt === void 0) { useBigInt = false; }
        this._disableLosslessIntegers = disableLosslessIntegers;
        this._useBigInt = useBigInt;
    }
    Unpacker.prototype.unpack = function (buffer, hydrateStructure) {
        if (hydrateStructure === void 0) { hydrateStructure = lang_1.functional.identity; }
        var marker = buffer.readUInt8();
        var markerHigh = marker & 0xf0;
        var markerLow = marker & 0x0f;
        if (marker === NULL) {
            return null;
        }
        var boolean = this._unpackBoolean(marker);
        if (boolean !== null) {
            return boolean;
        }
        var numberOrInteger = this._unpackNumberOrInteger(marker, buffer);
        if (numberOrInteger !== null) {
            if ((0, neo4j_driver_core_1.isInt)(numberOrInteger)) {
                if (this._useBigInt) {
                    return numberOrInteger.toBigInt();
                }
                else if (this._disableLosslessIntegers) {
                    return numberOrInteger.toNumberOrInfinity();
                }
            }
            return numberOrInteger;
        }
        var string = this._unpackString(marker, markerHigh, markerLow, buffer);
        if (string !== null) {
            return string;
        }
        var list = this._unpackList(marker, markerHigh, markerLow, buffer, hydrateStructure);
        if (list !== null) {
            return list;
        }
        var byteArray = this._unpackByteArray(marker, buffer);
        if (byteArray !== null) {
            return byteArray;
        }
        var map = this._unpackMap(marker, markerHigh, markerLow, buffer, hydrateStructure);
        if (map !== null) {
            return map;
        }
        var struct = this._unpackStruct(marker, markerHigh, markerLow, buffer, hydrateStructure);
        if (struct !== null) {
            return struct;
        }
        throw (0, neo4j_driver_core_1.newError)('Unknown packed value with marker ' + marker.toString(16));
    };
    Unpacker.prototype.unpackInteger = function (buffer) {
        var marker = buffer.readUInt8();
        var result = this._unpackInteger(marker, buffer);
        if (result == null) {
            throw (0, neo4j_driver_core_1.newError)('Unable to unpack integer value with marker ' + marker.toString(16));
        }
        return result;
    };
    Unpacker.prototype._unpackBoolean = function (marker) {
        if (marker === TRUE) {
            return true;
        }
        else if (marker === FALSE) {
            return false;
        }
        else {
            return null;
        }
    };
    Unpacker.prototype._unpackNumberOrInteger = function (marker, buffer) {
        if (marker === FLOAT_64) {
            return buffer.readFloat64();
        }
        else {
            return this._unpackInteger(marker, buffer);
        }
    };
    Unpacker.prototype._unpackInteger = function (marker, buffer) {
        if (marker >= 0 && marker < 128) {
            return (0, neo4j_driver_core_1.int)(marker);
        }
        else if (marker >= 240 && marker < 256) {
            return (0, neo4j_driver_core_1.int)(marker - 256);
        }
        else if (marker === INT_8) {
            return (0, neo4j_driver_core_1.int)(buffer.readInt8());
        }
        else if (marker === INT_16) {
            return (0, neo4j_driver_core_1.int)(buffer.readInt16());
        }
        else if (marker === INT_32) {
            var b = buffer.readInt32();
            return (0, neo4j_driver_core_1.int)(b);
        }
        else if (marker === INT_64) {
            var high = buffer.readInt32();
            var low = buffer.readInt32();
            return new neo4j_driver_core_1.Integer(low, high);
        }
        else {
            return null;
        }
    };
    Unpacker.prototype._unpackString = function (marker, markerHigh, markerLow, buffer) {
        if (markerHigh === TINY_STRING) {
            return channel_1.utf8.decode(buffer, markerLow);
        }
        else if (marker === STRING_8) {
            return channel_1.utf8.decode(buffer, buffer.readUInt8());
        }
        else if (marker === STRING_16) {
            return channel_1.utf8.decode(buffer, buffer.readUInt16());
        }
        else if (marker === STRING_32) {
            return channel_1.utf8.decode(buffer, buffer.readUInt32());
        }
        else {
            return null;
        }
    };
    Unpacker.prototype._unpackList = function (marker, markerHigh, markerLow, buffer, hydrateStructure) {
        if (markerHigh === TINY_LIST) {
            return this._unpackListWithSize(markerLow, buffer, hydrateStructure);
        }
        else if (marker === LIST_8) {
            return this._unpackListWithSize(buffer.readUInt8(), buffer, hydrateStructure);
        }
        else if (marker === LIST_16) {
            return this._unpackListWithSize(buffer.readUInt16(), buffer, hydrateStructure);
        }
        else if (marker === LIST_32) {
            return this._unpackListWithSize(buffer.readUInt32(), buffer, hydrateStructure);
        }
        else {
            return null;
        }
    };
    Unpacker.prototype._unpackListWithSize = function (size, buffer, hydrateStructure) {
        var value = [];
        for (var i = 0; i < size; i++) {
            value.push(this.unpack(buffer, hydrateStructure));
        }
        return value;
    };
    Unpacker.prototype._unpackByteArray = function (marker, buffer) {
        if (marker === BYTES_8) {
            return this._unpackByteArrayWithSize(buffer.readUInt8(), buffer);
        }
        else if (marker === BYTES_16) {
            return this._unpackByteArrayWithSize(buffer.readUInt16(), buffer);
        }
        else if (marker === BYTES_32) {
            return this._unpackByteArrayWithSize(buffer.readUInt32(), buffer);
        }
        else {
            return null;
        }
    };
    Unpacker.prototype._unpackByteArrayWithSize = function (size, buffer) {
        var value = new Int8Array(size);
        for (var i = 0; i < size; i++) {
            value[i] = buffer.readInt8();
        }
        return value;
    };
    Unpacker.prototype._unpackMap = function (marker, markerHigh, markerLow, buffer, hydrateStructure) {
        if (markerHigh === TINY_MAP) {
            return this._unpackMapWithSize(markerLow, buffer, hydrateStructure);
        }
        else if (marker === MAP_8) {
            return this._unpackMapWithSize(buffer.readUInt8(), buffer, hydrateStructure);
        }
        else if (marker === MAP_16) {
            return this._unpackMapWithSize(buffer.readUInt16(), buffer, hydrateStructure);
        }
        else if (marker === MAP_32) {
            return this._unpackMapWithSize(buffer.readUInt32(), buffer, hydrateStructure);
        }
        else {
            return null;
        }
    };
    Unpacker.prototype._unpackMapWithSize = function (size, buffer, hydrateStructure) {
        var value = {};
        for (var i = 0; i < size; i++) {
            var key = this.unpack(buffer, hydrateStructure);
            value[key] = this.unpack(buffer, hydrateStructure);
        }
        return value;
    };
    Unpacker.prototype._unpackStruct = function (marker, markerHigh, markerLow, buffer, hydrateStructure) {
        if (markerHigh === TINY_STRUCT) {
            return this._unpackStructWithSize(markerLow, buffer, hydrateStructure);
        }
        else if (marker === STRUCT_8) {
            return this._unpackStructWithSize(buffer.readUInt8(), buffer, hydrateStructure);
        }
        else if (marker === STRUCT_16) {
            return this._unpackStructWithSize(buffer.readUInt16(), buffer, hydrateStructure);
        }
        else {
            return null;
        }
    };
    Unpacker.prototype._unpackStructWithSize = function (structSize, buffer, hydrateStructure) {
        var signature = buffer.readUInt8();
        var structure = new structure_1.Structure(signature, []);
        for (var i = 0; i < structSize; i++) {
            structure.fields.push(this.unpack(buffer, hydrateStructure));
        }
        return hydrateStructure(structure);
    };
    return Unpacker;
}());
exports.Unpacker = Unpacker;
function isIterable(obj) {
    if (obj == null) {
        return false;
    }
    return typeof obj[Symbol.iterator] === 'function';
}
