/**
 * @provides phabricator-keyboard-shortcut-manager
 * @requires javelin-install
 *           javelin-util
 *           javelin-stratcom
 *           javelin-dom
 *           javelin-vector
 * @javelin
 */

JX.install('KeyboardShortcutManager', {

  construct : function() {
    this._shortcuts = [];

    JX.Stratcom.listen('keypress', null, JX.bind(this, this._onkeypress));
    JX.Stratcom.listen('keydown', null, JX.bind(this, this._onkeydown));
    JX.Stratcom.listen('keyup', null, JX.bind(this, this._onkeyup));

    var onelement = JX.bind(this, this._onelement);
    JX.Stratcom.listen('click', 'has-key-command', onelement);
  },

  statics : {
    _instance : null,

    /**
     * Some keys don't invoke keypress events in some browsers. We handle these
     * on keydown instead of keypress.
     */
    _downkeys : {
      left: 1,
      right: 1,
      up: 1,
      down: 1
    },

    /**
     * Some keys require Alt to be pressed in order to type them on certain
     * keyboard layouts.
     */
    _altkeys: {
      // "Alt+L" on German layouts.
      '@': 1,

      // "Alt+Shift+7" on German layouts.
      '\\': 1
    },

    getInstance : function() {
      if (!JX.KeyboardShortcutManager._instance) {
        JX.KeyboardShortcutManager._instance = new JX.KeyboardShortcutManager();
      }
      return JX.KeyboardShortcutManager._instance;
    }
  },

  members : {
    _shortcuts : null,

    /**
     * Instead of calling this directly, you should call
     * KeyboardShortcut.register().
     */
    addKeyboardShortcut : function(s) {
      this._shortcuts.push(s);
    },
    getShortcutDescriptions : function() {
      var desc = [];
      for (var ii = 0; ii < this._shortcuts.length; ii++) {
        var shortcut = this._shortcuts[ii];
        desc.push({
          keys : shortcut.getKeys(),
          group: shortcut.getGroup(),
          description : shortcut.getDescription()
        });
      }
      return desc;
    },

    /**
     * Scroll an element into view.
     */
    scrollTo : function(node) {
      var scroll_distance = JX.Vector.getAggregateScrollForNode(node);
      var node_position = JX.$V(node);
      JX.DOM.scrollToPosition(0, node_position.y + scroll_distance.y - 60);
    },

    _onkeypress : function(e) {
      if (!(this._getKey(e) in JX.KeyboardShortcutManager._downkeys)) {
        this._onkeyhit(e);
      }
    },
    _onkeyhit : function(e) {
      var self = JX.KeyboardShortcutManager;

      var raw = e.getRawEvent();

      if (raw.ctrlKey || raw.metaKey) {
        // Never activate keyboard shortcuts if modifier keys are also
        // depressed.
        return;
      }

      // For most keystrokes, don't activate keyboard shortcuts if the Alt
      // key is depressed. However, we continue if the character requires the
      // use of Alt to type it on some keyboard layouts.
      var key = this._getKey(e);
      if (raw.altKey && !(key in self._altkeys)) {
        return;
      }

      var target = e.getTarget();
      var ignore = ['input', 'select', 'textarea', 'object', 'embed'];
      if (JX.DOM.isType(target, ignore)) {
        // Never activate keyboard shortcuts if the user has some other control
        // focused.
        return;
      }

      var key = this._getKey(e);

      var handled = this._handleKey(key);

      if (handled) {
        e.kill();
      }
    },

    _handleKey: function(key) {
      var shortcuts = this._shortcuts;

      for (var ii = 0; ii < shortcuts.length; ii++) {
        var keys = shortcuts[ii].getKeys();
        for (var jj = 0; jj < keys.length; jj++) {
          if (keys[jj] == key) {
            shortcuts[ii].getHandler()(this);
            return true;
          }
        }
      }

      return false;
    },

    _onkeydown : function(e) {
      this._handleTooltipKeyEvent(e, true);

      if (this._getKey(e) in JX.KeyboardShortcutManager._downkeys) {
        this._onkeyhit(e);
      }
    },
    _onkeyup : function(e) {
      this._handleTooltipKeyEvent(e, false);
    },
    _getKey : function(e) {
      return e.getSpecialKey() || String.fromCharCode(e.getRawEvent().charCode);
    },
    _onelement: function(e) {
      var data = e.getNodeData('has-key-command');
      this._handleKey(data.keyCommand);

      e.kill();
    },
    _handleTooltipKeyEvent : function(e, is_keydown) {
      if (e.getRawEvent().keyCode != 18) {
        // If this isn't the alt/option key, don't do anything.
        return;
      }
      // Fire all the shortcut handlers.
      var shortcuts = this._shortcuts;
      for (var ii = 0; ii < shortcuts.length; ii++) {
        var handler = shortcuts[ii].getTooltipHandler();
        handler && handler(this, is_keydown);
      }
    }

  }
});
