/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    }
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { IntervalTimer } from '../../../base/common/async';
import { Disposable, dispose, toDisposable } from '../../../base/common/lifecycle';
import { SimpleWorkerClient, logOnceWebWorkerWarning } from '../../../base/common/worker/simpleWorker';
import { DefaultWorkerFactory } from '../../../base/worker/defaultWorkerFactory';
import * as modes from '../modes';
import { LanguageConfigurationRegistry } from '../modes/languageConfigurationRegistry';
import { EditorSimpleWorkerImpl } from './editorSimpleWorker';
import { IModelService } from './modelService';
import { ITextResourceConfigurationService } from './resourceConfiguration';
/**
 * Stop syncing a model to the worker if it was not needed for 1 min.
 */
var STOP_SYNC_MODEL_DELTA_TIME_MS = 60 * 1000;
/**
 * Stop the worker if it was not needed for 5 min.
 */
var STOP_WORKER_DELTA_TIME_MS = 5 * 60 * 1000;
function canSyncModel(modelService, resource) {
    var model = modelService.getModel(resource);
    if (!model) {
        return false;
    }
    if (model.isTooLargeForSyncing()) {
        return false;
    }
    return true;
}
var EditorWorkerServiceImpl = /** @class */ (function (_super) {
    __extends(EditorWorkerServiceImpl, _super);
    function EditorWorkerServiceImpl(modelService, configurationService) {
        var _this = _super.call(this) || this;
        _this._modelService = modelService;
        _this._workerManager = _this._register(new WorkerManager(_this._modelService));
        // todo@joh make sure this happens only once
        _this._register(modes.LinkProviderRegistry.register('*', {
            provideLinks: function (model, token) {
                if (!canSyncModel(_this._modelService, model.uri)) {
                    return Promise.resolve([]); // File too large
                }
                return _this._workerManager.withWorker().then(function (client) { return client.computeLinks(model.uri); });
            }
        }));
        _this._register(modes.CompletionProviderRegistry.register('*', new WordBasedCompletionItemProvider(_this._workerManager, configurationService, _this._modelService)));
        return _this;
    }
    EditorWorkerServiceImpl.prototype.dispose = function () {
        _super.prototype.dispose.call(this);
    };
    EditorWorkerServiceImpl.prototype.canComputeDiff = function (original, modified) {
        return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified));
    };
    EditorWorkerServiceImpl.prototype.computeDiff = function (original, modified, ignoreTrimWhitespace) {
        return this._workerManager.withWorker().then(function (client) { return client.computeDiff(original, modified, ignoreTrimWhitespace); });
    };
    EditorWorkerServiceImpl.prototype.computeMoreMinimalEdits = function (resource, edits) {
        if (!Array.isArray(edits) || edits.length === 0) {
            return Promise.resolve(edits);
        }
        else {
            if (!canSyncModel(this._modelService, resource)) {
                return Promise.resolve(edits); // File too large
            }
            return this._workerManager.withWorker().then(function (client) { return client.computeMoreMinimalEdits(resource, edits); });
        }
    };
    EditorWorkerServiceImpl.prototype.canNavigateValueSet = function (resource) {
        return (canSyncModel(this._modelService, resource));
    };
    EditorWorkerServiceImpl.prototype.navigateValueSet = function (resource, range, up) {
        return this._workerManager.withWorker().then(function (client) { return client.navigateValueSet(resource, range, up); });
    };
    EditorWorkerServiceImpl.prototype.canComputeWordRanges = function (resource) {
        return canSyncModel(this._modelService, resource);
    };
    EditorWorkerServiceImpl.prototype.computeWordRanges = function (resource, range) {
        return this._workerManager.withWorker().then(function (client) { return client.computeWordRanges(resource, range); });
    };
    EditorWorkerServiceImpl = __decorate([
        __param(0, IModelService),
        __param(1, ITextResourceConfigurationService)
    ], EditorWorkerServiceImpl);
    return EditorWorkerServiceImpl;
}(Disposable));
export { EditorWorkerServiceImpl };
var WordBasedCompletionItemProvider = /** @class */ (function () {
    function WordBasedCompletionItemProvider(workerManager, configurationService, modelService) {
        this._workerManager = workerManager;
        this._configurationService = configurationService;
        this._modelService = modelService;
    }
    WordBasedCompletionItemProvider.prototype.provideCompletionItems = function (model, position) {
        var wordBasedSuggestions = this._configurationService.getValue(model.uri, position, 'editor').wordBasedSuggestions;
        if (!wordBasedSuggestions) {
            return undefined;
        }
        if (!canSyncModel(this._modelService, model.uri)) {
            return undefined; // File too large
        }
        return this._workerManager.withWorker().then(function (client) { return client.textualSuggest(model.uri, position); });
    };
    return WordBasedCompletionItemProvider;
}());
var WorkerManager = /** @class */ (function (_super) {
    __extends(WorkerManager, _super);
    function WorkerManager(modelService) {
        var _this = _super.call(this) || this;
        _this._modelService = modelService;
        _this._editorWorkerClient = null;
        var stopWorkerInterval = _this._register(new IntervalTimer());
        stopWorkerInterval.cancelAndSet(function () { return _this._checkStopIdleWorker(); }, Math.round(STOP_WORKER_DELTA_TIME_MS / 2));
        _this._register(_this._modelService.onModelRemoved(function (_) { return _this._checkStopEmptyWorker(); }));
        return _this;
    }
    WorkerManager.prototype.dispose = function () {
        if (this._editorWorkerClient) {
            this._editorWorkerClient.dispose();
            this._editorWorkerClient = null;
        }
        _super.prototype.dispose.call(this);
    };
    /**
     * Check if the model service has no more models and stop the worker if that is the case.
     */
    WorkerManager.prototype._checkStopEmptyWorker = function () {
        if (!this._editorWorkerClient) {
            return;
        }
        var models = this._modelService.getModels();
        if (models.length === 0) {
            // There are no more models => nothing possible for me to do
            this._editorWorkerClient.dispose();
            this._editorWorkerClient = null;
        }
    };
    /**
     * Check if the worker has been idle for a while and then stop it.
     */
    WorkerManager.prototype._checkStopIdleWorker = function () {
        if (!this._editorWorkerClient) {
            return;
        }
        var timeSinceLastWorkerUsedTime = (new Date()).getTime() - this._lastWorkerUsedTime;
        if (timeSinceLastWorkerUsedTime > STOP_WORKER_DELTA_TIME_MS) {
            this._editorWorkerClient.dispose();
            this._editorWorkerClient = null;
        }
    };
    WorkerManager.prototype.withWorker = function () {
        this._lastWorkerUsedTime = (new Date()).getTime();
        if (!this._editorWorkerClient) {
            this._editorWorkerClient = new EditorWorkerClient(this._modelService, 'editorWorkerService');
        }
        return Promise.resolve(this._editorWorkerClient);
    };
    return WorkerManager;
}(Disposable));
var EditorModelManager = /** @class */ (function (_super) {
    __extends(EditorModelManager, _super);
    function EditorModelManager(proxy, modelService, keepIdleModels) {
        var _this = _super.call(this) || this;
        _this._syncedModels = Object.create(null);
        _this._syncedModelsLastUsedTime = Object.create(null);
        _this._proxy = proxy;
        _this._modelService = modelService;
        if (!keepIdleModels) {
            var timer = new IntervalTimer();
            timer.cancelAndSet(function () { return _this._checkStopModelSync(); }, Math.round(STOP_SYNC_MODEL_DELTA_TIME_MS / 2));
            _this._register(timer);
        }
        return _this;
    }
    EditorModelManager.prototype.dispose = function () {
        for (var modelUrl in this._syncedModels) {
            dispose(this._syncedModels[modelUrl]);
        }
        this._syncedModels = Object.create(null);
        this._syncedModelsLastUsedTime = Object.create(null);
        _super.prototype.dispose.call(this);
    };
    EditorModelManager.prototype.esureSyncedResources = function (resources) {
        for (var i = 0; i < resources.length; i++) {
            var resource = resources[i];
            var resourceStr = resource.toString();
            if (!this._syncedModels[resourceStr]) {
                this._beginModelSync(resource);
            }
            if (this._syncedModels[resourceStr]) {
                this._syncedModelsLastUsedTime[resourceStr] = (new Date()).getTime();
            }
        }
    };
    EditorModelManager.prototype._checkStopModelSync = function () {
        var currentTime = (new Date()).getTime();
        var toRemove = [];
        for (var modelUrl in this._syncedModelsLastUsedTime) {
            var elapsedTime = currentTime - this._syncedModelsLastUsedTime[modelUrl];
            if (elapsedTime > STOP_SYNC_MODEL_DELTA_TIME_MS) {
                toRemove.push(modelUrl);
            }
        }
        for (var i = 0; i < toRemove.length; i++) {
            this._stopModelSync(toRemove[i]);
        }
    };
    EditorModelManager.prototype._beginModelSync = function (resource) {
        var _this = this;
        var model = this._modelService.getModel(resource);
        if (!model) {
            return;
        }
        if (model.isTooLargeForSyncing()) {
            return;
        }
        var modelUrl = resource.toString();
        this._proxy.acceptNewModel({
            url: model.uri.toString(),
            lines: model.getLinesContent(),
            EOL: model.getEOL(),
            versionId: model.getVersionId()
        });
        var toDispose = [];
        toDispose.push(model.onDidChangeContent(function (e) {
            _this._proxy.acceptModelChanged(modelUrl.toString(), e);
        }));
        toDispose.push(model.onWillDispose(function () {
            _this._stopModelSync(modelUrl);
        }));
        toDispose.push(toDisposable(function () {
            _this._proxy.acceptRemovedModel(modelUrl);
        }));
        this._syncedModels[modelUrl] = toDispose;
    };
    EditorModelManager.prototype._stopModelSync = function (modelUrl) {
        var toDispose = this._syncedModels[modelUrl];
        delete this._syncedModels[modelUrl];
        delete this._syncedModelsLastUsedTime[modelUrl];
        dispose(toDispose);
    };
    return EditorModelManager;
}(Disposable));
var SynchronousWorkerClient = /** @class */ (function () {
    function SynchronousWorkerClient(instance) {
        this._instance = instance;
        this._proxyObj = Promise.resolve(this._instance);
    }
    SynchronousWorkerClient.prototype.dispose = function () {
        this._instance.dispose();
    };
    SynchronousWorkerClient.prototype.getProxyObject = function () {
        return this._proxyObj;
    };
    return SynchronousWorkerClient;
}());
var EditorWorkerClient = /** @class */ (function (_super) {
    __extends(EditorWorkerClient, _super);
    function EditorWorkerClient(modelService, label) {
        var _this = _super.call(this) || this;
        _this._modelService = modelService;
        _this._workerFactory = new DefaultWorkerFactory(label);
        _this._worker = null;
        _this._modelManager = null;
        return _this;
    }
    EditorWorkerClient.prototype._getOrCreateWorker = function () {
        if (!this._worker) {
            try {
                this._worker = this._register(new SimpleWorkerClient(this._workerFactory, 'vs/editor/common/services/editorSimpleWorker'));
            }
            catch (err) {
                logOnceWebWorkerWarning(err);
                this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl(null));
            }
        }
        return this._worker;
    };
    EditorWorkerClient.prototype._getProxy = function () {
        var _this = this;
        return this._getOrCreateWorker().getProxyObject().then(null, function (err) {
            logOnceWebWorkerWarning(err);
            _this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl(null));
            return _this._getOrCreateWorker().getProxyObject();
        });
    };
    EditorWorkerClient.prototype._getOrCreateModelManager = function (proxy) {
        if (!this._modelManager) {
            this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, false));
        }
        return this._modelManager;
    };
    EditorWorkerClient.prototype._withSyncedResources = function (resources) {
        var _this = this;
        return this._getProxy().then(function (proxy) {
            _this._getOrCreateModelManager(proxy).esureSyncedResources(resources);
            return proxy;
        });
    };
    EditorWorkerClient.prototype.computeDiff = function (original, modified, ignoreTrimWhitespace) {
        return this._withSyncedResources([original, modified]).then(function (proxy) {
            return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace);
        });
    };
    EditorWorkerClient.prototype.computeMoreMinimalEdits = function (resource, edits) {
        return this._withSyncedResources([resource]).then(function (proxy) {
            return proxy.computeMoreMinimalEdits(resource.toString(), edits);
        });
    };
    EditorWorkerClient.prototype.computeLinks = function (resource) {
        return this._withSyncedResources([resource]).then(function (proxy) {
            return proxy.computeLinks(resource.toString());
        });
    };
    EditorWorkerClient.prototype.textualSuggest = function (resource, position) {
        var _this = this;
        return this._withSyncedResources([resource]).then(function (proxy) {
            var model = _this._modelService.getModel(resource);
            if (!model) {
                return null;
            }
            var wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
            var wordDef = wordDefRegExp.source;
            var wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : '');
            return proxy.textualSuggest(resource.toString(), position, wordDef, wordDefFlags);
        });
    };
    EditorWorkerClient.prototype.computeWordRanges = function (resource, range) {
        var _this = this;
        return this._withSyncedResources([resource]).then(function (proxy) {
            var model = _this._modelService.getModel(resource);
            if (!model) {
                return Promise.resolve(null);
            }
            var wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
            var wordDef = wordDefRegExp.source;
            var wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : '');
            return proxy.computeWordRanges(resource.toString(), range, wordDef, wordDefFlags);
        });
    };
    EditorWorkerClient.prototype.navigateValueSet = function (resource, range, up) {
        var _this = this;
        return this._withSyncedResources([resource]).then(function (proxy) {
            var model = _this._modelService.getModel(resource);
            if (!model) {
                return null;
            }
            var wordDefRegExp = LanguageConfigurationRegistry.getWordDefinition(model.getLanguageIdentifier().id);
            var wordDef = wordDefRegExp.source;
            var wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : '');
            return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags);
        });
    };
    return EditorWorkerClient;
}(Disposable));
export { EditorWorkerClient };
