// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
/**
 *
 *
 * @module keyboardmanager
 * @namespace keyboardmanager
 * @class KeyboardManager
 */

define([
    'jquery',
    'base/js/utils',
    'base/js/keyboard',
], function($, utils, keyboard) {
    "use strict";
    
    // Main keyboard manager for the notebook
    var keycodes = keyboard.keycodes;

    var KeyboardManager = function (options) {
        /**
         * A class to deal with keyboard event and shortcut
         *
         * @class KeyboardManager
         * @constructor
         * @param options {dict} Dictionary of keyword arguments :
         *    @param options.events {$(Events)} instance 
         *    @param options.pager: {Pager}  pager instance
         */
        this.mode = 'command';
        this.enabled = true;
        this.pager = options.pager;
        this.quick_help = undefined;
        this.notebook = undefined;
        this.last_mode = undefined;
        this.bind_events();
        this.env = {pager:this.pager};
        this.actions = options.actions;
        this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env );
        this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
        this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
        this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env);
        this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
        this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
        Object.seal(this);
    };




    /**
     * Return a dict of common shortcut
     * @method get_default_common_shortcuts
     *
     * @example Example of returned shortcut
     * ```
     * 'shortcut-key': 'action-name'
     * // a string representing the shortcut as dash separated value.
     * // e.g. 'shift' , 'shift-enter', 'cmd-t'
     *```
     */
    KeyboardManager.prototype.get_default_common_shortcuts = function() {
        return {
            'shift'       : 'ipython.ignore',
            'shift-enter' : 'ipython.run-select-next',
            'ctrl-enter'  : 'ipython.execute-in-place',
            'alt-enter'   : 'ipython.execute-and-insert-after',
            // cmd on mac, ctrl otherwise
            'cmdtrl-s'    : 'ipython.save-notebook',
        };
    };

    KeyboardManager.prototype.get_default_edit_shortcuts = function() {
        return {
            'esc'                 : 'ipython.go-to-command-mode',
            'ctrl-m'              : 'ipython.go-to-command-mode',
            'up'                  : 'ipython.move-cursor-up-or-previous-cell',
            'down'                : 'ipython.move-cursor-down-or-next-cell',
            'ctrl-shift--'        : 'ipython.split-cell-at-cursor',
            'ctrl-shift-subtract' : 'ipython.split-cell-at-cursor',
        };
    };

    KeyboardManager.prototype.get_default_command_shortcuts = function() {
        return {
            'shift-space': 'ipython.scroll-up',
            'shift-v' : 'ipython.paste-cell-before',
            'shift-m' : 'ipython.merge-selected-cells',
            'shift-o' : 'ipython.toggle-output-scrolling-selected-cell',
            'enter' : 'ipython.enter-edit-mode',
            'space' : 'ipython.scroll-down',
            'down' : 'ipython.select-next-cell',
            'i,i' : 'ipython.interrupt-kernel',
            '0,0' : 'ipython.restart-kernel',
            'd,d' : 'ipython.delete-cell',
            'esc': 'ipython.close-pager',
            'up' : 'ipython.select-previous-cell',
            'k' : 'ipython.select-previous-cell',
            'j' : 'ipython.select-next-cell',
            'shift-k': 'ipython.extend-selection-previous',
            'shift-j': 'ipython.extend-selection-next',
            'x' : 'ipython.cut-selected-cell',
            'c' : 'ipython.copy-selected-cell',
            'v' : 'ipython.paste-cell-after',
            'a' : 'ipython.insert-cell-before',
            'b' : 'ipython.insert-cell-after',
            'y' : 'ipython.change-selected-cell-to-code-cell',
            'm' : 'ipython.change-selected-cell-to-markdown-cell',
            'r' : 'ipython.change-selected-cell-to-raw-cell',
            '1' : 'ipython.change-selected-cell-to-heading-1',
            '2' : 'ipython.change-selected-cell-to-heading-2',
            '3' : 'ipython.change-selected-cell-to-heading-3',
            '4' : 'ipython.change-selected-cell-to-heading-4',
            '5' : 'ipython.change-selected-cell-to-heading-5',
            '6' : 'ipython.change-selected-cell-to-heading-6',
            'o' : 'ipython.toggle-output-visibility-selected-cell',
            's' : 'ipython.save-notebook',
            'l' : 'ipython.toggle-line-number-selected-cell',
            'h' : 'ipython.show-keyboard-shortcut-help-dialog',
            'z' : 'ipython.undo-last-cell-deletion',
            'q' : 'ipython.close-pager',
        };
    };

    KeyboardManager.prototype.bind_events = function () {
        var that = this;
        $(document).keydown(function (event) {
            if(event._ipkmIgnore===true||(event.originalEvent||{})._ipkmIgnore===true){
                return false;
            }
            return that.handle_keydown(event);
        });
    };

    KeyboardManager.prototype.set_notebook = function (notebook) {
        this.notebook = notebook;
        this.actions.extend_env({notebook:notebook});
    };
    
    KeyboardManager.prototype.set_quickhelp = function (notebook) {
        this.actions.extend_env({quick_help:notebook});
    };


    KeyboardManager.prototype.handle_keydown = function (event) {
        /**
         *  returning false from this will stop event propagation
         **/

        if (event.which === keycodes.esc) {
            // Intercept escape at highest level to avoid closing
            // websocket connection with firefox
            event.preventDefault();
        }
        
        if (!this.enabled) {
            if (event.which === keycodes.esc) {
                this.notebook.command_mode();
                return false;
            }
            return true;
        }
        
        if (this.mode === 'edit') {
            return this.edit_shortcuts.call_handler(event);
        } else if (this.mode === 'command') {
            return this.command_shortcuts.call_handler(event);
        }
        return true;
    };

    KeyboardManager.prototype.edit_mode = function () {
        this.last_mode = this.mode;
        this.mode = 'edit';
    };

    KeyboardManager.prototype.command_mode = function () {
        this.last_mode = this.mode;
        this.mode = 'command';
    };

    KeyboardManager.prototype.enable = function () {
        this.enabled = true;
    };

    KeyboardManager.prototype.disable = function () {
        this.enabled = false;
    };

    KeyboardManager.prototype.register_events = function (e) {
        e = $(e);
        var that = this;
        var handle_focus = function () {
            that.disable();
        };
        var handle_blur = function () {
            that.enable();
        };
        e.on('focusin', handle_focus);
        e.on('focusout', handle_blur);
        // TODO: Very strange. The focusout event does not seem fire for the 
        // bootstrap textboxes on FF25&26...  This works around that by 
        // registering focus and blur events recursively on all inputs within
        // registered element.
        e.find('input').blur(handle_blur);
        e.on('DOMNodeInserted', function (event) {
            var target = $(event.target);
            if (target.is('input')) {
                target.blur(handle_blur);
            } else {
                target.find('input').blur(handle_blur);    
            }
          });
        // There are times (raw_input) where we remove the element from the DOM before
        // focusout is called. In this case we bind to the remove event of jQueryUI,
        // which gets triggered upon removal, iff it is focused at the time.
        // is_focused must be used to check for the case where an element within
        // the element being removed is focused.
        e.on('remove', function () {
            if (utils.is_focused(e[0])) {
                that.enable();
            }
        });
    };

    return {'KeyboardManager': KeyboardManager};
});
