# Source: composer/key-composer.js

    /**
     * @module montage/composer/key-composer
     * @requires montage/core/core
     * @requires montage/composer/composer
     */
    var Montage = require("../core/core").Montage,
        Composer = require("./composer").Composer;

    /**
     * @class KeyManagerProxy
     * @classdesc Provide a proxy for lazy load of KeyManager.
     * @extends Montage
     * @private
     */
    var _keyManagerProxy = null;

    var KeyManagerProxy = Montage.specialize({

        /**
         * @private
         */
        _defaultKeyManager: {
            value: null
        },

        /**
         * @private
         */
        _loadingDefaultKeyManager: {
            value: false
        },

        /**
         * @private
         */
        _keysToRegister : {
            value: []
        },

        /**
         * Register a `KeyComposer` with the default `KeyManager`.
         * @function
         * @param {Object} keyComposer. A key composer object.
         */
        registerKey: {
            value: function (keyComposer) {
                var thisRef = this;

                if (!this._defaultKeyManager) {
                    this._keysToRegister.push(keyComposer);
                    if (!this._loadingDefaultKeyManager) {
                        this._loadingDefaultKeyManager = true;

                        require.async("core/event/key-manager")
                        .then(function (module) {
                            var keyManager = thisRef._defaultKeyManager = module.defaultKeyManager;
                            thisRef._keysToRegister.forEach(function (keyComposer) {
                                keyManager.registerKey(keyComposer);
                            });
                            thisRef._keysToRegister.length = 0;
                        });
                    }
                } else {
                    // This will happend only if somebody uses a cached return
                    // value from KeyManagerProxy.defaultKeyManager
                    this._defaultKeyManager.registerKey(keyComposer);
                }
            }
        },

        /**
         * Unregister a `KeyComposer` with the default `KeyManager`.
         * @function
         * @param {Object} keyComposer. A key composer object.
         */
        unregisterKey: {
            value: function (keyComposer) {
                if (this._defaultKeyManager) {
                    this._defaultKeyManager.unregisterKey(keyComposer);
                }
            }
        }

    }, {

        /**
         * Return either the default `KeyManager` or its `KeyManagerProxy`.
         * @function
         * @returns {Object} `KeyManager` or `KeyManagerProxy`.
         */
        defaultKeyManager: {
            get: function () {
                if (!_keyManagerProxy) {
                    _keyManagerProxy = new KeyManagerProxy();
                }
                if (this._defaultKeyManager) {
                    return this._defaultKeyManager;
                } else {
                    return _keyManagerProxy;
                }
            }
        }
    });

    // Event types dispatched by KeyComposer
    var KEYPRESS_EVENT_TYPE = "keyPress",
        LONGKEYPRESS_EVENT_TYPE = "longKeyPress",
        KEYRELEASE_EVENT_TYPE = "keyRelease";

    /**
     * @class KeyComposer
     * @classdesc A `Composer` that makes it easy to listen for specific key
     * combinations and react to them.
     * @extends Composer
     * @fires keyPress
     * @fires longKeyPress
     * @fires keyRelease
     * @example
     * keyComposer = KeyComposer.createKey(textComponent, "command+z", "undo");
     * keyComposer.addEventListener("keyPress", undoManager);
     * // when command+z is pressed inside textComponent,
     * // undoManager.handleUndoKeyPress() will be called.
     */
    var KeyComposer = exports.KeyComposer = Composer.specialize( /** @lends KeyComposer# */ {

        _shouldDispatchEvent: {
            value: false
        },

        shouldDispatchLongPress: {
            value: false
        },

        _longPressTimeout: {
            value: null
        },

        _keyRegistered: {
            value: false
        },

        _keys:{
            value: null
        },

        /**
         * The keyboard shortcut to listen to. One alphanumeric character or named
         * non-alphanumeric key, possibly with modifiers connected by '+'. The full
         * list of normalized keys and modifiers is in `KeyManager`.  @example "j",
         * "shift+j", "command+shift+j", "backspace", "win+pipe"
         * @type {string}
         * @default null
         */
        keys: {
            get: function () {
                return this._keys;
            },
            set: function (keys) {
                if (this._keyRegistered) {
                    KeyManagerProxy.defaultKeyManager.unregisterKey(this);
                    this._keys = keys;
                    KeyManagerProxy.defaultKeyManager.registerKey(this);
                } else {
                    this._keys = keys;
                }
            }
        },

        load: {
            value: function () {
                // Only register the key if somebody is listening for, else let do
                // it later.
                // console.log("--- load", this.identifier);
                if (this._shouldDispatchEvent && !this._keyRegistered) {
                    KeyManagerProxy.defaultKeyManager.registerKey(this);
                    this._keyRegistered = true;
                }
            }
        },

        unload: {
            value: function () {
                KeyManagerProxy.defaultKeyManager.unregisterKey(this);
                this._keyRegistered = false;
            }
        },

        /**
         * Listen to find out when this `KeyComposer` detects a matching key press.
         * @function
         * @param {string} type Any of the following types: keyPress, longKeyPress
         * and keyRelease.
         * @param {Object|function} listener The listener object or function to
         * call when dispatching the event.
         * @param {boolean} useCapture Specify if the listener want to be called
         * during the capture phase of the event.
         */
        addEventListener: {
            value: function (type, listener, useCapture) {
                // Optimisation so that we don't dispatch an event if we do not need to
                // console.log("--- addEventListener", this.identifier);
                var component = this.component;

                Composer.prototype.addEventListener.call(this, type, listener, useCapture);

                if (type === KEYPRESS_EVENT_TYPE || type === LONGKEYPRESS_EVENT_TYPE || type === KEYRELEASE_EVENT_TYPE) {
                    this._shouldDispatchEvent = true;
                    if (type === LONGKEYPRESS_EVENT_TYPE) {
                        this._shouldDispatchLongPress = true;
                    }

                    if (this._isLoaded) {
                        if (!this._keyRegistered) {
                            KeyManagerProxy.defaultKeyManager.registerKey(this);
                            this._keyRegistered = true;
                        }
                    } else if (component && typeof component.addComposer !== "function") {
                        // this keyComposer is not associated with an element,
                        // let's make it a global key
                        if (!this.element) {
                            this.element = window;
                        }
                        // this keyComposer is not attached to a UI Component,
                        // let's load it manually
                        this.component.loadComposer(this);
                    }
                }
            }
        },

        /**
         * Called when a composer is part of a template serialization. Responsible
         * for calling `addComposer` on the component or calling `load` on the
         * composer.
         * @private
         */
        deserializedFromTemplate: {
            value: function () {
                var component = this.component;

                if (this.identifier === null) {
                    this.identifier = Montage.getInfoForObject(this).label;
                }

                if (component) {
                    if (typeof component.addComposer === "function") {
                        component.addComposer(this);
                    } else if (!this._isLoaded) {
                        // this keyComposer is not associated with an element,
                        // let's make it a global key
                        if (!this.element) {
                            this.element = window;
                        }
                        // this keyComposer is not attached to a UI Component,
                        // let's load it manually
                        this.component.loadComposer(this);
                    }
                }
            }
        }
    }, {

        /**
         * Constructs a `KeyComposer` to listen for a key combination on a
         * component.
         *
         * The composer will only respond to key events triggered by the DOM
         * elements inside its component or when its component is set as the
         * `activeTarget`.
         *
         * @param {Object} component The component to attach the `KeyComposer` to.
         * @param {Object} keys The key combination, possibly including modifier
         * keys.
         * @param {Object} identifier The identifier for events triggered by this
         * composer.
         * @returns {Object} the newly created `KeyComposer` Object
         */
        createKey: {
            value: function (component, keys, identifier) {
                var key = this;

                if (this === KeyComposer) {
                    // This function has been called without creating a new
                    // instance of KeyComposer first.
                    key = new KeyComposer();
                }

                if (!identifier) {
                    if (component.identifier) {
                        identifier = component.identifier + keys.toLowerCase().replace(/[ +]/g).toCapitalized();
                    } else {
                        identifier = keys.toLowerCase().replace(/[ +]/g);
                    }
                }
                key.keys = keys;
                key.identifier = identifier;

                // console.log("CREATING KEY:", component, key, key.identifier);

                component.addComposer(key);

                return key;
            }
        },

        /**
         * Constructs a `KeyComposer` listening for a key combination anywhere on
         * the page.
         *
         * The composer will respond to key events that bubble up to the `window`.
         *
         * @function
         * @param {Object} component. The component to attach the keyComposer to.
         * @param {Object} keys. The key sequence.
         * @param {Object} identifier. The identifier.
         * @returns {Object} the newly created KeyComposer Object
         */
        createGlobalKey: {
            value: function (component, keys, identifier) {
                var key = this;

                if (this === KeyComposer) {
                    // This function has been called without creating a new
                    // instance of KeyComposer first
                    key = new KeyComposer();
                }

                key.keys = keys;
                key.identifier = identifier;
                // console.log("CREATING GLOBAL KEY:", component, key);

                component.addComposerForElement(key, window);

                return key;
            }
        }

    });