/*
 * 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.
 *
 */

/*jslint regexp: true */

define(function (require, exports, module) {
    "use strict";

    var _ = brackets.getModule("thirdparty/lodash");

    var Commands               = brackets.getModule("command/Commands"),
        CommandManager         = brackets.getModule("command/CommandManager"),
        Menus                  = brackets.getModule("command/Menus"),
        FileSystem             = brackets.getModule("filesystem/FileSystem"),
        FileUtils              = brackets.getModule("file/FileUtils"),
        PerfUtils              = brackets.getModule("utils/PerfUtils"),
        StringUtils            = brackets.getModule("utils/StringUtils"),
        Dialogs                = brackets.getModule("widgets/Dialogs"),
        Strings                = brackets.getModule("strings"),
        PreferencesManager     = brackets.getModule("preferences/PreferencesManager"),
        LocalizationUtils      = brackets.getModule("utils/LocalizationUtils"),
        MainViewManager        = brackets.getModule("view/MainViewManager"),
        WorkingSetView         = brackets.getModule("project/WorkingSetView"),
        ExtensionManager       = brackets.getModule("extensibility/ExtensionManager"),
        Mustache               = brackets.getModule("thirdparty/mustache/mustache"),
        ErrorNotification      = require("ErrorNotification"),
        NodeDebugUtils         = require("NodeDebugUtils"),
        PerfDialogTemplate     = require("text!htmlContent/perf-dialog.html"),
        LanguageDialogTemplate = require("text!htmlContent/language-dialog.html");

    var KeyboardPrefs = JSON.parse(require("text!keyboard.json"));

    // default preferences file name
    var DEFAULT_PREFERENCES_FILENAME = "defaultPreferences.json",
        SUPPORTED_PREFERENCE_TYPES   = ["number", "boolean", "string", "array", "object"];

    var recomputeDefaultPrefs        = true,
        defaultPreferencesFullPath   = brackets.app.getApplicationSupportDirectory() + "/" + DEFAULT_PREFERENCES_FILENAME;

    /**
     * Brackets Application Menu Constant
     * @const {string}
     */
    var DEBUG_MENU = "debug-menu";

     /**
      * Debug commands IDs
      * @enum {string}
      */
    var DEBUG_REFRESH_WINDOW                  = "debug.refreshWindow", // string must MATCH string in native code (brackets_extensions)
        DEBUG_SHOW_DEVELOPER_TOOLS            = "debug.showDeveloperTools",
        DEBUG_RUN_UNIT_TESTS                  = "debug.runUnitTests",
        DEBUG_SHOW_PERF_DATA                  = "debug.showPerfData",
        DEBUG_RELOAD_WITHOUT_USER_EXTS        = "debug.reloadWithoutUserExts",
        DEBUG_NEW_BRACKETS_WINDOW             = "debug.newBracketsWindow",
        DEBUG_SWITCH_LANGUAGE                 = "debug.switchLanguage",
        DEBUG_ENABLE_NODE_DEBUGGER            = "debug.enableNodeDebugger",
        DEBUG_LOG_NODE_STATE                  = "debug.logNodeState",
        DEBUG_RESTART_NODE                    = "debug.restartNode",
        DEBUG_SHOW_ERRORS_IN_STATUS_BAR       = "debug.showErrorsInStatusBar",
        DEBUG_OPEN_BRACKETS_SOURCE            = "debug.openBracketsSource",
        DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW  = "debug.openPrefsInSplitView";

    // define a preference to turn off opening preferences in split-view.
    var prefs = PreferencesManager.getExtensionPrefs("preferencesView");
    prefs.definePreference("openPrefsInSplitView",   "boolean", true, {
        description: Strings.DESCRIPTION_OPEN_PREFS_IN_SPLIT_VIEW
    });

    prefs.definePreference("openUserPrefsInSecondPane",   "boolean", true, {
        description: Strings.DESCRIPTION_OPEN_USER_PREFS_IN_SECOND_PANE
    });

    PreferencesManager.definePreference(DEBUG_SHOW_ERRORS_IN_STATUS_BAR, "boolean", false, {
        description: Strings.DESCRIPTION_SHOW_ERRORS_IN_STATUS_BAR
    });

    function handleShowDeveloperTools() {
        brackets.app.showDeveloperTools();
    }

    // Implements the 'Run Tests' menu to bring up the Jasmine unit test window
    var _testWindow = null;
    function _runUnitTests(spec) {
        var queryString = spec ? "?spec=" + spec : "";
        if (_testWindow && !_testWindow.closed) {
            if (_testWindow.location.search !== queryString) {
                _testWindow.location.href = "../test/SpecRunner.html" + queryString;
            } else {
                _testWindow.location.reload(true);
            }
        } else {
            _testWindow = window.open("../test/SpecRunner.html" + queryString, "brackets-test", "width=" + $(window).width() + ",height=" + $(window).height());
            _testWindow.location.reload(true); // if it had been opened earlier, force a reload because it will be cached
        }
    }

    function handleReload() {
        CommandManager.execute(Commands.APP_RELOAD);
    }

    function handleReloadWithoutUserExts() {
        CommandManager.execute(Commands.APP_RELOAD_WITHOUT_EXTS);
    }

    function handleNewBracketsWindow() {
        window.open(window.location.href);
    }

    function handleShowPerfData() {
        var templateVars = {
            delimitedPerfData: PerfUtils.getDelimitedPerfData(),
            perfData: []
        };

        var getValue = function (entry) {
            // entry is either an Array or a number
            if (Array.isArray(entry)) {
                // For Array of values, return: minimum/average(count)/maximum/last
                var i, e, avg, sum = 0, min = Number.MAX_VALUE, max = 0;

                for (i = 0; i < entry.length; i++) {
                    e = entry[i];
                    min = Math.min(min, e);
                    sum += e;
                    max = Math.max(max, e);
                }
                avg = Math.round(sum * 10 / entry.length) / 10; // tenth of a millisecond
                return String(min) + "/" + String(avg) + "(" + entry.length + ")/" + String(max) + "/" + String(e);
            } else {
                return entry;
            }
        };

        var perfData = PerfUtils.getData();
        _.forEach(perfData, function (value, testName) {
            templateVars.perfData.push({
                testName: StringUtils.breakableUrl(testName),
                value:    getValue(value)
            });
        });

        var template = Mustache.render(PerfDialogTemplate, templateVars);
        Dialogs.showModalDialogUsingTemplate(template);

        // Select the raw perf data field on click since select all doesn't
        // work outside of the editor
        $("#brackets-perf-raw-data").click(function () {
            $(this).focus().select();
        });
    }

    function handleSwitchLanguage() {
        var stringsPath = FileUtils.getNativeBracketsDirectoryPath() + "/nls";

        FileSystem.getDirectoryForPath(stringsPath).getContents(function (err, entries) {
            if (!err) {
                var $dialog,
                    $submit,
                    $select,
                    locale,
                    curLocale = (brackets.isLocaleDefault() ? null : brackets.getLocale()),
                    languages = [];

                var setLanguage = function (event) {
                    locale = $select.val();
                    $submit.prop("disabled", locale === (curLocale || ""));
                };

                // inspect all children of dirEntry
                entries.forEach(function (entry) {
                    if (entry.isDirectory) {
                        var match = entry.name.match(/^([a-z]{2})(-[a-z]{2})?$/);

                        if (match) {
                            var language = entry.name,
                                label = match[1];

                            if (match[2]) {
                                label += match[2].toUpperCase();
                            }

                            languages.push({label: LocalizationUtils.getLocalizedLabel(label), language: language});
                        }
                    }
                });
                // add English (US), which is the root folder and should be sorted as well
                languages.push({label: LocalizationUtils.getLocalizedLabel("en"),  language: "en"});

                // sort the languages via their display name
                languages.sort(function (lang1, lang2) {
                    return lang1.label.localeCompare(lang2.label);
                });

                // add system default (which is placed on the very top)
                languages.unshift({label: Strings.LANGUAGE_SYSTEM_DEFAULT, language: null});

                var template = Mustache.render(LanguageDialogTemplate, {languages: languages, Strings: Strings});
                Dialogs.showModalDialogUsingTemplate(template).done(function (id) {
                    if (id === Dialogs.DIALOG_BTN_OK && locale !== curLocale) {
                        brackets.setLocale(locale);
                        CommandManager.execute(Commands.APP_RELOAD);
                    }
                });

                $dialog = $(".switch-language.instance");
                $submit = $dialog.find(".dialog-button[data-button-id='" + Dialogs.DIALOG_BTN_OK + "']");
                $select = $dialog.find("select");

                $select.on("change", setLanguage).val(curLocale);
            }
        });
    }

    function enableRunTestsMenuItem() {
        if (brackets.inBrowser) {
            return;
        }

        // Check for the SpecRunner.html file
        var file = FileSystem.getFileForPath(
            FileUtils.getNativeBracketsDirectoryPath() + "/../test/SpecRunner.html"
        );

        file.exists(function (err, exists) {
            if (!err && exists) {
                // If the SpecRunner.html file exists, enable the menu item.
                // (menu item is already disabled, so no need to disable if the
                // file doesn't exist).
                CommandManager.get(DEBUG_RUN_UNIT_TESTS).setEnabled(true);
            }
        });
    }

    function toggleErrorNotification(bool) {
        var val,
            oldPref = !!PreferencesManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR);

        if (bool === undefined) {
            val = !oldPref;
        } else {
            val = !!bool;
        }

        ErrorNotification.toggle(val);

        // update menu
        CommandManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR).setChecked(val);
        if (val !== oldPref) {
            PreferencesManager.set(DEBUG_SHOW_ERRORS_IN_STATUS_BAR, val);
        }
    }

    function handleOpenBracketsSource() {
        // Brackets source dir w/o the trailing src/ folder
        var dir = FileUtils.getNativeBracketsDirectoryPath().replace(/\/[^\/]+$/, "/");
        brackets.app.showOSFolder(dir);
    }

    function _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise) {

        var currScheme         = MainViewManager.getLayoutScheme(),
            file               = FileSystem.getFileForPath(prefsPath),
            defaultPrefsFile   = FileSystem.getFileForPath(defaultPrefsPath),
            DEFAULT_PREFS_PANE = "first-pane",
            USER_PREFS_PANE    = "second-pane";

        // Exchange the panes, if default preferences need to be opened
        // in the right pane.
        if (!prefs.get("openUserPrefsInSecondPane")) {
            DEFAULT_PREFS_PANE = "second-pane";
            USER_PREFS_PANE    = "first-pane";
        }

        function _openFiles() {

            if (currScheme.rows === 1 && currScheme.columns === 1) {
                // Split layout is not active yet. Inititate the
                // split view.
                MainViewManager.setLayoutScheme(1, 2);
            }

            // Open the default preferences in the left pane in the read only mode.
            CommandManager.execute(Commands.FILE_OPEN, { fullPath: defaultPrefsPath, paneId: DEFAULT_PREFS_PANE, options: { isReadOnly: true } })
                .done(function () {

                    // Make sure the preference file is going to be opened in pane
                    // specified in the preference.
                    if (MainViewManager.findInWorkingSet(DEFAULT_PREFS_PANE, prefsPath) >= 0) {

                        MainViewManager._moveView(DEFAULT_PREFS_PANE, USER_PREFS_PANE, file, 0, true);

                        // Now refresh the project tree by asking
                        // it to rebuild the UI.
                        WorkingSetView.refresh(true);
                    }

                    CommandManager.execute(Commands.FILE_OPEN, { fullPath: prefsPath, paneId: USER_PREFS_PANE})
                        .done(function () {
                            deferredPromise.resolve();
                        }).fail(function () {
                            deferredPromise.reject();
                        });
                }).fail(function () {
                    deferredPromise.reject();
                });
        }

        var resultObj = MainViewManager.findInAllWorkingSets(defaultPrefsPath);
        if (resultObj && resultObj.length > 0) {
            CommandManager.execute(Commands.FILE_CLOSE, {file: defaultPrefsFile, paneId: resultObj[0].paneId})
                .done(function () {
                    _openFiles();
                }).fail(function () {
                    deferredPromise.reject();
                });
        } else {
            _openFiles();
        }

    }

    function _isSupportedPrefType(prefType) {

        if (SUPPORTED_PREFERENCE_TYPES.indexOf(prefType) >= 0) {
            return true;
        } else {
            return false;
        }
    }

   /*
    * This method tries to deduce the preference type
    * based on various parameters like objects initial
    * value, object type, object's type property.
    */
    function _getPrefType(prefItem) {

        var finalPrefType = "undefined";

        if (prefItem) {
            // check the type parameter.
            var _prefType = prefItem.type;
            if (_prefType !== undefined) {
                finalPrefType = prefItem.type.toLowerCase();
                // make sure the initial property's
                // object type matches to that of 'type' propety.
                if (prefItem.initial !== undefined) {

                    if (Array.isArray(prefItem.initial)) {
                        _prefType = "array";
                    } else {
                        var _initialType = typeof (prefItem.initial);
                        _initialType = _initialType.toLowerCase();
                        if (_prefType !== _initialType) {
                            _prefType = _initialType;
                        }
                    }
                }
            }

            if (_prefType) {
                // preference object's type
                // is defined. Check if that is valid or not.
                finalPrefType = _prefType;
                if (!_isSupportedPrefType(finalPrefType)) {
                    finalPrefType = "undefined";
                }
            } else if (Array.isArray(prefItem)) {
                // Check if the object itself
                // is an array, in which case
                // we log the default.
                finalPrefType = "array";
            } else if (prefItem.initial !== undefined  ||
                       prefItem.keys !== undefined) {

                // OK looks like this preference has
                // no explicit type defined. instead
                // it needs to be deduced from initial/keys
                // variable.
                var _prefVar;
                if (prefItem.initial !== undefined) {
                    _prefVar = prefItem.initial;
                } else {
                    _prefVar = prefItem.keys;
                }

                if (Array.isArray(_prefVar)) {
                    // In cases of array the
                    // typeof is returning a function.
                    finalPrefType = "array";
                }

            } else {
                finalPrefType = typeof (prefItem);
            }
        }

        // Now make sure we recognize this format.
        if (!_isSupportedPrefType(finalPrefType)) {
            finalPrefType = "undefined";
        }

        return finalPrefType;
    }

    function _isValidPref(pref) {

        // Make sure to generate pref description only for
        // user overrides and don't generate for properties
        // meant to be used for internal purposes. Also check
        // if the preference type is valid or not.
        if (pref && !pref.excludeFromHints && _getPrefType(pref) !== "undefined") {
            return true;
        }

        return false;
    }

   /*
    * This method tries to match between initial objects
    * and key objects and then aggregates objects from both
    * the properties.
    */
    function _getChildPrefs(prefItem) {

        var finalObj = {},
            keysFound = false;

        if (!prefItem) {
            return {};
        }

        function _populateKeys(allKeys) {

            var prop;
            if (typeof (allKeys) === "object") {
                // iterate through the list.
                keysFound = true;
                for (prop in allKeys) {
                    if (allKeys.hasOwnProperty(prop)) {
                        finalObj[prop] = allKeys[prop];
                    }
                }
            }
        }

        _populateKeys(prefItem.initial);
        _populateKeys(prefItem.keys);

        // Last resort: Maybe plain objects, in which case
        // we blindly extract all the properties.
        if (!keysFound) {
            _populateKeys(prefItem);
        }

        return finalObj;
    }

    function _formatBasicPref(prefItem, prefName, tabIndentStr) {

        if (!prefItem || typeof (prefName) !== "string" || _getPrefType(prefItem) === "object") {
            // return empty string in case of
            // object or pref is not defined.
            return "";
        }

        var prefDescription = prefItem.description || "",
            prefDefault     = prefItem.initial,
            prefFormatText  = tabIndentStr + "\t// {0}\n" + tabIndentStr + "\t\"{1}\": {2}",
            prefItemType    = _getPrefType(prefItem);

        if (prefDefault === undefined && !prefItem.description) {
            // This could be the case when prefItem is a basic JS variable.
            if (prefItemType === "number" || prefItemType === "boolean" || prefItemType === "string") {
                prefDefault = prefItem;
            }
        }

        if (prefDefault === undefined) {
            if (prefItemType === "number") {
                prefDefault = 0;
            } else if (prefItemType === "boolean") {
                // Defaulting the preference to false,
                // in case this is missing.
                prefDefault = false;
            } else {
                // for all other types
                prefDefault = "";
            }
        }

        if ((prefDescription === undefined || prefDescription.length === 0)) {
            if (!Array.isArray(prefDefault)) {
                prefDescription = Strings.DEFAULT_PREFERENCES_JSON_DEFAULT + ": " + prefDefault;
            } else {
                prefDescription = "";
            }
        }

        if (prefItemType === "array") {
            prefDefault = "[]";
        } else if (prefDefault.length === 0 || (prefItemType !== "boolean" && prefItemType !== "number")) {
            prefDefault = "\"" + prefDefault + "\"";
        }

        return StringUtils.format(prefFormatText, prefDescription, prefName, prefDefault);
    }

    function _formatPref(prefName,  prefItem, indentLevel) {

        // check for validity of the parameters being passed
        if (!prefItem || indentLevel < 0 || !prefName || !prefName.length) {
            return "";
        }

        var iLevel,
            prefItemKeys,
            entireText     = "",
            prefItemDesc   = prefItem.description || "",
            prefItemType   = _getPrefType(prefItem),
            hasKeys        = false,
            tabIndents     = "",
            numKeys        = 0;

        // Generate the indentLevel string
        for (iLevel = 0; iLevel < indentLevel; iLevel++) {
            tabIndents += "\t";
        }

        // Check if the preference is an object.
        if (_getPrefType(prefItem) === "object") {
            prefItemKeys = _getChildPrefs(prefItem);
            if (Object.keys(prefItemKeys).length > 0) {
                hasKeys = true;
            }
        }

        // There are some properties like "highlightMatches" that
        // are declared as boolean type but still can take object keys.
        // The below condition check can take care of cases like this.
        if (prefItemType !== "object" && hasKeys === false) {
            return _formatBasicPref(prefItem, prefName, tabIndents);
        }

        // Indent the beginning of the object.
        tabIndents += "\t";

        if (prefItemDesc && prefItemDesc.length > 0) {
            entireText = tabIndents + "// " + prefItemDesc + "\n";
        }

        entireText += tabIndents + "\"" + prefName + "\": " + "{";

        if (prefItemKeys) {
            numKeys = Object.keys(prefItemKeys).length;
        }

        // In case the object array is empty
        if (numKeys <= 0) {
            entireText += "}";
            return entireText;
        } else {
            entireText += "\n";
        }

        // Now iterate through all the keys
        // and generate nested formatted objects.

        Object.keys(prefItemKeys).sort().forEach(function (property) {

            if (prefItemKeys.hasOwnProperty(property)) {

                var pref = prefItemKeys[property];

                if (_isValidPref(pref)) {

                    var formattedText = "";

                    if (_getPrefType(pref) === "object") {
                        formattedText = _formatPref(property, pref, indentLevel + 1);
                    } else {
                        formattedText = _formatBasicPref(pref, property, tabIndents);
                    }

                    if (formattedText.length > 0) {
                        entireText += formattedText + ",\n\n";
                    }
                }
            }
        });

        // Strip ",\n\n" that got added above, for the last property
        if (entireText.length > 0) {
            entireText = entireText.slice(0, -3) + "\n" + tabIndents + "}";
        } else {
            entireText = "{}";
        }

        return entireText;
    }

    function _getDefaultPreferencesString() {

        var allPrefs       = PreferencesManager.getAllPreferences(),
            headerComment  = Strings.DEFAULT_PREFERENCES_JSON_HEADER_COMMENT + "\n\n{\n",
            entireText     = "";

        Object.keys(allPrefs).sort().forEach(function (property) {
            if (allPrefs.hasOwnProperty(property)) {

                var pref = allPrefs[property];

                if (_isValidPref(pref)) {
                    entireText += _formatPref(property, pref, 0) + ",\n\n";
                }
            }
        });

        // Strip ",\n\n" that got added above, for the last property
        if (entireText.length > 0) {
            entireText = headerComment + entireText.slice(0, -3) + "\n}\n";
        } else {
            entireText = headerComment + "}\n";
        }

        return entireText;
    }

    function _loadDefaultPrefs(prefsPath, deferredPromise) {

        var defaultPrefsPath = defaultPreferencesFullPath,
            file             = FileSystem.getFileForPath(defaultPrefsPath);

        function _executeDefaultOpenPrefsCommand() {

            CommandManager.execute(Commands.FILE_OPEN_PREFERENCES)
                .done(function () {
                    deferredPromise.resolve();
                }).fail(function () {
                    deferredPromise.reject();
                });
        }

        file.exists(function (err, doesExist) {

            if (doesExist) {

                // Go about recreating the default preferecences file.
                if (recomputeDefaultPrefs) {

                    var prefsString       = _getDefaultPreferencesString();
                    recomputeDefaultPrefs = false;

                    // We need to delete this first
                    file.unlink(function (err) {
                        if (!err) {
                            // Go about recreating this
                            // file and write the default
                            // preferences string to this file.
                            FileUtils.writeText(file, prefsString, true)
                                .done(function () {
                                    recomputeDefaultPrefs = false;
                                    _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise);
                                }).fail(function (error) {
                                    // Give a chance for default preferences command.
                                    console.error("Unable to write to default preferences file! error code:" + error);
                                    _executeDefaultOpenPrefsCommand();
                                });
                        } else {
                            // Some error occured while trying to delete
                            // the file. In this case open the user
                            // preferences alone.
                            console.error("Unable to delete the existing default preferences file! error code:" + err);
                            _executeDefaultOpenPrefsCommand();
                        }
                    });

                } else {
                    // Default preferences already generated.
                    // Just go about opening both the files.
                    _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise);
                }
            } else {

                // The default prefs file does not exist at all.
                // So go about recreating the default preferences
                // file.
                var _prefsString = _getDefaultPreferencesString();
                FileUtils.writeText(file, _prefsString, true)
                    .done(function () {
                        recomputeDefaultPrefs = false;
                        _openPrefFilesInSplitView(prefsPath, defaultPrefsPath, deferredPromise);
                    }).fail(function (error) {
                        // Give a chance for default preferences command.
                        console.error("Unable to write to default preferences file! error code:" + error);
                        _executeDefaultOpenPrefsCommand();
                    });
            }
        });
    }

    function handleOpenPrefsInSplitView() {

        var fullPath        = PreferencesManager.getUserPrefFile(),
            file            = FileSystem.getFileForPath(fullPath),
            splitViewPrefOn = prefs.get("openPrefsInSplitView"),
            result          = new $.Deferred();

        if (!splitViewPrefOn) {
            return CommandManager.execute(Commands.FILE_OPEN_PREFERENCES);
        } else {
            file.exists(function (err, doesExist) {
                if (doesExist) {
                    _loadDefaultPrefs(fullPath, result);
                } else {
                    FileUtils.writeText(file, "", true)
                        .done(function () {
                            _loadDefaultPrefs(fullPath, result);
                        }).fail(function () {
                            result.reject();
                        });
                }
            });
        }

        return result.promise();
    }

    ExtensionManager.on("statusChange", function (id) {
        // Seems like an extension(s) got installed.
        // Need to recompute the default prefs.
        recomputeDefaultPrefs = true;
    });

    /* Register all the command handlers */

    // Show Developer Tools (optionally enabled)
    CommandManager.register(Strings.CMD_SHOW_DEV_TOOLS,             DEBUG_SHOW_DEVELOPER_TOOLS,     handleShowDeveloperTools)
        .setEnabled(!!brackets.app.showDeveloperTools);
    CommandManager.register(Strings.CMD_REFRESH_WINDOW,             DEBUG_REFRESH_WINDOW,           handleReload);
    CommandManager.register(Strings.CMD_RELOAD_WITHOUT_USER_EXTS,   DEBUG_RELOAD_WITHOUT_USER_EXTS, handleReloadWithoutUserExts);
    CommandManager.register(Strings.CMD_NEW_BRACKETS_WINDOW,        DEBUG_NEW_BRACKETS_WINDOW,      handleNewBracketsWindow);

    // Start with the "Run Tests" item disabled. It will be enabled later if the test file can be found.
    CommandManager.register(Strings.CMD_RUN_UNIT_TESTS,       DEBUG_RUN_UNIT_TESTS,         _runUnitTests)
        .setEnabled(false);

    CommandManager.register(Strings.CMD_SHOW_PERF_DATA,            DEBUG_SHOW_PERF_DATA,            handleShowPerfData);

    // Open Brackets Source (optionally enabled)
    CommandManager.register(Strings.CMD_OPEN_BRACKETS_SOURCE,      DEBUG_OPEN_BRACKETS_SOURCE,      handleOpenBracketsSource)
        .setEnabled(!StringUtils.endsWith(decodeURI(window.location.pathname), "/www/index.html"));

    CommandManager.register(Strings.CMD_SWITCH_LANGUAGE,           DEBUG_SWITCH_LANGUAGE,           handleSwitchLanguage);
    CommandManager.register(Strings.CMD_SHOW_ERRORS_IN_STATUS_BAR, DEBUG_SHOW_ERRORS_IN_STATUS_BAR, toggleErrorNotification);

    // Node-related Commands
    CommandManager.register(Strings.CMD_ENABLE_NODE_DEBUGGER, DEBUG_ENABLE_NODE_DEBUGGER,   NodeDebugUtils.enableDebugger);
    CommandManager.register(Strings.CMD_LOG_NODE_STATE,       DEBUG_LOG_NODE_STATE,         NodeDebugUtils.logNodeState);
    CommandManager.register(Strings.CMD_RESTART_NODE,         DEBUG_RESTART_NODE,           NodeDebugUtils.restartNode);

    CommandManager.register(Strings.CMD_OPEN_PREFERENCES, DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW, handleOpenPrefsInSplitView);

    enableRunTestsMenuItem();
    toggleErrorNotification(PreferencesManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR));

    PreferencesManager.on("change", DEBUG_SHOW_ERRORS_IN_STATUS_BAR, function () {
        toggleErrorNotification(PreferencesManager.get(DEBUG_SHOW_ERRORS_IN_STATUS_BAR));
    });

    /*
     * Debug menu
     */
    var menu = Menus.addMenu(Strings.DEBUG_MENU, DEBUG_MENU, Menus.BEFORE, Menus.AppMenuBar.HELP_MENU);
    menu.addMenuItem(DEBUG_SHOW_DEVELOPER_TOOLS, KeyboardPrefs.showDeveloperTools);
    menu.addMenuItem(DEBUG_REFRESH_WINDOW, KeyboardPrefs.refreshWindow);
    menu.addMenuItem(DEBUG_RELOAD_WITHOUT_USER_EXTS, KeyboardPrefs.reloadWithoutUserExts);
    menu.addMenuItem(DEBUG_NEW_BRACKETS_WINDOW);
    menu.addMenuDivider();
    menu.addMenuItem(DEBUG_SWITCH_LANGUAGE);
    menu.addMenuDivider();
    menu.addMenuItem(DEBUG_RUN_UNIT_TESTS);
    menu.addMenuItem(DEBUG_SHOW_PERF_DATA);
    menu.addMenuItem(DEBUG_OPEN_BRACKETS_SOURCE);
    menu.addMenuDivider();
    menu.addMenuItem(DEBUG_ENABLE_NODE_DEBUGGER);
    menu.addMenuItem(DEBUG_LOG_NODE_STATE);
    menu.addMenuItem(DEBUG_RESTART_NODE);
    menu.addMenuItem(DEBUG_SHOW_ERRORS_IN_STATUS_BAR);
    menu.addMenuItem(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW); // this command will enable defaultPreferences and brackets preferences to be open side by side in split view.
    menu.addMenuItem(Commands.FILE_OPEN_KEYMAP);      // this command is defined in core, but exposed only in Debug menu for now

    // exposed for convenience, but not official API
    exports._runUnitTests = _runUnitTests;
});
