/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module RpcInterface
 */
import { BentleyStatus } from "@itwin/core-bentley";
import { IModelError } from "../../IModelError";
// cspell:ignore unmarshal
/* eslint-disable @typescript-eslint/no-deprecated */
function isBuffer(val) {
    return val && typeof (val.constructor) !== "undefined" && typeof (val.constructor.isBuffer) === "function" && val.constructor.isBuffer(val);
}
let marshalingTarget;
let chunkThreshold = 0;
/** @internal */
export var MarshalingBinaryMarker;
(function (MarshalingBinaryMarker) {
    function createDefault() {
        return { isBinary: true, index: 0, size: -1, chunks: 1 };
    }
    MarshalingBinaryMarker.createDefault = createDefault;
})(MarshalingBinaryMarker || (MarshalingBinaryMarker = {}));
/** @internal */
export var RpcSerializedValue;
(function (RpcSerializedValue) {
    function create(objects = "", data = []) {
        return { objects, data };
    }
    RpcSerializedValue.create = create;
})(RpcSerializedValue || (RpcSerializedValue = {}));
/** @internal */
export class RpcMarshaling {
    constructor() { }
    /** Serializes a value. */
    static serialize(protocol, value) {
        const serialized = RpcSerializedValue.create();
        if (value === undefined)
            return serialized;
        marshalingTarget = serialized;
        chunkThreshold = protocol ? protocol.transferChunkThreshold : 0;
        serialized.objects = JSON.stringify(value, (_key, _value) => WireFormat.marshal(_key, _value));
        marshalingTarget = undefined;
        chunkThreshold = 0;
        return serialized;
    }
    /** Deserializes a value. */
    static deserialize(protocol, value) {
        if (value.objects === "") {
            return undefined;
        }
        marshalingTarget = value;
        chunkThreshold = protocol ? protocol.transferChunkThreshold : 0;
        let result;
        try {
            result = JSON.parse(value.objects, (_key, _value) => WireFormat.unmarshal(_key, _value));
        }
        catch (error) {
            if (error instanceof SyntaxError)
                throw new IModelError(BentleyStatus.ERROR, `Invalid JSON: "${value.objects}"`);
            throw error;
        }
        marshalingTarget = undefined;
        chunkThreshold = 0;
        return result;
    }
}
class WireFormat {
    /** JSON.stringify replacer callback. */
    static marshal(_key, value) {
        const asBinary = WireFormat.marshalBinary(value);
        if (asBinary) {
            return asBinary;
        }
        const asError = WireFormat.marshalError(value);
        if (asError) {
            return asError;
        }
        return value;
    }
    /** JSON.parse reviver callback. */
    static unmarshal(_key, value) {
        if (typeof (value) === "object" && value !== null && value.hasOwnProperty("isBinary") && value.isBinary) {
            return WireFormat.unmarshalBinary(value);
        }
        return value;
    }
    static marshalBinary(value) {
        if (value instanceof Uint8Array || isBuffer(value)) {
            const marker = { isBinary: true, index: -1, size: value.byteLength, chunks: 1 };
            if (chunkThreshold && value.byteLength > chunkThreshold) {
                marker.index = marshalingTarget.data.length;
                marker.chunks = 0;
                let cursor = value.byteOffset;
                const end = cursor + value.byteLength;
                let chunk = chunkThreshold;
                for (;;) {
                    if (cursor >= end) {
                        break;
                    }
                    marshalingTarget.data.push(new Uint8Array(value.buffer, cursor, chunk));
                    ++marker.chunks;
                    cursor += chunk;
                    const consumed = cursor - value.byteOffset;
                    const remaining = value.byteLength - consumed;
                    chunk = Math.min(chunkThreshold, remaining);
                }
            }
            else {
                marker.index = marshalingTarget.data.push(value) - 1;
            }
            return marker;
        }
        else {
            return undefined;
        }
    }
    static unmarshalBinary(value) {
        if (value.index >= marshalingTarget.data.length) {
            throw new IModelError(BentleyStatus.ERROR, `Cannot unmarshal missing binary value.`);
        }
        if (value.chunks === 0) {
            return new Uint8Array();
        }
        else if (value.chunks === 1) {
            return new Uint8Array(marshalingTarget.data[value.index]);
        }
        else {
            const buffer = new ArrayBuffer(value.size);
            const view = new Uint8Array(buffer);
            let cursor = 0;
            for (let c = 0; c !== value.chunks; ++c) {
                const chunk = marshalingTarget.data[value.index + c];
                view.set(chunk, cursor);
                cursor += chunk.byteLength;
            }
            return view;
        }
    }
    static marshalError(value) {
        if (value instanceof Error) {
            const props = Object.getOwnPropertyDescriptors(value);
            props.isError = { configurable: true, enumerable: true, writable: true, value: true };
            props.name = { configurable: true, enumerable: true, writable: true, value: value.name };
            props.message = { configurable: true, enumerable: true, writable: true, value: value.message };
            props.stack = { configurable: true, enumerable: true, writable: true, value: value.stack };
            return Object.create(Object.prototype, props);
        }
        return undefined;
    }
}
//# sourceMappingURL=RpcMarshaling.js.map