/**
 * Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind navigation keys to
 * function calls that will get called when the keys are pressed, providing an easy way to implement custom navigation
 * schemes for any UI component.
 *
 * The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
 * pageUp, pageDown, del, backspace, home, end.
 *
 * Usage:
 *
 *     var nav = new Ext.util.KeyNav({
 *         target : "my-element",
 *         left   : function(e){
 *             this.moveLeft(e.ctrlKey);
 *         },
 *         right  : function(e){
 *             this.moveRight(e.ctrlKey);
 *         },
 *         enter  : function(e){
 *             this.save();
 *         },
 *         
 *         // Binding may be a function specifiying fn, scope and defaultAction
 *         esc: {
 *             fn: this.onEsc,
 *             defaultEventAction: false
 *         },
 *         scope : this
 *     });
 */
Ext.define('Ext.util.KeyNav', {
    alternateClassName: 'Ext.KeyNav',
    
    requires: ['Ext.util.KeyMap'],
    
    statics: {
        keyOptions: {
            left: 37,
            right: 39,
            up: 38,
            down: 40,
            space: 32,
            pageUp: 33,
            pageDown: 34,
            del: 46,
            backspace: 8,
            home: 36,
            end: 35,
            enter: 13,
            esc: 27,
            tab: 9
        }
    },

    constructor: function(config) {
        var me = this;
        if (arguments.length === 2) {
            me.legacyConstructor.apply(me, arguments);
            return;
        }
        me.setConfig(config);
    },

    /**
     * @private
     * Old constructor signature.
     * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
     * @param {Object} config The config
     */
    legacyConstructor: function(el, config) {
        this.setConfig(Ext.apply({
            target: el
        }, config));
    },
    
    /**
     * Sets up a configuration for the KeyNav.
     * @private
     * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
     * @param {Object} config A configuration object as specified in the constructor.
     */
    setConfig: function(config) {
        var me = this,
            keymapCfg = {
                target: config.target,
                eventName: me.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : me.forceKeyDown, config.eventName)
            },
            map, keyCodes, defaultScope, keyName, binding;

        if (me.map) {
            me.map.destroy();
        }

        if (config.processEvent) {
            keymapCfg.processEvent = config.processEvent;
            keymapCfg.processEventScope = config.processEventScope||me;
        }
        map = me.map = new Ext.util.KeyMap(keymapCfg);
        keyCodes = Ext.util.KeyNav.keyOptions;
        defaultScope = config.scope || me;
        
        for (keyName in keyCodes) {
            if (keyCodes.hasOwnProperty(keyName)) {

                // There is a property named after a key name.
                // It may be a function or an binding spec containing handler, scope and defaultAction configs
                if (binding = config[keyName]) {
                    if (typeof binding === 'function') {
                        binding = {
                            handler: binding,
                            defaultAction: (config.defaultEventAction !== undefined) ? config.defaultEventAction : me.defaultEventAction
                        };
                    }
                    map.addBinding({
                        key: keyCodes[keyName],
                        handler: Ext.Function.bind(me.handleEvent, binding.scope||defaultScope, binding.handler||binding.fn, true),
                        defaultEventAction: (binding.defaultEventAction !== undefined) ? binding.defaultAction : me.defaultEventAction
                    });
                }
            }
        }
        
        map.disable();
        if (!config.disabled) {
            map.enable();
        }
    },
    
    /**
     * Method for filtering out the map argument
     * @private
     * @param {Number} keyCode
     * @param {Ext.EventObject} event
     * @param {Object} options Contains the handler to call
     */
    handleEvent: function(keyCode, event, handler){
        return handler.call(this, event);
    },
    
    /**
     * @cfg {Boolean} disabled
     * True to disable this KeyNav instance.
     */
    disabled: false,
    
    /**
     * @cfg {String} defaultEventAction
     * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are {@link
     * Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and {@link Ext.EventObject#stopPropagation}.
     *
     * If a falsy value is specified, no method is called on the key event.
     */
    defaultEventAction: "stopEvent",
    
    /**
     * @cfg {Boolean} forceKeyDown
     * Handle the keydown event instead of keypress. KeyNav automatically does this for IE since IE does not propagate
     * special keys on keypress, but setting this to true will force other browsers to also handle keydown instead of
     * keypress.
     */
    forceKeyDown: false,

    /**
     * @cfg {Ext.Component/Ext.Element/HTMLElement/String} target
     * The object on which to listen for the event specified by the {@link #eventName} config option.
     */

    /**
     * @cfg {String} eventName
     * The event to listen for to pick up key events.
     */
    eventName: 'keypress',

    /**
     * @cfg {Function} processEvent
     * An optional event processor function which accepts the argument list provided by the {@link #eventName configured
     * event} of the {@link #target}, and returns a keyEvent for processing by the KeyMap.
     *
     * This may be useful when the {@link #target} is a Component with s complex event signature. Extra information from
     * the event arguments may be injected into the event for use by the handler functions before returning it.
     */

    /**
     * @cfg {Object} [processEventScope=this]
     * The scope (`this` context) in which the {@link #processEvent} method is executed.
     */

    /**
     * Destroy this KeyNav (this is the same as calling disable).
     * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
     */
    destroy: function(removeEl){
        this.map.destroy(removeEl);
        delete this.map;
    },

    /**
     * Enables this KeyNav.
     */
    enable: function() {
        this.map.enable();
        this.disabled = false;
    },

    /**
     * Disables this KeyNav.
     */
    disable: function() {
        this.map.disable();
        this.disabled = true;
    },
    
    /**
     * Convenience function for setting disabled/enabled by boolean.
     * @param {Boolean} disabled
     */
    setDisabled : function(disabled){
        this.map.setDisabled(disabled);
        this.disabled = disabled;
    },
    
    /**
     * @private
     * Determines the event to bind to listen for keys. Defaults to the {@link #eventName} value, but
     * may be overridden the {@link #forceKeyDown} setting.
     *
     * The useKeyDown option on the EventManager modifies the default {@link #eventName} to be `keydown`,
     * but a configured {@link #eventName} takes priority over this.
     *
     * @return {String} The type of event to listen for.
     */
    getKeyEvent: function(forceKeyDown, configuredEventName){
        if (forceKeyDown || (Ext.EventManager.useKeyDown && !configuredEventName)) {
            return 'keydown';
        } else {
            return configuredEventName||this.eventName;
        }
    }
});