/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
/*
 * Copyright (C) 2022 TypeFox and others.
 *
 * 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
 */
import { RequestQueue, RequestQueueingType } from './requestQueue.js';
import { ServerResponse } from './requests.js';
import { CallbackMap } from './callbackMap.js';
import { TypeScriptServerError } from './serverError.js';
export var ExecutionTarget;
(function (ExecutionTarget) {
    ExecutionTarget[ExecutionTarget["Semantic"] = 0] = "Semantic";
    ExecutionTarget[ExecutionTarget["Syntax"] = 1] = "Syntax";
})(ExecutionTarget = ExecutionTarget || (ExecutionTarget = {}));
export class ProcessBasedTsServer {
    constructor(_serverId, _serverSource, _process, _tsServerLogFile, _requestCanceller, _version, _tracer) {
        this._serverId = _serverId;
        this._serverSource = _serverSource;
        this._process = _process;
        this._tsServerLogFile = _tsServerLogFile;
        this._requestCanceller = _requestCanceller;
        this._version = _version;
        this._tracer = _tracer;
        this._requestQueue = new RequestQueue();
        this._callbacks = new CallbackMap();
        this._pendingResponses = new Set();
        this._eventHandlers = new Set();
        this._exitHandlers = new Set();
        this._errorHandlers = new Set();
        this._process.onData(msg => {
            this.dispatchMessage(msg);
        });
        this._process.onExit((code, signal) => {
            this._exitHandlers.forEach(handler => handler({ code, signal }));
            this._callbacks.destroy('server exited');
        });
        this._process.onError(error => {
            this._errorHandlers.forEach(handler => handler(error));
            this._callbacks.destroy('server errored');
        });
    }
    onEvent(handler) {
        this._eventHandlers.add(handler);
    }
    onExit(handler) {
        this._exitHandlers.add(handler);
    }
    onError(handler) {
        this._errorHandlers.add(handler);
    }
    get tsServerLogFile() {
        return this._tsServerLogFile;
    }
    write(serverRequest) {
        this._process.write(serverRequest);
    }
    dispose() {
        this._callbacks.destroy('server disposed');
        this._pendingResponses.clear();
        this._eventHandlers.clear();
        this._exitHandlers.clear();
        this._errorHandlers.clear();
    }
    kill() {
        this.dispose();
        this._process.kill();
    }
    dispatchMessage(message) {
        try {
            switch (message.type) {
                case 'response':
                    if (this._serverSource) {
                        this.dispatchResponse({
                            ...message,
                        });
                    }
                    else {
                        this.dispatchResponse(message);
                    }
                    break;
                case 'event': {
                    const event = message;
                    if (event.event === 'requestCompleted') {
                        const seq = event.body.request_seq;
                        const callback = this._callbacks.fetch(seq);
                        if (callback) {
                            this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback);
                            callback.onSuccess(undefined);
                        }
                    }
                    else {
                        this._tracer.traceEvent(this._serverId, event);
                        this._eventHandlers.forEach(handler => handler(event));
                    }
                    break;
                }
                default:
                    throw new Error(`Unknown message type ${message.type} received`);
            }
        }
        finally {
            this.sendNextRequests();
        }
    }
    tryCancelRequest(seq, command) {
        try {
            if (this._requestQueue.tryDeletePendingRequest(seq)) {
                this.logTrace(`Canceled request with sequence number ${seq}`);
                return true;
            }
            if (this._requestCanceller.tryCancelOngoingRequest(seq)) {
                return true;
            }
            this.logTrace(`Tried to cancel request with sequence number ${seq}. But request got already delivered.`);
            return false;
        }
        finally {
            const callback = this.fetchCallback(seq);
            callback?.onSuccess(new ServerResponse.Cancelled(`Cancelled request ${seq} - ${command}`));
        }
    }
    dispatchResponse(response) {
        const callback = this.fetchCallback(response.request_seq);
        if (!callback) {
            return;
        }
        this._tracer.traceResponse(this._serverId, response, callback);
        if (response.success) {
            callback.onSuccess(response);
        }
        else if (response.message === 'No content available.') {
            // Special case where response itself is successful but there is not any data to return.
            callback.onSuccess(ServerResponse.NoContent);
        }
        else {
            callback.onError(TypeScriptServerError.create(this._serverId, this._version, response));
        }
    }
    executeImpl(command, args, executeInfo) {
        const request = this._requestQueue.createRequest(command, args);
        const requestInfo = {
            request,
            expectsResponse: executeInfo.expectsResult,
            isAsync: executeInfo.isAsync,
            queueingType: ProcessBasedTsServer.getQueueingType(command, executeInfo.lowPriority),
        };
        let result;
        if (executeInfo.expectsResult) {
            result = new Promise((resolve, reject) => {
                this._callbacks.add(request.seq, { onSuccess: resolve, onError: reject, queuingStartTime: Date.now(), isAsync: executeInfo.isAsync }, executeInfo.isAsync);
                if (executeInfo.token) {
                    executeInfo.token.onCancellationRequested(() => {
                        this.tryCancelRequest(request.seq, command);
                    });
                }
            });
        }
        this._requestQueue.enqueue(requestInfo);
        this.sendNextRequests();
        return [result];
    }
    sendNextRequests() {
        // console.error({ pending: this._pendingResponses.size, queue: this._requestQueue.length });
        while (this._pendingResponses.size === 0 && this._requestQueue.length > 0) {
            const item = this._requestQueue.dequeue();
            if (item) {
                this.sendRequest(item);
            }
        }
    }
    sendRequest(requestItem) {
        const serverRequest = requestItem.request;
        this._tracer.traceRequest(this._serverId, serverRequest, requestItem.expectsResponse, this._requestQueue.length);
        if (requestItem.expectsResponse && !requestItem.isAsync) {
            this._pendingResponses.add(requestItem.request.seq);
        }
        try {
            this.write(serverRequest);
        }
        catch (err) {
            const callback = this.fetchCallback(serverRequest.seq);
            callback?.onError(err);
        }
    }
    fetchCallback(seq) {
        const callback = this._callbacks.fetch(seq);
        if (!callback) {
            return undefined;
        }
        this._pendingResponses.delete(seq);
        return callback;
    }
    logTrace(message) {
        this._tracer.logTrace(this._serverId, message);
    }
    static getQueueingType(command, lowPriority) {
        if (ProcessBasedTsServer.fenceCommands.has(command)) {
            return RequestQueueingType.Fence;
        }
        return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal;
    }
}
ProcessBasedTsServer.fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']);
//# sourceMappingURL=server.js.map