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

define([
    'base/js/namespace',
    'jquery',
    'base/js/utils',
    'base/js/keyboard',
], function(IPython, $, utils, keyboard) {
    "use strict";
    
    var browser = utils.browser[0];
    var platform = utils.platform;

    // 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.bind_events();
        this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
        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.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
        this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
    };

    /**
     * Return a dict of common shortcut
     * @method get_default_common_shortcuts
     *
     * @example Example of returned shortcut
     * ```
     * 'shortcut-key': // a string representing the shortcut as dash separated value.
     *                 // e.g. 'shift' , 'shift-enter', 'cmd-t'
     *  {
     *     help: String // user facing help string 
     *     help_index: String // string used internally to order the shortcut on the quickhelp
     *     handler: function(event){return true|false} // function that takes an even as first and only parameter
     *          // and return a boolean indicating whether or not the event should been handled further.
     *   }
     *```
     */
    KeyboardManager.prototype.get_default_common_shortcuts = function() {
        var that = this;
        var shortcuts = {
            'shift' : {
                help    : '',
                help_index : '',
                handler : function (event) {
                    // ignore shift keydown
                    return true;
                }
            },
            'shift-enter' : {
                help    : 'run cell, select below',
                help_index : 'ba',
                handler : function (event) {
                    that.notebook.execute_cell_and_select_below();
                    return false;
                }
            },
            'ctrl-enter' : {
                help    : 'run cell',
                help_index : 'bb',
                handler : function (event) {
                    that.notebook.execute_cell();
                    return false;
                }
            },
            'alt-enter' : {
                help    : 'run cell, insert below',
                help_index : 'bc',
                handler : function (event) {
                    that.notebook.execute_cell_and_insert_below();
                    return false;
                }
            }
        };

        if (platform === 'MacOS') {
            shortcuts['cmd-s'] =
                {
                    help    : 'save notebook',
                    help_index : 'fb',
                    handler : function (event) {
                        that.notebook.save_checkpoint();
                        event.preventDefault();
                        return false;
                    }
                };
        } else {
            shortcuts['ctrl-s'] =
                {
                    help    : 'save notebook',
                    help_index : 'fb',
                    handler : function (event) {
                        that.notebook.save_checkpoint();
                        event.preventDefault();
                        return false;
                    }
                };
        }
        return shortcuts;
    };

    KeyboardManager.prototype.get_default_edit_shortcuts = function() {
        var that = this;
        return {
            'esc' : {
                help    : 'command mode',
                help_index : 'aa',
                handler : function (event) {
                    that.notebook.command_mode();
                    return false;
                }
            },
            'ctrl-m' : {
                help    : 'command mode',
                help_index : 'ab',
                handler : function (event) {
                    that.notebook.command_mode();
                    return false;
                }
            },
            'up' : {
                help    : '',
                help_index : '',
                handler : function (event) {
                    var index = that.notebook.get_selected_index();
                    var cell = that.notebook.get_cell(index);
                    var cm = that.notebook.get_selected_cell().code_mirror;
                    var cur = cm.getCursor()
                    if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
                        event.preventDefault();
                        that.notebook.command_mode();
                        that.notebook.select_prev();
                        that.notebook.edit_mode();
                        var cm = that.notebook.get_selected_cell().code_mirror;
                        cm.setCursor(cm.lastLine(), 0);
                    }
                    return false;
                }
            },
            'down' : {
                help    : '',
                help_index : '',
                handler : function (event) {
                    var index = that.notebook.get_selected_index();
                    var cell = that.notebook.get_cell(index);
                    if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
                        event.preventDefault();
                        that.notebook.command_mode();
                        that.notebook.select_next();
                        that.notebook.edit_mode();
                        var cm = that.notebook.get_selected_cell().code_mirror;
                        cm.setCursor(0, 0);
                        return false;
                    }
                    return false;
                }
            },
            'ctrl-shift--' : {
                help    : 'split cell',
                help_index : 'ea',
                handler : function (event) {
                    that.notebook.split_cell();
                    return false;
                }
            },
            'ctrl-shift-subtract' : {
                help    : '',
                help_index : 'eb',
                handler : function (event) {
                    that.notebook.split_cell();
                    return false;
                }
            },
        };
    };

    KeyboardManager.prototype.get_default_command_shortcuts = function() {
        var that = this;
        return {
            'space': {
                help: "Scroll down",
                handler: function(event) {
                    return that.notebook.scroll_manager.scroll(1);
                },
            },
            'shift-space': {
                help: "Scroll up",
                handler: function(event) {
                    return that.notebook.scroll_manager.scroll(-1);
                },
            },
            'enter' : {
                help    : 'edit mode',
                help_index : 'aa',
                handler : function (event) {
                    that.notebook.edit_mode();
                    return false;
                }
            },
            'up' : {
                help    : 'select previous cell',
                help_index : 'da',
                handler : function (event) {
                    var index = that.notebook.get_selected_index();
                    if (index !== 0 && index !== null) {
                        that.notebook.select_prev();
                        that.notebook.focus_cell();
                    }
                    return false;
                }
            },
            'down' : {
                help    : 'select next cell',
                help_index : 'db',
                handler : function (event) {
                    var index = that.notebook.get_selected_index();
                    if (index !== (that.notebook.ncells()-1) && index !== null) {
                        that.notebook.select_next();
                        that.notebook.focus_cell();
                    }
                    return false;
                }
            },
            'k' : {
                help    : 'select previous cell',
                help_index : 'dc',
                handler : function (event) {
                    var index = that.notebook.get_selected_index();
                    if (index !== 0 && index !== null) {
                        that.notebook.select_prev();
                        that.notebook.focus_cell();
                    }
                    return false;
                }
            },
            'j' : {
                help    : 'select next cell',
                help_index : 'dd',
                handler : function (event) {
                    var index = that.notebook.get_selected_index();
                    if (index !== (that.notebook.ncells()-1) && index !== null) {
                        that.notebook.select_next();
                        that.notebook.focus_cell();
                    }
                    return false;
                }
            },
            'x' : {
                help    : 'cut cell',
                help_index : 'ee',
                handler : function (event) {
                    that.notebook.cut_cell();
                    return false;
                }
            },
            'c' : {
                help    : 'copy cell',
                help_index : 'ef',
                handler : function (event) {
                    that.notebook.copy_cell();
                    return false;
                }
            },
            'shift-v' : {
                help    : 'paste cell above',
                help_index : 'eg',
                handler : function (event) {
                    that.notebook.paste_cell_above();
                    return false;
                }
            },
            'v' : {
                help    : 'paste cell below',
                help_index : 'eh',
                handler : function (event) {
                    that.notebook.paste_cell_below();
                    return false;
                }
            },
            'd' : {
                help    : 'delete cell (press twice)',
                help_index : 'ej',
                count: 2,
                handler : function (event) {
                    that.notebook.delete_cell();
                    return false;
                }
            },
            'a' : {
                help    : 'insert cell above',
                help_index : 'ec',
                handler : function (event) {
                    that.notebook.insert_cell_above();
                    that.notebook.select_prev();
                    that.notebook.focus_cell();
                    return false;
                }
            },
            'b' : {
                help    : 'insert cell below',
                help_index : 'ed',
                handler : function (event) {
                    that.notebook.insert_cell_below();
                    that.notebook.select_next();
                    that.notebook.focus_cell();
                    return false;
                }
            },
            'y' : {
                help    : 'to code',
                help_index : 'ca',
                handler : function (event) {
                    that.notebook.to_code();
                    return false;
                }
            },
            'm' : {
                help    : 'to markdown',
                help_index : 'cb',
                handler : function (event) {
                    that.notebook.to_markdown();
                    return false;
                }
            },
            'r' : {
                help    : 'to raw',
                help_index : 'cc',
                handler : function (event) {
                    that.notebook.to_raw();
                    return false;
                }
            },
            '1' : {
                help    : 'to heading 1',
                help_index : 'cd',
                handler : function (event) {
                    that.notebook.to_heading(undefined, 1);
                    return false;
                }
            },
            '2' : {
                help    : 'to heading 2',
                help_index : 'ce',
                handler : function (event) {
                    that.notebook.to_heading(undefined, 2);
                    return false;
                }
            },
            '3' : {
                help    : 'to heading 3',
                help_index : 'cf',
                handler : function (event) {
                    that.notebook.to_heading(undefined, 3);
                    return false;
                }
            },
            '4' : {
                help    : 'to heading 4',
                help_index : 'cg',
                handler : function (event) {
                    that.notebook.to_heading(undefined, 4);
                    return false;
                }
            },
            '5' : {
                help    : 'to heading 5',
                help_index : 'ch',
                handler : function (event) {
                    that.notebook.to_heading(undefined, 5);
                    return false;
                }
            },
            '6' : {
                help    : 'to heading 6',
                help_index : 'ci',
                handler : function (event) {
                    that.notebook.to_heading(undefined, 6);
                    return false;
                }
            },
            'o' : {
                help    : 'toggle output',
                help_index : 'gb',
                handler : function (event) {
                    that.notebook.toggle_output();
                    return false;
                }
            },
            'shift-o' : {
                help    : 'toggle output scrolling',
                help_index : 'gc',
                handler : function (event) {
                    that.notebook.toggle_output_scroll();
                    return false;
                }
            },
            's' : {
                help    : 'save notebook',
                help_index : 'fa',
                handler : function (event) {
                    that.notebook.save_checkpoint();
                    return false;
                }
            },
            'ctrl-j' : {
                help    : 'move cell down',
                help_index : 'eb',
                handler : function (event) {
                    that.notebook.move_cell_down();
                    return false;
                }
            },
            'ctrl-k' : {
                help    : 'move cell up',
                help_index : 'ea',
                handler : function (event) {
                    that.notebook.move_cell_up();
                    return false;
                }
            },
            'l' : {
                help    : 'toggle line numbers',
                help_index : 'ga',
                handler : function (event) {
                    that.notebook.cell_toggle_line_numbers();
                    return false;
                }
            },
            'i' : {
                help    : 'interrupt kernel (press twice)',
                help_index : 'ha',
                count: 2,
                handler : function (event) {
                    that.notebook.kernel.interrupt();
                    return false;
                }
            },
            '0' : {
                help    : 'restart kernel (press twice)',
                help_index : 'hb',
                count: 2,
                handler : function (event) {
                    that.notebook.restart_kernel();
                    return false;
                }
            },
            'h' : {
                help    : 'keyboard shortcuts',
                help_index : 'ge',
                handler : function (event) {
                    that.quick_help.show_keyboard_shortcuts();
                    return false;
                }
            },
            'z' : {
                help    : 'undo last delete',
                help_index : 'ei',
                handler : function (event) {
                    that.notebook.undelete_cell();
                    return false;
                }
            },
            'shift-m' : {
                help    : 'merge cell below',
                help_index : 'ek',
                handler : function (event) {
                    that.notebook.merge_cell_below();
                    return false;
                }
            },
            'q' : {
                help    : 'close pager',
                help_index : 'gd',
                handler : function (event) {
                    that.pager.collapse();
                    return false;
                }
            },
        };
    };

    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.handle_keydown = function (event) {
        var notebook = this.notebook;

        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) {
                // ESC
                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) {
        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();
            }
        });
    };

    // For backwards compatability.
    IPython.KeyboardManager = KeyboardManager;

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