// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

cr.define('extensions', function() {
  'use strict';

  /** @enum {number} */
  const Key = {
    Comma: 188,
    Del: 46,
    Down: 40,
    End: 35,
    Escape: 27,
    Home: 36,
    Ins: 45,
    Left: 37,
    MediaNextTrack: 176,
    MediaPlayPause: 179,
    MediaPrevTrack: 177,
    MediaStop: 178,
    PageDown: 34,
    PageUp: 33,
    Period: 190,
    Right: 39,
    Space: 32,
    Tab: 9,
    Up: 38,
  };

  /**
   * Enum for whether we require modifiers of a keycode.
   * @enum {number}
   */
  const ModifierPolicy = {NOT_ALLOWED: 0, REQUIRED: 1};

  /**
   * Gets the ModifierPolicy. Currently only "MediaNextTrack", "MediaPrevTrack",
   * "MediaStop", "MediaPlayPause" are required to be used without any modifier.
   * @param {number} keyCode
   * @return {ModifierPolicy}
   */
  function getModifierPolicy(keyCode) {
    switch (keyCode) {
      case Key.MediaNextTrack:
      case Key.MediaPlayPause:
      case Key.MediaPrevTrack:
      case Key.MediaStop:
        return ModifierPolicy.NOT_ALLOWED;
      default:
        return ModifierPolicy.REQUIRED;
    }
  }

  /**
   * Returns whether the keyboard event has a key modifier, which could affect
   * how it's handled.
   * @param {!KeyboardEvent} e
   * @param {boolean} countShiftAsModifier Whether the 'Shift' key should be
   *     counted as modifier.
   * @return {boolean} True if the event has any modifiers.
   */
  function hasModifier(e, countShiftAsModifier) {
    return e.ctrlKey || e.altKey ||
        // Meta key is only relevant on Mac and CrOS, where we treat Command
        // and Search (respectively) as modifiers.
        (cr.isMac && e.metaKey) || (cr.isChromeOS && e.metaKey) ||
        (countShiftAsModifier && e.shiftKey);
  }

  /**
   * Checks whether the passed in |keyCode| is a valid extension command key.
   * @param {number} keyCode
   * @return {boolean} Whether the key is valid.
   */
  function isValidKeyCode(keyCode) {
    if (keyCode == Key.Escape) {
      return false;
    }
    for (const k in Key) {
      if (Key[k] == keyCode) {
        return true;
      }
    }
    return (keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) ||
        (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0));
  }

  /**
   * Converts a keystroke event to string form, ignoring invalid extension
   * commands.
   * @param {!KeyboardEvent} e
   * @return {string} The keystroke as a string.
   */
  function keystrokeToString(e) {
    const output = [];
    // TODO(devlin): Should this be i18n'd?
    if (cr.isMac && e.metaKey) {
      output.push('Command');
    }
    if (cr.isChromeOS && e.metaKey) {
      output.push('Search');
    }
    if (e.ctrlKey) {
      output.push('Ctrl');
    }
    if (!e.ctrlKey && e.altKey) {
      output.push('Alt');
    }
    if (e.shiftKey) {
      output.push('Shift');
    }

    const keyCode = e.keyCode;
    if (isValidKeyCode(keyCode)) {
      if ((keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) ||
          (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0))) {
        output.push(String.fromCharCode(keyCode));
      } else {
        switch (keyCode) {
          case Key.Comma:
            output.push('Comma');
            break;
          case Key.Del:
            output.push('Delete');
            break;
          case Key.Down:
            output.push('Down');
            break;
          case Key.End:
            output.push('End');
            break;
          case Key.Home:
            output.push('Home');
            break;
          case Key.Ins:
            output.push('Insert');
            break;
          case Key.Left:
            output.push('Left');
            break;
          case Key.MediaNextTrack:
            output.push('MediaNextTrack');
            break;
          case Key.MediaPlayPause:
            output.push('MediaPlayPause');
            break;
          case Key.MediaPrevTrack:
            output.push('MediaPrevTrack');
            break;
          case Key.MediaStop:
            output.push('MediaStop');
            break;
          case Key.PageDown:
            output.push('PageDown');
            break;
          case Key.PageUp:
            output.push('PageUp');
            break;
          case Key.Period:
            output.push('Period');
            break;
          case Key.Right:
            output.push('Right');
            break;
          case Key.Space:
            output.push('Space');
            break;
          case Key.Tab:
            output.push('Tab');
            break;
          case Key.Up:
            output.push('Up');
            break;
        }
      }
    }

    return output.join('+');
  }

  /**
   * Returns true if the event has valid modifiers.
   * @param {!KeyboardEvent} e The keyboard event to consider.
   * @return {boolean} True if the event is valid.
   */
  function hasValidModifiers(e) {
    switch (getModifierPolicy(e.keyCode)) {
      case ModifierPolicy.REQUIRED:
        return hasModifier(e, false);
      case ModifierPolicy.NOT_ALLOWED:
        return !hasModifier(e, true);
    }
    assertNotReached();
  }

  return {
    isValidKeyCode: isValidKeyCode,
    keystrokeToString: keystrokeToString,
    hasValidModifiers: hasValidModifiers,
    Key: Key,
  };
});
