/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview Constant declarations for common key codes.
 *
 * @see ../demos/keyhandler.html
 */

goog.provide('goog.events.KeyCodes');

goog.require('goog.userAgent');
goog.requireType('goog.events.BrowserEvent');


/**
 * Key codes for common characters.
 *
 * This list is not localized and therefore some of the key codes are not
 * correct for non US keyboard layouts. See comments below.
 *
 * @enum {number}
 */
goog.events.KeyCodes = {
  WIN_KEY_FF_LINUX: 0,
  MAC_ENTER: 3,
  BACKSPACE: 8,
  TAB: 9,
  NUM_CENTER: 12,  // NUMLOCK on FF/Safari Mac
  ENTER: 13,
  SHIFT: 16,
  CTRL: 17,
  ALT: 18,
  PAUSE: 19,
  CAPS_LOCK: 20,
  ESC: 27,
  SPACE: 32,
  PAGE_UP: 33,    // also NUM_NORTH_EAST
  PAGE_DOWN: 34,  // also NUM_SOUTH_EAST
  END: 35,        // also NUM_SOUTH_WEST
  HOME: 36,       // also NUM_NORTH_WEST
  LEFT: 37,       // also NUM_WEST
  UP: 38,         // also NUM_NORTH
  RIGHT: 39,      // also NUM_EAST
  DOWN: 40,       // also NUM_SOUTH
  PLUS_SIGN: 43,  // NOT numpad plus
  PRINT_SCREEN: 44,
  INSERT: 45,  // also NUM_INSERT
  DELETE: 46,  // also NUM_DELETE
  ZERO: 48,
  ONE: 49,
  TWO: 50,
  THREE: 51,
  FOUR: 52,
  FIVE: 53,
  SIX: 54,
  SEVEN: 55,
  EIGHT: 56,
  NINE: 57,
  FF_SEMICOLON: 59,  // Firefox (Gecko) fires this for semicolon instead of 186
  FF_EQUALS: 61,     // Firefox (Gecko) fires this for equals instead of 187
  FF_DASH: 173,      // Firefox (Gecko) fires this for dash instead of 189
  // Firefox (Gecko) fires this for # on UK keyboards, rather than
  // Shift+SINGLE_QUOTE.
  FF_HASH: 163,
  // Firefox (Gecko) fires this for ' (:) on JP keyboards, rather than
  // SINGLE_QUOTE (US keyboard layout) or SEMICOLON (JP keyboard layout in
  // chrome)
  FF_JP_QUOTE: 58,
  FF_DE_PLUS: 171,    // Firefox (Gecko) fires this for +/* on DE keyboards
                      // rather than CLOSE_SQUARE_BRACKET (DE layout in Chrome)
  QUESTION_MARK: 63,  // needs localization
  AT_SIGN: 64,
  A: 65,
  B: 66,
  C: 67,
  D: 68,
  E: 69,
  F: 70,
  G: 71,
  H: 72,
  I: 73,
  J: 74,
  K: 75,
  L: 76,
  M: 77,
  N: 78,
  O: 79,
  P: 80,
  Q: 81,
  R: 82,
  S: 83,
  T: 84,
  U: 85,
  V: 86,
  W: 87,
  X: 88,
  Y: 89,
  Z: 90,
  META: 91,  // WIN_KEY_LEFT
  WIN_KEY_RIGHT: 92,
  CONTEXT_MENU: 93,
  NUM_ZERO: 96,
  NUM_ONE: 97,
  NUM_TWO: 98,
  NUM_THREE: 99,
  NUM_FOUR: 100,
  NUM_FIVE: 101,
  NUM_SIX: 102,
  NUM_SEVEN: 103,
  NUM_EIGHT: 104,
  NUM_NINE: 105,
  NUM_MULTIPLY: 106,
  NUM_PLUS: 107,
  NUM_MINUS: 109,
  NUM_PERIOD: 110,
  NUM_DIVISION: 111,
  F1: 112,
  F2: 113,
  F3: 114,
  F4: 115,
  F5: 116,
  F6: 117,
  F7: 118,
  F8: 119,
  F9: 120,
  F10: 121,
  F11: 122,
  F12: 123,
  NUMLOCK: 144,
  SCROLL_LOCK: 145,

  // OS-specific media keys like volume controls and browser controls.
  FIRST_MEDIA_KEY: 166,
  LAST_MEDIA_KEY: 183,

  SEMICOLON: 186,             // needs localization
  DASH: 189,                  // needs localization
  EQUALS: 187,                // needs localization
  COMMA: 188,                 // needs localization
  PERIOD: 190,                // needs localization
  SLASH: 191,                 // needs localization
  APOSTROPHE: 192,            // needs localization
  TILDE: 192,                 // needs localization
  SINGLE_QUOTE: 222,          // needs localization
  OPEN_SQUARE_BRACKET: 219,   // needs localization
  BACKSLASH: 220,             // needs localization
  CLOSE_SQUARE_BRACKET: 221,  // needs localization
  WIN_KEY: 224,
  MAC_FF_META:
      224,  // Firefox (Gecko) fires this for the meta key instead of 91
  MAC_WK_CMD_LEFT: 91,   // WebKit Left Command key fired, same as META
  MAC_WK_CMD_RIGHT: 93,  // WebKit Right Command key fired, different from META
  WIN_IME: 229,

  // "Reserved for future use". Some programs (e.g. the SlingPlayer 2.4 ActiveX
  // control) fire this as a hacky way to disable screensavers.
  VK_NONAME: 252,

  // We've seen users whose machines fire this keycode at regular one
  // second intervals. The common thread among these users is that
  // they're all using Dell Inspiron laptops, so we suspect that this
  // indicates a hardware/bios problem.
  // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
  PHANTOM: 255
};


/**
 * Returns false if the event does not contain a text modifying key.
 *
 * When it returns true, the event might be text modifying. It is infeasible to
 * say for sure because of the many different keyboard layouts, so this method
 * errs on the side of assuming a key event is text-modifiable if we cannot be
 * certain it is not. As an example, it will return true for ctrl+a, though in
 * many standard keyboard layouts that key combination would mean "select all",
 * and not actually modify the text.
 *
 * @param {goog.events.BrowserEvent} e A key event.
 * @return {boolean} Whether it's a text modifying key.
 */
goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) {
  'use strict';
  if (e.altKey && !e.ctrlKey || e.metaKey ||
      // Function keys don't generate text
      e.keyCode >= goog.events.KeyCodes.F1 &&
          e.keyCode <= goog.events.KeyCodes.F12) {
    return false;
  }

  if (goog.events.KeyCodes.isCharacterKey(e.keyCode)) {
    return true;
  }

  switch (e.keyCode) {
    // The following keys are quite harmless, even in combination with
    // CTRL, ALT or SHIFT.
    case goog.events.KeyCodes.ALT:
    case goog.events.KeyCodes.CAPS_LOCK:
    case goog.events.KeyCodes.CONTEXT_MENU:
    case goog.events.KeyCodes.CTRL:
    case goog.events.KeyCodes.DOWN:
    case goog.events.KeyCodes.END:
    case goog.events.KeyCodes.ESC:
    case goog.events.KeyCodes.HOME:
    case goog.events.KeyCodes.INSERT:
    case goog.events.KeyCodes.LEFT:
    case goog.events.KeyCodes.MAC_FF_META:
    case goog.events.KeyCodes.META:
    case goog.events.KeyCodes.NUMLOCK:
    case goog.events.KeyCodes.NUM_CENTER:
    case goog.events.KeyCodes.PAGE_DOWN:
    case goog.events.KeyCodes.PAGE_UP:
    case goog.events.KeyCodes.PAUSE:
    case goog.events.KeyCodes.PHANTOM:
    case goog.events.KeyCodes.PRINT_SCREEN:
    case goog.events.KeyCodes.RIGHT:
    case goog.events.KeyCodes.SCROLL_LOCK:
    case goog.events.KeyCodes.SHIFT:
    case goog.events.KeyCodes.UP:
    case goog.events.KeyCodes.VK_NONAME:
    case goog.events.KeyCodes.WIN_KEY:
    case goog.events.KeyCodes.WIN_KEY_RIGHT:
      return false;
    case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
      return !goog.userAgent.GECKO;
    default:
      return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY ||
          e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY;
  }
};


/**
 * Returns true if the key fires a keypress event in the current browser.
 *
 * Accoridng to MSDN [1] IE only fires keypress events for the following keys:
 * - Letters: A - Z (uppercase and lowercase)
 * - Numerals: 0 - 9
 * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
 * - System: ESC, SPACEBAR, ENTER
 *
 * That's not entirely correct though, for instance there's no distinction
 * between upper and lower case letters.
 *
 * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx)
 *
 * Safari is similar to IE, but does not fire keypress for ESC.
 *
 * Additionally, IE6 does not fire keydown or keypress events for letters when
 * the control or alt keys are held down and the shift key is not. IE7 does
 * fire keydown in these cases, though, but not keypress.
 *
 * @param {number} keyCode A key code.
 * @param {number=} opt_heldKeyCode Key code of a currently-held key.
 * @param {boolean=} opt_shiftKey Whether the shift key is held down.
 * @param {boolean=} opt_ctrlKey Whether the control key is held down.
 * @param {boolean=} opt_altKey Whether the alt key is held down.
 * @param {boolean=} opt_metaKey Whether the meta key is held down.
 * @return {boolean} Whether it's a key that fires a keypress event.
 */
goog.events.KeyCodes.firesKeyPressEvent = function(
    keyCode, opt_heldKeyCode, opt_shiftKey, opt_ctrlKey, opt_altKey,
    opt_metaKey) {
  'use strict';
  if (goog.userAgent.MAC && opt_altKey) {
    return goog.events.KeyCodes.isCharacterKey(keyCode);
  }

  // Alt but not AltGr which is represented as Alt+Ctrl.
  if (opt_altKey && !opt_ctrlKey) {
    return false;
  }

  // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress.
  // WebKit prior to 525 won't get this far so no need to check the user agent.
  // Gecko doesn't need to use the held key for modifiers, it just checks the
  // ctrl/meta/alt/shiftKey fields.
  if (!goog.userAgent.GECKO) {
    if (typeof opt_heldKeyCode === 'number') {
      opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode);
    }
    var heldKeyIsModifier = opt_heldKeyCode == goog.events.KeyCodes.CTRL ||
        opt_heldKeyCode == goog.events.KeyCodes.ALT ||
        goog.userAgent.MAC && opt_heldKeyCode == goog.events.KeyCodes.META;
    // The Shift key blocks keypresses on Mac iff accompanied by another
    // modifier.
    var modifiedShiftKey = opt_heldKeyCode == goog.events.KeyCodes.SHIFT &&
        (opt_ctrlKey || opt_metaKey);
    if ((!opt_shiftKey || goog.userAgent.MAC) && heldKeyIsModifier ||
        goog.userAgent.MAC && modifiedShiftKey) {
      return false;
    }
  }

  // Some keys with Ctrl/Shift do not issue keypress in WEBKIT.
  if ((goog.userAgent.WEBKIT || goog.userAgent.EDGE) && opt_ctrlKey &&
      opt_shiftKey) {
    switch (keyCode) {
      case goog.events.KeyCodes.BACKSLASH:
      case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
      case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
      case goog.events.KeyCodes.TILDE:
      case goog.events.KeyCodes.SEMICOLON:
      case goog.events.KeyCodes.DASH:
      case goog.events.KeyCodes.EQUALS:
      case goog.events.KeyCodes.COMMA:
      case goog.events.KeyCodes.PERIOD:
      case goog.events.KeyCodes.SLASH:
      case goog.events.KeyCodes.APOSTROPHE:
      case goog.events.KeyCodes.SINGLE_QUOTE:
        return false;
    }
  }

  // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it
  // continues to fire keydown events as the event repeats.
  if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) {
    return false;
  }

  switch (keyCode) {
    case goog.events.KeyCodes.ENTER:
      if (goog.userAgent.GECKO) {
        // Only Enter, Shift + Enter, Ctrl + Enter causes keypress event on
        // Firefox.
        if (opt_metaKey || opt_altKey) {
          return false;
        }
        return !(opt_shiftKey && opt_ctrlKey);
      } else {
        return true;
      }
    case goog.events.KeyCodes.ESC:
      return !(
          goog.userAgent.WEBKIT || goog.userAgent.EDGE || goog.userAgent.GECKO);
  }

  // Gecko won't fire a keypress event even when the key is a character key if
  // ctrl, meta or alt are pressed. In all other cases, a keypress event is
  // only fired when the key is a character.
  if (goog.userAgent.GECKO && (opt_ctrlKey || opt_altKey || opt_metaKey)) {
    return false;
  } else {
    return goog.events.KeyCodes.isCharacterKey(keyCode);
  }
};


/**
 * Returns true if the key produces a character.
 * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).
 *
 * @param {number} keyCode A key code.
 * @return {boolean} Whether it's a character key.
 */
goog.events.KeyCodes.isCharacterKey = function(keyCode) {
  'use strict';
  if (keyCode >= goog.events.KeyCodes.ZERO &&
      keyCode <= goog.events.KeyCodes.NINE) {
    return true;
  }

  if (keyCode >= goog.events.KeyCodes.NUM_ZERO &&
      keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) {
    return true;
  }

  if (keyCode >= goog.events.KeyCodes.A && keyCode <= goog.events.KeyCodes.Z) {
    return true;
  }

  // Safari sends zero key code for non-latin characters.
  if ((goog.userAgent.WEBKIT || goog.userAgent.EDGE) && keyCode == 0) {
    return true;
  }

  switch (keyCode) {
    case goog.events.KeyCodes.SPACE:
    case goog.events.KeyCodes.PLUS_SIGN:
    case goog.events.KeyCodes.QUESTION_MARK:
    case goog.events.KeyCodes.AT_SIGN:
    case goog.events.KeyCodes.NUM_PLUS:
    case goog.events.KeyCodes.NUM_MINUS:
    case goog.events.KeyCodes.NUM_PERIOD:
    case goog.events.KeyCodes.NUM_DIVISION:
    case goog.events.KeyCodes.SEMICOLON:
    case goog.events.KeyCodes.FF_SEMICOLON:
    case goog.events.KeyCodes.DASH:
    case goog.events.KeyCodes.EQUALS:
    case goog.events.KeyCodes.FF_EQUALS:
    case goog.events.KeyCodes.COMMA:
    case goog.events.KeyCodes.PERIOD:
    case goog.events.KeyCodes.SLASH:
    case goog.events.KeyCodes.APOSTROPHE:
    case goog.events.KeyCodes.SINGLE_QUOTE:
    case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
    case goog.events.KeyCodes.BACKSLASH:
    case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
    case goog.events.KeyCodes.FF_HASH:
    case goog.events.KeyCodes.FF_JP_QUOTE:
      return true;
    case goog.events.KeyCodes.FF_DASH:
    case goog.events.KeyCodes.FF_DE_PLUS:
      return goog.userAgent.GECKO;
    default:
      return false;
  }
};


/**
 * Normalizes key codes from OS/Browser-specific value to the general one.
 * @param {number} keyCode The native key code.
 * @return {number} The normalized key code.
 */
goog.events.KeyCodes.normalizeKeyCode = function(keyCode) {
  'use strict';
  if (goog.userAgent.GECKO) {
    return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode);
  } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) {
    return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode);
  } else {
    return keyCode;
  }
};


/**
 * Normalizes key codes from their Gecko-specific value to the general one.
 * @param {number} keyCode The native key code.
 * @return {number} The normalized key code.
 */
goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) {
  'use strict';
  switch (keyCode) {
    case goog.events.KeyCodes.FF_EQUALS:
      return goog.events.KeyCodes.EQUALS;
    case goog.events.KeyCodes.FF_SEMICOLON:
      return goog.events.KeyCodes.SEMICOLON;
    case goog.events.KeyCodes.FF_DASH:
      return goog.events.KeyCodes.DASH;
    case goog.events.KeyCodes.MAC_FF_META:
      return goog.events.KeyCodes.META;
    case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
      return goog.events.KeyCodes.WIN_KEY;
    default:
      return keyCode;
  }
};


/**
 * Normalizes key codes from their Mac WebKit-specific value to the general one.
 * @param {number} keyCode The native key code.
 * @return {number} The normalized key code.
 */
goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) {
  'use strict';
  switch (keyCode) {
    case goog.events.KeyCodes.MAC_WK_CMD_RIGHT:  // 93
      return goog.events.KeyCodes.META;          // 91
    default:
      return keyCode;
  }
};
