
import { __decorate } from '../../../../../../../external/tslib/tslib.es6.js';
import '../../../common/arrays.js';
import { createCancelablePromise } from '../../../common/async.js';
import { VSBuffer } from '../../../common/buffer.js';
import { CancellationTokenSource, CancellationToken } from '../../../common/cancellation.js';
import { memoize } from '../../../common/decorators.js';
import { CancellationError, ErrorNoTelemetry } from '../../../common/errors.js';
import { Emitter, Event, Relay } from '../../../common/event.js';
import { createSingleCallFunction } from '../../../common/functional.js';
import { toDisposable, dispose } from '../../../common/lifecycle.js';
import { revive } from '../../../common/marshalling.js';
import { isUpperAsciiLetter } from '../../../common/strings.js';
import { isUndefinedOrNull } from '../../../common/types.js';

var RequestType;
(function(RequestType) {
    RequestType[RequestType["Promise"] = 100] = "Promise";
    RequestType[RequestType["PromiseCancel"] = 101] = "PromiseCancel";
    RequestType[RequestType["EventListen"] = 102] = "EventListen";
    RequestType[RequestType["EventDispose"] = 103] = "EventDispose";
})(RequestType || (RequestType = {}));
function requestTypeToStr(type) {
    switch (type) {
    case RequestType.Promise:
        return "req";
    case RequestType.PromiseCancel:
        return "cancel";
    case RequestType.EventListen:
        return "subscribe";
    case RequestType.EventDispose:
        return "unsubscribe";
    }
}
var ResponseType;
(function(ResponseType) {
    ResponseType[ResponseType["Initialize"] = 200] = "Initialize";
    ResponseType[ResponseType["PromiseSuccess"] = 201] = "PromiseSuccess";
    ResponseType[ResponseType["PromiseError"] = 202] = "PromiseError";
    ResponseType[ResponseType["PromiseErrorObj"] = 203] = "PromiseErrorObj";
    ResponseType[ResponseType["EventFire"] = 204] = "EventFire";
})(ResponseType || (ResponseType = {}));
function responseTypeToStr(type) {
    switch (type) {
    case ResponseType.Initialize:
        return `init`;
    case ResponseType.PromiseSuccess:
        return `reply:`;
    case ResponseType.PromiseError:
    case ResponseType.PromiseErrorObj:
        return `replyErr:`;
    case ResponseType.EventFire:
        return `event:`;
    }
}
var State;
(function(State) {
    State[State["Uninitialized"] = 0] = "Uninitialized";
    State[State["Idle"] = 1] = "Idle";
})(State || (State = {}));
function readIntVQL(reader) {
    let value = 0;
    for (let n = 0; ; n += 7) {
        const next = reader.read(1);
        value |= (next.buffer[0] & 0b01111111) << n;
        if (!(next.buffer[0] & 0b10000000)) {
            return value;
        }
    }
}
const vqlZero = createOneByteBuffer(0);
function writeInt32VQL(writer, value) {
    if (value === 0) {
        writer.write(vqlZero);
        return;
    }
    let len = 0;
    for (let v2 = value; v2 !== 0; v2 = v2 >>> 7) {
        len++;
    }
    const scratch = VSBuffer.alloc(len);
    for (let i = 0; value !== 0; i++) {
        scratch.buffer[i] = value & 0b01111111;
        value = value >>> 7;
        if (value > 0) {
            scratch.buffer[i] |= 0b10000000;
        }
    }
    writer.write(scratch);
}
class BufferReader {
    constructor(buffer) {
        this.buffer = buffer;
        this.pos = 0;
    }
    read(bytes) {
        const result = this.buffer.slice(this.pos, this.pos + bytes);
        this.pos += result.byteLength;
        return result;
    }
}
class BufferWriter {
    constructor() {
        this.buffers = [];
    }
    get buffer() {
        return VSBuffer.concat(this.buffers);
    }
    write(buffer) {
        this.buffers.push(buffer);
    }
}
var DataType;
(function(DataType) {
    DataType[DataType["Undefined"] = 0] = "Undefined";
    DataType[DataType["String"] = 1] = "String";
    DataType[DataType["Buffer"] = 2] = "Buffer";
    DataType[DataType["VSBuffer"] = 3] = "VSBuffer";
    DataType[DataType["Array"] = 4] = "Array";
    DataType[DataType["Object"] = 5] = "Object";
    DataType[DataType["Int"] = 6] = "Int";
})(DataType || (DataType = {}));
function createOneByteBuffer(value) {
    const result = VSBuffer.alloc(1);
    result.writeUInt8(value, 0);
    return result;
}
const BufferPresets = {
    Undefined: createOneByteBuffer(DataType.Undefined),
    String: createOneByteBuffer(DataType.String),
    Buffer: createOneByteBuffer(DataType.Buffer),
    VSBuffer: createOneByteBuffer(DataType.VSBuffer),
    Array: createOneByteBuffer(DataType.Array),
    Object: createOneByteBuffer(DataType.Object),
    Uint: createOneByteBuffer(DataType.Int)
};
function serialize(writer, data) {
    if (typeof data === "undefined") {
        writer.write(BufferPresets.Undefined);
    } else if (typeof data === "string") {
        const buffer = VSBuffer.fromString(data);
        writer.write(BufferPresets.String);
        writeInt32VQL(writer, buffer.byteLength);
        writer.write(buffer);
    } else if (VSBuffer.isNativeBuffer(data)) {
        const buffer = VSBuffer.wrap(data);
        writer.write(BufferPresets.Buffer);
        writeInt32VQL(writer, buffer.byteLength);
        writer.write(buffer);
    } else if (data instanceof VSBuffer) {
        writer.write(BufferPresets.VSBuffer);
        writeInt32VQL(writer, data.byteLength);
        writer.write(data);
    } else if (Array.isArray(data)) {
        writer.write(BufferPresets.Array);
        writeInt32VQL(writer, data.length);
        for (const el of data) {
            serialize(writer, el);
        }
    } else if (typeof data === "number" && (data | 0) === data) {
        writer.write(BufferPresets.Uint);
        writeInt32VQL(writer, data);
    } else {
        const buffer = VSBuffer.fromString(JSON.stringify(data));
        writer.write(BufferPresets.Object);
        writeInt32VQL(writer, buffer.byteLength);
        writer.write(buffer);
    }
}
function deserialize(reader) {
    const type = reader.read(1).readUInt8(0);
    switch (type) {
    case DataType.Undefined:
        return undefined;
    case DataType.String:
        return ( reader.read(readIntVQL(reader)).toString());
    case DataType.Buffer:
        return reader.read(readIntVQL(reader)).buffer;
    case DataType.VSBuffer:
        return reader.read(readIntVQL(reader));
    case DataType.Array:
        {
            const length = readIntVQL(reader);
            const result = [];
            for (let i = 0; i < length; i++) {
                result.push(deserialize(reader));
            }
            return result;
        }
    case DataType.Object:
        return JSON.parse(( reader.read(readIntVQL(reader)).toString()));
    case DataType.Int:
        return readIntVQL(reader);
    }
}
class ChannelServer {
    constructor(protocol, ctx, logger = null, timeoutDelay = 1000) {
        this.protocol = protocol;
        this.ctx = ctx;
        this.logger = logger;
        this.timeoutDelay = timeoutDelay;
        this.channels = ( new Map());
        this.activeRequests = ( new Map());
        this.pendingRequests = ( new Map());
        this.protocolListener = this.protocol.onMessage(msg => this.onRawMessage(msg));
        this.sendResponse({
            type: ResponseType.Initialize
        });
    }
    registerChannel(channelName, channel) {
        this.channels.set(channelName, channel);
        setTimeout(() => this.flushPendingRequests(channelName), 0);
    }
    sendResponse(response) {
        switch (response.type) {
        case ResponseType.Initialize:
            {
                const msgLength = this.send([response.type]);
                this.logger?.logOutgoing(msgLength, 0, RequestInitiator.OtherSide, responseTypeToStr(response.type));
                return;
            }
        case ResponseType.PromiseSuccess:
        case ResponseType.PromiseError:
        case ResponseType.EventFire:
        case ResponseType.PromiseErrorObj:
            {
                const msgLength = this.send([response.type, response.id], response.data);
                this.logger?.logOutgoing(
                    msgLength,
                    response.id,
                    RequestInitiator.OtherSide,
                    responseTypeToStr(response.type),
                    response.data
                );
                return;
            }
        }
    }
    send(header, body = undefined) {
        const writer = ( new BufferWriter());
        serialize(writer, header);
        serialize(writer, body);
        return this.sendBuffer(writer.buffer);
    }
    sendBuffer(message) {
        try {
            this.protocol.send(message);
            return message.byteLength;
        } catch (err) {
            return 0;
        }
    }
    onRawMessage(message) {
        const reader = ( new BufferReader(message));
        const header = deserialize(reader);
        const body = deserialize(reader);
        const type = header[0];
        switch (type) {
        case RequestType.Promise:
            this.logger?.logIncoming(
                message.byteLength,
                header[1],
                RequestInitiator.OtherSide,
                `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`,
                body
            );
            return this.onPromise({
                type,
                id: header[1],
                channelName: header[2],
                name: header[3],
                arg: body
            });
        case RequestType.EventListen:
            this.logger?.logIncoming(
                message.byteLength,
                header[1],
                RequestInitiator.OtherSide,
                `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`,
                body
            );
            return this.onEventListen({
                type,
                id: header[1],
                channelName: header[2],
                name: header[3],
                arg: body
            });
        case RequestType.PromiseCancel:
            this.logger?.logIncoming(
                message.byteLength,
                header[1],
                RequestInitiator.OtherSide,
                `${requestTypeToStr(type)}`
            );
            return this.disposeActiveRequest({
                type,
                id: header[1]
            });
        case RequestType.EventDispose:
            this.logger?.logIncoming(
                message.byteLength,
                header[1],
                RequestInitiator.OtherSide,
                `${requestTypeToStr(type)}`
            );
            return this.disposeActiveRequest({
                type,
                id: header[1]
            });
        }
    }
    onPromise(request) {
        const channel = this.channels.get(request.channelName);
        if (!channel) {
            this.collectPendingRequest(request);
            return;
        }
        const cancellationTokenSource = ( new CancellationTokenSource());
        let promise;
        try {
            promise = channel.call(this.ctx, request.name, request.arg, cancellationTokenSource.token);
        } catch (err) {
            promise = Promise.reject(err);
        }
        const id = request.id;
        promise.then(data => {
            this.sendResponse({
                id,
                data,
                type: ResponseType.PromiseSuccess
            });
        }, err => {
            if (err instanceof Error) {
                this.sendResponse({
                    id,
                    data: {
                        message: err.message,
                        name: err.name,
                        stack: err.stack ? err.stack.split("\n") : undefined
                    },
                    type: ResponseType.PromiseError
                });
            } else {
                this.sendResponse({
                    id,
                    data: err,
                    type: ResponseType.PromiseErrorObj
                });
            }
        }).finally(() => {
            disposable.dispose();
            this.activeRequests.delete(request.id);
        });
        const disposable = toDisposable(() => cancellationTokenSource.cancel());
        this.activeRequests.set(request.id, disposable);
    }
    onEventListen(request) {
        const channel = this.channels.get(request.channelName);
        if (!channel) {
            this.collectPendingRequest(request);
            return;
        }
        const id = request.id;
        const event = channel.listen(this.ctx, request.name, request.arg);
        const disposable = event(data => this.sendResponse({
            id,
            data,
            type: ResponseType.EventFire
        }));
        this.activeRequests.set(request.id, disposable);
    }
    disposeActiveRequest(request) {
        const disposable = this.activeRequests.get(request.id);
        if (disposable) {
            disposable.dispose();
            this.activeRequests.delete(request.id);
        }
    }
    collectPendingRequest(request) {
        let pendingRequests = this.pendingRequests.get(request.channelName);
        if (!pendingRequests) {
            pendingRequests = [];
            this.pendingRequests.set(request.channelName, pendingRequests);
        }
        const timer = setTimeout(() => {
            console.error(`Unknown channel: ${request.channelName}`);
            if (request.type === RequestType.Promise) {
                this.sendResponse({
                    id: request.id,
                    data: {
                        name: "Unknown channel",
                        message: `Channel name '${request.channelName}' timed out after ${this.timeoutDelay}ms`,
                        stack: undefined
                    },
                    type: ResponseType.PromiseError
                });
            }
        }, this.timeoutDelay);
        pendingRequests.push({
            request,
            timeoutTimer: timer
        });
    }
    flushPendingRequests(channelName) {
        const requests = this.pendingRequests.get(channelName);
        if (requests) {
            for (const request of requests) {
                clearTimeout(request.timeoutTimer);
                switch (request.request.type) {
                case RequestType.Promise:
                    this.onPromise(request.request);
                    break;
                case RequestType.EventListen:
                    this.onEventListen(request.request);
                    break;
                }
            }
            this.pendingRequests.delete(channelName);
        }
    }
    dispose() {
        if (this.protocolListener) {
            this.protocolListener.dispose();
            this.protocolListener = null;
        }
        dispose(( this.activeRequests.values()));
        this.activeRequests.clear();
    }
}
var RequestInitiator;
(function(RequestInitiator) {
    RequestInitiator[RequestInitiator["LocalSide"] = 0] = "LocalSide";
    RequestInitiator[RequestInitiator["OtherSide"] = 1] = "OtherSide";
})(RequestInitiator || (RequestInitiator = {}));
class ChannelClient {
    constructor(protocol, logger = null) {
        this.protocol = protocol;
        this.isDisposed = false;
        this.state = State.Uninitialized;
        this.activeRequests = ( new Set());
        this.handlers = ( new Map());
        this.lastRequestId = 0;
        this._onDidInitialize = ( new Emitter());
        this.onDidInitialize = this._onDidInitialize.event;
        this.protocolListener = this.protocol.onMessage(msg => this.onBuffer(msg));
        this.logger = logger;
    }
    getChannel(channelName) {
        const that = this;
        return {
            call(command, arg, cancellationToken) {
                if (that.isDisposed) {
                    return Promise.reject(( new CancellationError()));
                }
                return that.requestPromise(channelName, command, arg, cancellationToken);
            },
            listen(event, arg) {
                if (that.isDisposed) {
                    return Event.None;
                }
                return that.requestEvent(channelName, event, arg);
            }
        };
    }
    requestPromise(channelName, name, arg, cancellationToken = CancellationToken.None) {
        const id = this.lastRequestId++;
        const type = RequestType.Promise;
        const request = {
            id,
            type,
            channelName,
            name,
            arg
        };
        if (cancellationToken.isCancellationRequested) {
            return Promise.reject(( new CancellationError()));
        }
        let disposable;
        let disposableWithRequestCancel;
        const result = ( new Promise((c, e) => {
            if (cancellationToken.isCancellationRequested) {
                return e(( new CancellationError()));
            }
            const doRequest = () => {
                const handler = response => {
                    switch (response.type) {
                    case ResponseType.PromiseSuccess:
                        this.handlers.delete(id);
                        c(response.data);
                        break;
                    case ResponseType.PromiseError:
                        {
                            this.handlers.delete(id);
                            const error = ( new Error(response.data.message));
                            error.stack = Array.isArray(response.data.stack) ? response.data.stack.join("\n") : response.data.stack;
                            error.name = response.data.name;
                            e(error);
                            break;
                        }
                    case ResponseType.PromiseErrorObj:
                        this.handlers.delete(id);
                        e(response.data);
                        break;
                    }
                };
                this.handlers.set(id, handler);
                this.sendRequest(request);
            };
            let uninitializedPromise = null;
            if (this.state === State.Idle) {
                doRequest();
            } else {
                uninitializedPromise = createCancelablePromise(_ => this.whenInitialized());
                uninitializedPromise.then(() => {
                    uninitializedPromise = null;
                    doRequest();
                });
            }
            const cancel = () => {
                if (uninitializedPromise) {
                    uninitializedPromise.cancel();
                    uninitializedPromise = null;
                } else {
                    this.sendRequest({
                        id,
                        type: RequestType.PromiseCancel
                    });
                }
                e(( new CancellationError()));
            };
            disposable = cancellationToken.onCancellationRequested(cancel);
            disposableWithRequestCancel = {
                dispose: createSingleCallFunction(() => {
                    cancel();
                    disposable.dispose();
                })
            };
            this.activeRequests.add(disposableWithRequestCancel);
        }));
        return result.finally(() => {
            disposable?.dispose();
            this.activeRequests.delete(disposableWithRequestCancel);
        });
    }
    requestEvent(channelName, name, arg) {
        const id = this.lastRequestId++;
        const type = RequestType.EventListen;
        const request = {
            id,
            type,
            channelName,
            name,
            arg
        };
        let uninitializedPromise = null;
        const emitter = ( new Emitter({
            onWillAddFirstListener: () => {
                const doRequest = () => {
                    this.activeRequests.add(emitter);
                    this.sendRequest(request);
                };
                if (this.state === State.Idle) {
                    doRequest();
                } else {
                    uninitializedPromise = createCancelablePromise(_ => this.whenInitialized());
                    uninitializedPromise.then(() => {
                        uninitializedPromise = null;
                        doRequest();
                    });
                }
            },
            onDidRemoveLastListener: () => {
                if (uninitializedPromise) {
                    uninitializedPromise.cancel();
                    uninitializedPromise = null;
                } else {
                    this.activeRequests.delete(emitter);
                    this.sendRequest({
                        id,
                        type: RequestType.EventDispose
                    });
                }
                this.handlers.delete(id);
            }
        }));
        const handler = res => emitter.fire(res.data);
        this.handlers.set(id, handler);
        return emitter.event;
    }
    sendRequest(request) {
        switch (request.type) {
        case RequestType.Promise:
        case RequestType.EventListen:
            {
                const msgLength = this.send([request.type, request.id, request.channelName, request.name], request.arg);
                this.logger?.logOutgoing(
                    msgLength,
                    request.id,
                    RequestInitiator.LocalSide,
                    `${requestTypeToStr(request.type)}: ${request.channelName}.${request.name}`,
                    request.arg
                );
                return;
            }
        case RequestType.PromiseCancel:
        case RequestType.EventDispose:
            {
                const msgLength = this.send([request.type, request.id]);
                this.logger?.logOutgoing(
                    msgLength,
                    request.id,
                    RequestInitiator.LocalSide,
                    requestTypeToStr(request.type)
                );
                return;
            }
        }
    }
    send(header, body = undefined) {
        const writer = ( new BufferWriter());
        serialize(writer, header);
        serialize(writer, body);
        return this.sendBuffer(writer.buffer);
    }
    sendBuffer(message) {
        try {
            this.protocol.send(message);
            return message.byteLength;
        } catch (err) {
            return 0;
        }
    }
    onBuffer(message) {
        const reader = ( new BufferReader(message));
        const header = deserialize(reader);
        const body = deserialize(reader);
        const type = header[0];
        switch (type) {
        case ResponseType.Initialize:
            this.logger?.logIncoming(message.byteLength, 0, RequestInitiator.LocalSide, responseTypeToStr(type));
            return this.onResponse({
                type: header[0]
            });
        case ResponseType.PromiseSuccess:
        case ResponseType.PromiseError:
        case ResponseType.EventFire:
        case ResponseType.PromiseErrorObj:
            this.logger?.logIncoming(
                message.byteLength,
                header[1],
                RequestInitiator.LocalSide,
                responseTypeToStr(type),
                body
            );
            return this.onResponse({
                type: header[0],
                id: header[1],
                data: body
            });
        }
    }
    onResponse(response) {
        if (response.type === ResponseType.Initialize) {
            this.state = State.Idle;
            this._onDidInitialize.fire();
            return;
        }
        const handler = this.handlers.get(response.id);
        handler?.(response);
    }
    get onDidInitializePromise() {
        return Event.toPromise(this.onDidInitialize);
    }
    whenInitialized() {
        if (this.state === State.Idle) {
            return Promise.resolve();
        } else {
            return this.onDidInitializePromise;
        }
    }
    dispose() {
        this.isDisposed = true;
        if (this.protocolListener) {
            this.protocolListener.dispose();
            this.protocolListener = null;
        }
        dispose(( this.activeRequests.values()));
        this.activeRequests.clear();
    }
}
ChannelClient.__decorator = ( __decorate([memoize], ChannelClient.prototype, "onDidInitializePromise", null));
class IPCClient {
    constructor(protocol, ctx, ipcLogger = null) {
        const writer = ( new BufferWriter());
        serialize(writer, ctx);
        protocol.send(writer.buffer);
        this.channelClient = ( new ChannelClient(protocol, ipcLogger));
        this.channelServer = ( new ChannelServer(protocol, ctx, ipcLogger));
    }
    getChannel(channelName) {
        return this.channelClient.getChannel(channelName);
    }
    registerChannel(channelName, channel) {
        this.channelServer.registerChannel(channelName, channel);
    }
    dispose() {
        this.channelClient.dispose();
        this.channelServer.dispose();
    }
}
function getDelayedChannel(promise) {
    return {
        call(command, arg, cancellationToken) {
            return promise.then(c => c.call(command, arg, cancellationToken));
        },
        listen(event, arg) {
            const relay = ( new Relay());
            promise.then(c => relay.input = c.listen(event, arg));
            return relay.event;
        }
    };
}
var ProxyChannel;
(function(ProxyChannel) {
    function fromService(service, disposables, options) {
        const handler = service;
        const disableMarshalling = options?.disableMarshalling;
        const mapEventNameToEvent = ( new Map());
        for (const key in handler) {
            if (propertyIsEvent(key)) {
                mapEventNameToEvent.set(key, Event.buffer(handler[key], true, undefined, disposables));
            }
        }
        return new (class {
            listen(_, event, arg) {
                const eventImpl = mapEventNameToEvent.get(event);
                if (eventImpl) {
                    return eventImpl;
                }
                const target = handler[event];
                if (typeof target === "function") {
                    if (propertyIsDynamicEvent(event)) {
                        return target.call(handler, arg);
                    }
                    if (propertyIsEvent(event)) {
                        mapEventNameToEvent.set(event, Event.buffer(handler[event], true, undefined, disposables));
                        return mapEventNameToEvent.get(event);
                    }
                }
                throw ( new ErrorNoTelemetry(`Event not found: ${event}`));
            }
            call(_, command, args) {
                const target = handler[command];
                if (typeof target === "function") {
                    if (!disableMarshalling && Array.isArray(args)) {
                        for (let i = 0; i < args.length; i++) {
                            args[i] = revive(args[i]);
                        }
                    }
                    let res = target.apply(handler, args);
                    if (!(res instanceof Promise)) {
                        res = Promise.resolve(res);
                    }
                    return res;
                }
                throw ( new ErrorNoTelemetry(`Method not found: ${command}`));
            }
        })();
    }
    ProxyChannel.fromService = fromService;
    function toService(channel, options) {
        const disableMarshalling = options?.disableMarshalling;
        return ( new Proxy({}, {
            get(_target, propKey) {
                if (typeof propKey === "string") {
                    if (options?.properties?.has(propKey)) {
                        return options.properties.get(propKey);
                    }
                    if (propertyIsDynamicEvent(propKey)) {
                        return function(arg) {
                            return channel.listen(propKey, arg);
                        };
                    }
                    if (propertyIsEvent(propKey)) {
                        return channel.listen(propKey);
                    }
                    return async function(...args) {
                        let methodArgs;
                        if (options && !isUndefinedOrNull(options.context)) {
                            methodArgs = [options.context, ...args];
                        } else {
                            methodArgs = args;
                        }
                        const result = await channel.call(propKey, methodArgs);
                        if (!disableMarshalling) {
                            return revive(result);
                        }
                        return result;
                    };
                }
                throw ( new ErrorNoTelemetry(`Property not found: ${String(propKey)}`));
            }
        }));
    }
    ProxyChannel.toService = toService;
    function propertyIsEvent(name) {
        return name[0] === "o" && name[1] === "n" && isUpperAsciiLetter(name.charCodeAt(2));
    }
    function propertyIsDynamicEvent(name) {
        return /^onDynamic/.test(name) && isUpperAsciiLetter(name.charCodeAt(9));
    }
})(ProxyChannel || (ProxyChannel = {}));

export { BufferReader, BufferWriter, ChannelClient, ChannelServer, IPCClient, ProxyChannel, RequestInitiator, deserialize, getDelayedChannel, serialize };
