"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module IpcSocket
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.IpcWebSocketTransport = void 0;
const IpcWebSocket_1 = require("./IpcWebSocket");
function isBuffer(val) {
    return val && typeof (val.constructor) !== "undefined" && typeof (val.constructor.isBuffer) === "function" && val.constructor.isBuffer(val);
}
let parts = [];
/** @internal */
class IpcWebSocketTransport {
    _partial;
    _received = [];
    _outstanding = 0;
    unwrap(data) {
        return (typeof (Blob) !== "undefined" && data instanceof Blob) ? data.arrayBuffer() : data;
    }
    async notifyIncoming(data, connection) {
        if (this._partial) {
            this._received.push(data);
            --this._outstanding;
            if (this._outstanding === 0) {
                const partial = this._partial;
                const received = this._received;
                this._partial = undefined;
                this._received = [];
                await Promise.all(received.map(async (v, i, a) => a[i] = await this.unwrap(v)));
                parts = received;
                const message = JSON.parse(partial, reviver);
                parts.length = 0;
                return InSentOrder.deliver(message, connection);
            }
            else {
                return IpcWebSocket_1.IpcWebSocketMessage.internal();
            }
        }
        else {
            const [serialized, followers] = JSON.parse(data);
            if (followers) {
                this._partial = serialized;
                this._outstanding = followers;
                return IpcWebSocket_1.IpcWebSocketMessage.internal();
            }
            else {
                const message = JSON.parse(serialized, reviver);
                return InSentOrder.deliver(message, connection);
            }
        }
    }
    serialize(data) {
        parts.length = 0;
        const objects = JSON.stringify(data, replacer);
        const value = [JSON.stringify([objects, parts.length]), ...parts];
        parts.length = 0;
        return value;
    }
    notifyClose(connection) {
        InSentOrder.close(connection);
    }
}
exports.IpcWebSocketTransport = IpcWebSocketTransport;
const types = [Uint8Array, Int8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, DataView];
function identify(value) {
    return isBuffer(value) ? 0 : types.indexOf(value.constructor);
}
function lookup(value) {
    return types[value.type];
}
function replacer(_key, value) {
    const asBinary = replaceBinary(value);
    if (asBinary) {
        return asBinary;
    }
    return value;
}
function reviver(_key, value) {
    if (typeof (value) === "object" && value !== null && value.hasOwnProperty("ipc") && value.ipc === "binary") {
        return reviveBinary(value);
    }
    return value;
}
function replaceBinary(value) {
    if (ArrayBuffer.isView(value) || isBuffer(value)) {
        const index = parts.push(value) - 1;
        const type = identify(value);
        return { ipc: "binary", type, index };
    }
    else {
        return undefined;
    }
}
function reviveBinary(value) {
    const constructor = lookup(value);
    const part = parts[value.index];
    return new constructor(part);
}
function makePromise() {
    let resolve = () => { };
    let reject = () => { };
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise, resolve, reject };
}
/* Reconstructing the sequence in which messages were sent is necessary since
   the binary data for a message has to be awaited in IpcWebSocketTransport.unwrap. */
class InSentOrder {
    static _connections = new Map();
    static async deliver(message, connection) {
        let context = this._connections.get(connection);
        if (!context) {
            context = { queue: [], last: -1 };
            this._connections.set(connection, context);
        }
        const entry = new InSentOrder(message);
        context.queue.push(entry);
        context.queue.sort((a, b) => a.sequence - b.sequence);
        while (context.queue.length !== 0) {
            const next = context.queue[0];
            const duplicate = next.sequence <= context.last;
            const match = next.sequence === (context.last + 1);
            if (duplicate) {
                next.duplicate = true;
            }
            else if (match) {
                ++context.last;
            }
            if (duplicate || match) {
                context.queue.shift();
                next.release();
            }
            else {
                break;
            }
        }
        return entry.message;
    }
    static close(connection) {
        this._connections.delete(connection);
    }
    release = () => { };
    sequence;
    duplicate = false;
    message;
    constructor(message) {
        this.sequence = message.sequence;
        const { promise, resolve } = makePromise();
        this.message = promise;
        this.release = () => {
            const value = this.duplicate ? IpcWebSocket_1.IpcWebSocketMessage.duplicate() : message;
            resolve(value);
        };
    }
}
//# sourceMappingURL=IpcWebSocketTransport.js.map