/*---------------------------------------------------------
 * Copyright (C) Microsoft Corporation. All rights reserved.
 *--------------------------------------------------------*/
'use strict';
define(["require", "exports", 'vs/languages/lib/common/wireProtocol', 'monaco', 'child_process', 'path'], function (require, exports, WireProtocol, monaco, cp, path) {
    var isWin = /^win/.test(process.platform);
    var isDarwin = /^darwin/.test(process.platform);
    var isLinux = /^linux/.test(process.platform);
    var arch = process.arch;
    var TypeScriptServiceClient = (function () {
        function TypeScriptServiceClient(host, configService) {
            this.host = host;
            this.configService = configService;
            this.pathSeparator = path.sep;
            this.servicePromise = null;
            this.lastError = null;
            this.sequenceNumber = 0;
            this.requestQueue = [];
            this.pendingResponses = 0;
            this.callbacks = Object.create(null);
            this.startService();
        }
        Object.defineProperty(TypeScriptServiceClient.prototype, "trace", {
            get: function () {
                return TypeScriptServiceClient.Trace;
            },
            enumerable: true,
            configurable: true
        });
        TypeScriptServiceClient.prototype.service = function () {
            if (this.servicePromise) {
                return this.servicePromise;
            }
            if (this.lastError) {
                return monaco.Promise.wrapError(this.lastError);
            }
            this.startService();
            return this.servicePromise;
        };
        TypeScriptServiceClient.prototype.startService = function () {
            var _this = this;
            this.servicePromise = new monaco.Promise(function (c, e, p) {
                _this.configService.loadConfiguration('typescript').done(function (config) {
                    var childProcess = null;
                    try {
                        var tsserverConfig = config ? config['tsdk'] : undefined;
                        var modulePath = tsserverConfig ? path.join(tsserverConfig, 'tsserver.js') : path.join(__dirname, 'lib', 'tsserver.js');
                        var args;
                        var value = process.env.TSS_DEBUG;
                        if (value) {
                            var port = parseInt(value);
                            args = isNaN(port) ? [modulePath] : ['--debug=' + port, modulePath];
                        }
                        else {
                            args = [modulePath];
                        }
                        if (isWin) {
                            childProcess = cp.spawn(path.join(__dirname, 'bin/win/node.exe'), args);
                        }
                        else if (isDarwin) {
                            childProcess = cp.spawn(path.join(__dirname, 'bin/osx/node'), args);
                        }
                        else if (isLinux && arch === 'x64') {
                            childProcess = cp.spawn(path.join(__dirname, 'bin/linux/x64/node'), args);
                        }
                        else {
                            childProcess = cp.fork(modulePath, [], {
                                silent: true
                            });
                        }
                        childProcess.on('error', function (err) {
                            _this.lastError = err;
                            _this.serviceExited();
                        });
                        childProcess.on('exit', function (err) {
                            _this.serviceExited();
                        });
                        _this.reader = new WireProtocol.Reader(childProcess.stdout, function (msg) {
                            _this.dispatchMessage(msg);
                        });
                        c(childProcess);
                    }
                    catch (error) {
                        e(error);
                    }
                }, function (err) {
                    e(err);
                });
            });
            this.serviceStarted();
        };
        TypeScriptServiceClient.prototype.serviceStarted = function () {
            /*
            this.mode.getOpenBuffers().forEach((file) => {
                this.execute('open', { file: file }, false);
            });
            */
        };
        TypeScriptServiceClient.prototype.serviceExited = function () {
            var _this = this;
            this.servicePromise = null;
            Object.keys(this.callbacks).forEach(function (key) {
                _this.callbacks[parseInt(key)].e(new Error('Service died.'));
            });
        };
        TypeScriptServiceClient.prototype.asAbsolutePath = function (resource) {
            if (resource.scheme !== 'file') {
                return null;
            }
            var result = resource.fsPath;
            // Both \ and / must be escaped in regular expressions
            return result ? result.replace(new RegExp('\\' + this.pathSeparator, 'g'), '/') : null;
        };
        TypeScriptServiceClient.prototype.asUrl = function (filepath) {
            return new monaco.URL(monaco.URI.file(filepath));
        };
        TypeScriptServiceClient.prototype.execute = function (command, args, expectsResult) {
            var _this = this;
            if (expectsResult === void 0) { expectsResult = true; }
            var request = {
                seq: this.sequenceNumber++,
                type: 'request',
                command: command,
                arguments: args
            };
            var requestInfo = {
                request: request,
                promise: null,
                callbacks: null
            };
            var result = null;
            if (expectsResult) {
                result = new monaco.Promise(function (c, e, p) {
                    requestInfo.callbacks = { c: c, e: e, start: Date.now() };
                }, function () {
                    _this.tryCancelRequest(request.seq);
                });
            }
            requestInfo.promise = result;
            this.requestQueue.push(requestInfo);
            this.sendNextRequests();
            return result;
        };
        TypeScriptServiceClient.prototype.sendNextRequests = function () {
            while (this.pendingResponses === 0 && this.requestQueue.length > 0) {
                this.sendRequest(this.requestQueue.shift());
            }
        };
        TypeScriptServiceClient.prototype.sendRequest = function (requestItem) {
            var _this = this;
            var serverRequest = requestItem.request;
            if (TypeScriptServiceClient.Trace) {
                console.log('TypeScript Service: sending request ' + serverRequest.command + '(' + serverRequest.seq + '). Response expected: ' + (requestItem.callbacks ? 'yes' : 'no') + '. Current queue length: ' + this.requestQueue.length);
            }
            if (requestItem.callbacks) {
                this.callbacks[serverRequest.seq] = requestItem.callbacks;
                this.pendingResponses++;
            }
            this.service().done(function (childProcess) {
                childProcess.stdin.write(JSON.stringify(serverRequest) + '\r\n', 'utf8');
            }, function (err) {
                var callback = _this.callbacks[serverRequest.seq];
                if (callback) {
                    callback.e(err);
                    delete _this.callbacks[serverRequest.seq];
                    _this.pendingResponses--;
                }
            });
        };
        TypeScriptServiceClient.prototype.tryCancelRequest = function (seq) {
            for (var i = 0; i < this.requestQueue.length; i++) {
                if (this.requestQueue[i].request.seq === seq) {
                    this.requestQueue.splice(i, 1);
                    if (TypeScriptServiceClient.Trace) {
                        console.log('TypeScript Service: canceled request with sequence number ' + seq);
                    }
                    return true;
                }
            }
            if (TypeScriptServiceClient.Trace) {
                console.log('TypeScript Service: tried to cancel request with sequence number ' + seq + '. But request got already delivered.');
            }
            return false;
        };
        TypeScriptServiceClient.prototype.dispatchMessage = function (message) {
            try {
                if (message.type === 'response') {
                    var response = message;
                    var p = this.callbacks[response.request_seq];
                    if (p) {
                        if (TypeScriptServiceClient.Trace) {
                            console.log('TypeScript Service: request ' + response.command + '(' + response.request_seq + ') took ' + (Date.now() - p.start) + 'ms. Success: ' + response.success);
                        }
                        delete this.callbacks[response.request_seq];
                        this.pendingResponses--;
                        if (response.success) {
                            p.c(response);
                        }
                        else {
                            p.e(response);
                        }
                    }
                }
                else if (message.type === 'event') {
                    var event = message;
                    if (event.event === 'syntaxDiag') {
                        this.host.syntaxDiagnosticsReceived(event);
                    }
                    if (event.event === 'semanticDiag') {
                        this.host.semanticDiagnosticsReceived(event);
                    }
                }
                else {
                    throw new Error('Unknown message type ' + message.type + ' recevied');
                }
            }
            finally {
                this.sendNextRequests();
            }
        };
        TypeScriptServiceClient.Trace = false;
        return TypeScriptServiceClient;
    })();
    return TypeScriptServiceClient;
});
