/*
 * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/**
 * EditorManager owns the UI for the editor area. This essentially mirrors the 'current document'
 * property maintained by DocumentManager's model.
 *
 * Note that there is a little bit of unusual overlap between EditorManager and DocumentManager:
 * because the Document state is actually stored in the CodeMirror editor UI, DocumentManager is
 * not a pure headless model. Each Document encapsulates an editor instance, and thus EditorManager
 * must have some knowledge about Document's internal state (we access its _editor property).
 *
 * This module dispatches the following events:
 *    - activeEditorChange --  Fires after the active editor (full or inline).
 *
 *      Doesn't fire when editor temporarily loses focus to a non-editor
 *      control (e.g. search toolbar or modal dialog, or window deactivation).
 *
 *      Does fire when focus moves between inline editor and its full-size container.
 *
 *      This event tracks `MainViewManagers's `currentFileChange` event and all editor
 *      objects "focus" event.
 *
 *          (e, editorGainingFocus:editor, editorLosingFocus:editor)
 *
 *      The 2nd arg to the listener is which Editor became active; the 3rd arg is
 *      which Editor is deactivated as a result. Either one may be null.
 *      NOTE (#1257): `getFocusedEditor()` sometimes lags behind this event. Listeners
 *      should use the arguments or call `getActiveEditor()` to reliably see which Editor
 *      just gained focus.
 */
define(function (require, exports, module) {
    "use strict";

    // Load dependent modules
    var Commands            = require("command/Commands"),
        EventDispatcher     = require("utils/EventDispatcher"),
        WorkspaceManager    = require("view/WorkspaceManager"),
        PreferencesManager  = require("preferences/PreferencesManager"),
        CommandManager      = require("command/CommandManager"),
        DocumentManager     = require("document/DocumentManager"),
        MainViewManager     = require("view/MainViewManager"),
        ViewStateManager    = require("view/ViewStateManager"),
        PerfUtils           = require("utils/PerfUtils"),
        Editor              = require("editor/Editor").Editor,
        InlineTextEditor    = require("editor/InlineTextEditor").InlineTextEditor,
        Strings             = require("strings"),
        LanguageManager     = require("language/LanguageManager"),
        DeprecationWarning  = require("utils/DeprecationWarning");


    /**
     * Currently focused Editor (full-size, inline, or otherwise)
     * @type {?Editor}
     * @private
     */
    var _lastFocusedEditor = null;

    /**
     * Registered inline-editor widget providers sorted descending by priority.
     * @see {@link #registerInlineEditProvider}.
     * @type {Array.<{priority:number, provider:function(...)}>}
     * @private
     */
    var _inlineEditProviders = [];

    /**
     * Registered inline documentation widget providers sorted descending by priority.
     * @see {@link #registerInlineDocsProvider}.
     * @type {Array.<{priority:number, provider:function(...)}>}
     * @private
     */
    var _inlineDocsProviders = [];

    /**
     * DOM element to house any hidden editors created soley for inline widgets
     * @private
     * @type {jQuery}
     */
    var _$hiddenEditorsContainer;


    /**
     * Retrieves the visible full-size Editor for the currently opened file in the ACTIVE_PANE
     * @return {?Editor} editor of the current view or null
     */
    function getCurrentFullEditor() {
        var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE),
            doc = currentPath && DocumentManager.getOpenDocumentForPath(currentPath);
        return doc && doc._masterEditor;
    }



    /**
     * Updates _viewStateCache from the given editor's actual current state
     * @private
     * @param {!Editor} editor - editor to cache data for
     */
    function _saveEditorViewState(editor) {
        ViewStateManager.updateViewState(editor);
    }

    /**
     * Updates _viewStateCache from the given editor's actual current state
     * @param {!Editor} editor - editor restore cached data
     * @private
     */
    function _restoreEditorViewState(editor) {
        // We want to ignore the current state of the editor, so don't call __getViewState()
        var viewState = ViewStateManager.getViewState(editor.document.file);
        if (viewState) {
            editor.restoreViewState(viewState);
        }
    }


	/**
     * Editor focus handler to change the currently active editor
     * @private
     * @param {?Editor} current - the editor that will be the active editor
     */
    function _notifyActiveEditorChanged(current) {
        // Skip if the Editor that gained focus was already the most recently focused editor.
        // This may happen e.g. if the window loses then regains focus.
        if (_lastFocusedEditor === current) {
            return;
        }
        var previous = _lastFocusedEditor;
        _lastFocusedEditor = current;

        exports.trigger("activeEditorChange", current, previous);
    }

    /**
     * Current File Changed handler
     * MainViewManager dispatches a "currentFileChange" event whenever the currently viewed
     * file changes.  Which could mean that the previously viewed file has been closed or a
     * non-editor view (image) has been given focus.  _notifyAcitveEditorChanged is also hooked
     * up to editor.focus to handle focus events for editors which handles changing focus between
     * two editors but, because editormanager maintains  a "_lastFocusedEditor" state, we have to
     * "nullify" that state whenever the focus goes to a non-editor or when the current editor is closed
     * @private
     * @param {!jQuery.Event} e - event
     * @param {?File} file - current file (can be null)
     */
    function _handleCurrentFileChange(e, file) {
        var doc = file && DocumentManager.getOpenDocumentForPath(file.fullPath);
        _notifyActiveEditorChanged(doc && doc._masterEditor);
    }

    /**
     * Creates a new Editor bound to the given Document.
     * The editor is appended to the given container as a visible child.
     * @private
     * @param {!Document} doc  Document for the Editor's content
     * @param {!boolean} makeMasterEditor  If true, the Editor will set itself as the private "master"
     *          Editor for the Document. If false, the Editor will attach to the Document as a "slave."
     * @param {!jQueryObject} container  Container to add the editor to.
     * @param {{startLine: number, endLine: number}=} range If specified, range of lines within the document
     *          to display in this editor. Inclusive.
     * @param {!Object} editorOptions If specified, contains editor options that can be passed to CodeMirror
     * @return {Editor} the newly created editor.
     */
    function _createEditorForDocument(doc, makeMasterEditor, container, range, editorOptions) {
        var editor = new Editor(doc, makeMasterEditor, container, range, editorOptions);

        editor.on("focus", function () {
            _notifyActiveEditorChanged(editor);
        });

        editor.on("beforeDestroy", function () {
            if (editor.$el.is(":visible")) {
                _saveEditorViewState(editor);
            }
        });

        return editor;
    }

    /**
     * @private
     * Finds an inline widget provider from the given list that can offer a widget for the current cursor
     * position, and once the widget has been created inserts it into the editor.
     *
     * @param {!Editor} editor The host editor
     * @param {Array.<{priority:number, provider:function(...)}>} providers
     *      prioritized list of providers
     * @param {string=} defaultErrorMsg Default message to display if no providers return non-null
     * @return {$.Promise} a promise that will be resolved when an InlineWidget
     *      is created or rejected if no inline providers have offered one.
     */
    function _openInlineWidget(editor, providers, defaultErrorMsg) {
        PerfUtils.markStart(PerfUtils.INLINE_WIDGET_OPEN);

        // Run through inline-editor providers until one responds
        var pos = editor.getCursorPos(),
            inlinePromise,
            i,
            result = new $.Deferred(),
            errorMsg,
            providerRet;

        // Query each provider in priority order. Provider may return:
        // 1. `null` to indicate it does not apply to current cursor position
        // 2. promise that should resolve to an InlineWidget
        // 3. string which indicates provider does apply to current cursor position,
        //    but reason it could not create InlineWidget
        //
        // Keep looping until a provider is found. If a provider is not found,
        // display the highest priority error message that was found, otherwise display
        // default error message
        for (i = 0; i < providers.length && !inlinePromise; i++) {
            var provider = providers[i].provider;
            providerRet = provider(editor, pos);
            if (providerRet) {
                if (providerRet.hasOwnProperty("done")) {
                    inlinePromise = providerRet;
                } else if (!errorMsg && typeof (providerRet) === "string") {
                    errorMsg = providerRet;
                }
            }
        }

        // Use default error message if none other provided
        errorMsg = errorMsg || defaultErrorMsg;

        // If one of them will provide a widget, show it inline once ready
        if (inlinePromise) {
            inlinePromise.done(function (inlineWidget) {
                editor.addInlineWidget(pos, inlineWidget).done(function () {
                    PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
                    result.resolve();
                });
            }).fail(function () {
                // terminate timer that was started above
                PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
                editor.displayErrorMessageAtCursor(errorMsg);
                result.reject();
            });
        } else {
            // terminate timer that was started above
            PerfUtils.finalizeMeasurement(PerfUtils.INLINE_WIDGET_OPEN);
            editor.displayErrorMessageAtCursor(errorMsg);
            result.reject();
        }

        return result.promise();
    }


    /**
     * Closes any focused inline widget. Else, asynchronously asks providers to create one.
     *
     * @param {Array.<{priority:number, provider:function(...)}>} providers
     *   prioritized list of providers
     * @param {string=} errorMsg Default message to display if no providers return non-null
     * @return {!Promise} A promise resolved with true if an inline widget is opened or false
     *   when closed. Rejected if there is neither an existing widget to close nor a provider
     *   willing to create a widget (or if no editor is open).
     */
    function _toggleInlineWidget(providers, errorMsg) {
        var result = new $.Deferred();

        var currentEditor = getCurrentFullEditor();

        if (currentEditor) {
            var inlineWidget = currentEditor.getFocusedInlineWidget();

            if (inlineWidget) {
                // an inline widget's editor has focus, so close it
                PerfUtils.markStart(PerfUtils.INLINE_WIDGET_CLOSE);
                inlineWidget.close().done(function () {
                    PerfUtils.addMeasurement(PerfUtils.INLINE_WIDGET_CLOSE);
                    // return a resolved promise to CommandManager
                    result.resolve(false);
                });
            } else {
                // main editor has focus, so create an inline editor
                _openInlineWidget(currentEditor, providers, errorMsg).done(function () {
                    result.resolve(true);
                }).fail(function () {
                    result.reject();
                });
            }
        } else {
            // Can not open an inline editor without a host editor
            result.reject();
        }

        return result.promise();
    }

    /**
     * Inserts a prioritized provider object into the array in sorted (descending) order.
     * @private
     * @param {Array.<{priority:number, provider:function(...)}>} array
     * @param {number} priority
     * @param {function(...)} provider
     */
    function _insertProviderSorted(array, provider, priority) {
        var index,
            prioritizedProvider = {
                priority: priority,
                provider: provider
            };

        for (index = 0; index < array.length; index++) {
            if (array[index].priority < priority) {
                break;
            }
        }

        array.splice(index, 0, prioritizedProvider);
    }


    /**
     * Creates a hidden, unattached master editor that is needed when a document is created for the
     * sole purpose of creating an inline editor so operations that require a master editor can be performed
     * Only called from Document._ensureMasterEditor()
     * The editor view is placed in a hidden part of the DOM but can later be moved to a visible pane
     * when the document is opened using pane.addView()
     * @param {!Document} doc - document to create a hidden editor for
     */
    function _createUnattachedMasterEditor(doc) {
        // attach to the hidden containers DOM node if necessary
        if (!_$hiddenEditorsContainer) {
            _$hiddenEditorsContainer = $("#hidden-editors");
        }
        // Create an editor
        var editor = _createEditorForDocument(doc, true, _$hiddenEditorsContainer);
        // and hide it
        editor.setVisible(false);
    }

    /**
     * Removes the given widget UI from the given hostEditor (agnostic of what the widget's content
     * is). The widget's onClosed() callback will be run as a result.
     * @param {!Editor} hostEditor The editor containing the widget.
     * @param {!InlineWidget} inlineWidget The inline widget to close.
     * @return {$.Promise} A promise that's resolved when the widget is fully closed.
     */
    function closeInlineWidget(hostEditor, inlineWidget) {
        // If widget has focus, return it to the hostEditor & move the cursor to where the inline used to be
        if (inlineWidget.hasFocus()) {
            // Place cursor back on the line just above the inline (the line from which it was opened)
            // If cursor's already on that line, leave it be to preserve column position
            var widgetLine = hostEditor._codeMirror.getLineNumber(inlineWidget.info.line);
            var cursorLine = hostEditor.getCursorPos().line;
            if (cursorLine !== widgetLine) {
                hostEditor.setCursorPos({ line: widgetLine, pos: 0 });
            }

            hostEditor.focus();
        }

        return hostEditor.removeInlineWidget(inlineWidget);
    }

    /**
     * Registers a new inline editor provider. When Quick Edit is invoked each registered provider is
     * asked if it wants to provide an inline editor given the current editor and cursor location.
     * An optional priority parameter is used to give providers with higher priority an opportunity
     * to provide an inline editor before providers with lower priority.
     *
     * @param {function(!Editor, !{line:number, ch:number}):?($.Promise|string)} provider
     * @param {number=} priority
     * The provider returns a promise that will be resolved with an InlineWidget, or returns a string
     * indicating why the provider cannot respond to this case (or returns null to indicate no reason).
     */
    function registerInlineEditProvider(provider, priority) {
        if (priority === undefined) {
            priority = 0;
        }
        _insertProviderSorted(_inlineEditProviders, provider, priority);
    }

    /**
     * Registers a new inline docs provider. When Quick Docs is invoked each registered provider is
     * asked if it wants to provide inline docs given the current editor and cursor location.
     * An optional priority parameter is used to give providers with higher priority an opportunity
     * to provide an inline editor before providers with lower priority.
     *
     * @param {function(!Editor, !{line:number, ch:number}):?($.Promise|string)} provider
     * @param {number=} priority
     * The provider returns a promise that will be resolved with an InlineWidget, or returns a string
     * indicating why the provider cannot respond to this case (or returns null to indicate no reason).
     */
    function registerInlineDocsProvider(provider, priority) {
        if (priority === undefined) {
            priority = 0;
        }
        _insertProviderSorted(_inlineDocsProviders, provider, priority);
    }


    /**
     * @private
     * Given a host editor, return a list of all Editors in all its open inline widgets. (Ignoring
     * any other inline widgets that might be open but don't contain Editors).
     * @param {!Editor} hostEditor
     * @return {Array.<Editor>}
     *
     */
    function getInlineEditors(hostEditor) {
        var inlineEditors = [];

        if (hostEditor) {
            hostEditor.getInlineWidgets().forEach(function (widget) {
                if (widget instanceof InlineTextEditor && widget.editor) {
                    inlineEditors.push(widget.editor);
                }
            });
        }

        return inlineEditors;
    }



    /**
     * @private
     * Creates a new "full-size" (not inline) Editor for the given Document, and sets it as the
     * Document's master backing editor. The editor is not yet visible;
     * Semi-private: should only be called within this module or by Document.
     * @param {!Document} document  Document whose main/full Editor to create
     * @param {!Pane} pane  Pane in which the editor will be hosted
     * @param {!Object} editorOptions If specified, contains editor options that
     * can be passed to CodeMirror
     * @return {!Editor}
     */
    function _createFullEditorForDocument(document, pane, editorOptions) {
        // Create editor; make it initially invisible
        var editor = _createEditorForDocument(document, true, pane.$content, undefined, editorOptions);
        editor.setVisible(false);
        pane.addView(editor);
        exports.trigger("_fullEditorCreatedForDocument", document, editor, pane.id);
        return editor;
    }


    /**
     * Creates a new inline Editor instance for the given Document.
     * The editor is not yet visible or attached to a host editor.
     * @param {!Document} doc  Document for the Editor's content
     * @param {?{startLine:Number, endLine:Number}} range  If specified, all lines outside the given
     *      range are hidden from the editor. Range is inclusive. Line numbers start at 0.
     * @param {HTMLDivContainer} inlineContent
     * @param  {function(inlineWidget)} closeThisInline
     *
     * @return {{content:DOMElement, editor:Editor}}
     */
    function createInlineEditorForDocument(doc, range, inlineContent) {
        // Hide the container for the editor before creating it so that CodeMirror doesn't do extra work
        // when initializing the document. When we construct the editor, we have to set its text and then
        // set the (small) visible range that we show in the editor. If the editor is visible, CM has to
        // render a large portion of the document before setting the visible range. By hiding the editor
        // first and showing it after the visible range is set, we avoid that initial render.
        $(inlineContent).hide();
        var inlineEditor = _createEditorForDocument(doc, false, inlineContent, range);
        inlineEditor._hostEditor = getCurrentFullEditor();
        $(inlineContent).show();

        return { content: inlineContent, editor: inlineEditor };
    }

    /**
     * Returns focus to the last visible editor that had focus. If no editor visible, does nothing.
     * This function should be called to restore editor focus after it has been temporarily
     * removed. For example, after a dialog with editable text is closed.
     */
    function focusEditor() {
        DeprecationWarning.deprecationWarning("Use MainViewManager.focusActivePane() instead of EditorManager.focusEditor().", true);
        MainViewManager.focusActivePane();
    }

    /**
     * @deprecated
     * resizes the editor
     */
    function resizeEditor() {
        DeprecationWarning.deprecationWarning("Use WorkspaceManager.recomputeLayout() instead of EditorManager.resizeEditor().", true);
        WorkspaceManager.recomputeLayout();
    }

    /**
     * Create and/or show the editor for the specified document
     * @param {!Document} document - document to edit
     * @param {!Pane} pane - pane to show it in
     * @param {!Object} editorOptions - If specified, contains
     * editor options that can be passed to CodeMirror
     * @private
     */
    function _showEditor(document, pane, editorOptions) {
        // Ensure a main editor exists for this document to show in the UI
        var createdNewEditor = false,
            editor = document._masterEditor;

        // Check if a master editor is not set already or the current master editor doesn't belong
        // to the pane container requested - to support creation of multiple full editors
        // This check is required as _masterEditor is the active full editor for the document
        // and there can be existing full editor created for other panes
        if (editor && editor._paneId && editor._paneId !== pane.id) {
            editor = document._checkAssociatedEditorForPane(pane.id);
        }

        if (!editor) {
            // Performance (see #4757) Chrome wastes time messing with selection
            // that will just be changed at end, so clear it for now
            if (window.getSelection && window.getSelection().empty) {  // Chrome
                window.getSelection().empty();
            }

            // Editor doesn't exist: populate a new Editor with the text
            editor = _createFullEditorForDocument(document, pane, editorOptions);
            createdNewEditor = true;
        } else if (editor.$el.parent()[0] !== pane.$content[0]) {
            // editor does exist but is not a child of the pane so add it to the
            //  pane (which will switch the view's container as well)
            pane.addView(editor);
        }

        // show the view
        pane.showView(editor);

        if (MainViewManager.getActivePaneId() === pane.id) {
            // give it focus
            editor.focus();
        }

        if (createdNewEditor) {
            _restoreEditorViewState(editor);
        }
    }

    /**
     * @deprecated use MainViewManager.getCurrentlyViewedFile() instead
     * @return {string=} path of the file currently viewed in the active, full sized editor or null when there is no active editor
     */
    function getCurrentlyViewedPath() {
        DeprecationWarning.deprecationWarning("Use MainViewManager.getCurrentlyViewedFile() instead of EditorManager.getCurrentlyViewedPath().", true);

        // We only want to return a path of a document object
        // not other things like images, etc...
        var currentPath = MainViewManager.getCurrentlyViewedPath(MainViewManager.ACTIVE_PANE),
            doc;

        if (currentPath) {
            doc = DocumentManager.getOpenDocumentForPath(currentPath);
        }

        if (doc) {
            return currentPath;
        }

        return null;
    }

    /**
     * @deprecated There is no equivalent API moving forward.
     * Use MainViewManager._initialize() from a unit test to create a Main View attached to a specific DOM element
     */
    function setEditorHolder() {
        throw new Error("EditorManager.setEditorHolder() has been removed.");
    }

    /**
     * @deprecated Register a View Factory instead
     * @see MainViewFactory::#registerViewFactory
     */
    function registerCustomViewer() {
        throw new Error("EditorManager.registerCustomViewer() has been removed.");
    }

    /**
     * Determines if the file can be opened in an editor
     * @param {!string} fullPath - file to be opened
     * @return {boolean} true if the file can be opened in an editor, false if not
     */
    function canOpenPath(fullPath) {
        return !LanguageManager.getLanguageForPath(fullPath).isBinary();
    }

    /**
     * Opens the specified document in the given pane
     * @param {!Document} doc - the document to open
     * @param {!Pane} pane - the pane to open the document in
     * @param {!Object} editorOptions - If specified, contains
     * editor options that can be passed to CodeMirror
     * @return {boolean} true if the file can be opened, false if not
     */
    function openDocument(doc, pane, editorOptions) {
        var perfTimerName = PerfUtils.markStart("EditorManager.openDocument():\t" + (!doc || doc.file.fullPath));

        if (doc && pane) {
            _showEditor(doc, pane, editorOptions);
        }

        PerfUtils.addMeasurement(perfTimerName);
    }

    /**
     * Returns the currently focused inline widget, if any.
     * @return {?InlineWidget}
     */
    function getFocusedInlineWidget() {
        var currentEditor = getCurrentFullEditor();
        if (currentEditor) {
            return currentEditor.getFocusedInlineWidget();
        }
        return null;
    }

    /**
     * Returns the focused Editor within an inline text editor, or null if something else has focus
     * @return {?Editor}
     */
    function _getFocusedInlineEditor() {
        var focusedWidget = getFocusedInlineWidget();
        if (focusedWidget instanceof InlineTextEditor) {
            return focusedWidget.getFocusedEditor();
        }
        return null;
    }

    /**
     * Returns the currently focused editor instance (full-sized OR inline editor).
     * This function is similar to getActiveEditor(), with one main difference: this
     * function will only return editors that currently have focus, whereas
     * getActiveEditor() will return the last visible editor that was given focus (but
     * may not currently have focus because, for example, a dialog with editable text
     * is open).
     * @return {?Editor}
     */
    function getFocusedEditor() {
        var currentEditor = getCurrentFullEditor();
        if (currentEditor) {

            // See if any inlines have focus
            var focusedInline = _getFocusedInlineEditor();
            if (focusedInline) {
                return focusedInline;
            }

            // otherwise, see if full-sized editor has focus
            if (currentEditor.hasFocus()) {
                return currentEditor;
            }
        }

        return null;
    }

    /**
     * Returns the current active editor (full-sized OR inline editor). This editor may not
     * have focus at the moment, but it is visible and was the last editor that was given
     * focus. Returns null if no editors are active.
     * @see #getFocusedEditor
     * @return {?Editor}
     */
    function getActiveEditor() {
        return _lastFocusedEditor;
    }

    /**
     * file removed from pane handler.
     * @param {jQuery.Event} e
     * @param {File|Array.<File>} removedFiles - file, path or array of files or paths that are being removed
     */
    function _handleRemoveFromPaneView(e, removedFiles) {
        var handleFileRemoved = function (file) {
            var doc = DocumentManager.getOpenDocumentForPath(file.fullPath);

            if (doc) {
                MainViewManager._destroyEditorIfNotNeeded(doc);
            }
        };

        // when files are removed from a pane then
        //    we should destroy any unnecssary views
        if ($.isArray(removedFiles)) {
            removedFiles.forEach(function (removedFile) {
                handleFileRemoved(removedFile);
            });
        } else {
            handleFileRemoved(removedFiles);
        }
    }


    // Set up event dispatching
    EventDispatcher.makeEventDispatcher(exports);

    // File-based preferences handling
    exports.on("activeEditorChange", function (e, current) {
        if (current && current.document && current.document.file) {
            PreferencesManager._setCurrentFile(current.document.file.fullPath);
        }
    });

    // Initialize: command handlers
    CommandManager.register(Strings.CMD_TOGGLE_QUICK_EDIT, Commands.TOGGLE_QUICK_EDIT, function () {
        return _toggleInlineWidget(_inlineEditProviders, Strings.ERROR_QUICK_EDIT_PROVIDER_NOT_FOUND);
    });
    CommandManager.register(Strings.CMD_TOGGLE_QUICK_DOCS, Commands.TOGGLE_QUICK_DOCS, function () {
        return _toggleInlineWidget(_inlineDocsProviders, Strings.ERROR_QUICK_DOCS_PROVIDER_NOT_FOUND);
    });

    MainViewManager.on("currentFileChange", _handleCurrentFileChange);
    MainViewManager.on("workingSetRemove workingSetRemoveList", _handleRemoveFromPaneView);


    // For unit tests and internal use only
    exports._createFullEditorForDocument  = _createFullEditorForDocument;
    exports._notifyActiveEditorChanged    = _notifyActiveEditorChanged;

    // Internal Use only
    exports._saveEditorViewState          = _saveEditorViewState;
    exports._createUnattachedMasterEditor = _createUnattachedMasterEditor;

    // Define public API
    exports.createInlineEditorForDocument = createInlineEditorForDocument;
    exports.getFocusedInlineWidget        = getFocusedInlineWidget;
    exports.getInlineEditors              = getInlineEditors;
    exports.closeInlineWidget             = closeInlineWidget;
    exports.openDocument                  = openDocument;
    exports.canOpenPath                   = canOpenPath;

    // Convenience Methods
    exports.getActiveEditor               = getActiveEditor;
    exports.getCurrentFullEditor          = getCurrentFullEditor;
    exports.getFocusedEditor              = getFocusedEditor;


    exports.registerInlineEditProvider    = registerInlineEditProvider;
    exports.registerInlineDocsProvider    = registerInlineDocsProvider;

    // Deprecated
    exports.registerCustomViewer          = registerCustomViewer;
    exports.resizeEditor                  = resizeEditor;
    exports.focusEditor                   = focusEditor;
    exports.getCurrentlyViewedPath        = getCurrentlyViewedPath;
    exports.setEditorHolder               = setEditorHolder;
});
