/*
 * KeyboardHelper.java
 *
 * Copyright (C) 2022 by Posit Software, PBC
 *
 * Unless you have received this program directly from Posit Software pursuant
 * to the terms of a commercial license agreement with Posit Software, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
package org.rstudio.core.client.command;

import org.rstudio.core.client.BrowseCap;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;

public class KeyboardHelper
{
   public static boolean isHyphen(NativeEvent event)
   {
      if (KeyboardShortcut.getModifierValue(event) != KeyboardShortcut.NONE)
         return false;

      return isHyphenKeycode(event.getKeyCode());
   }

   public static boolean isHyphenKeycode(NativeEvent event)
   {
      return isHyphenKeycode(event.getKeyCode());
   }

   public static boolean isHyphenKeycode(int keyCode)
   {
      if (BrowseCap.isFirefox())
         return keyCode == 173;
      else
         return keyCode == 189;
   }

   public static boolean isNavigationalKeycode(int keycode)
   {
      return (keycode == KeyCodes.KEY_TAB ||
              keycode == KeyCodes.KEY_ENTER ||
              keycode == KeyCodes.KEY_ESCAPE ||
              keycode == KeyCodes.KEY_HOME ||
              keycode == KeyCodes.KEY_END ||
              keycode == KeyCodes.KEY_RIGHT ||
              keycode == KeyCodes.KEY_LEFT);
   }

   public static boolean isControlKeycode(int keycode)
   {
      return keycode < 32;
   }

   public static boolean isUnderscore(NativeEvent event)
   {
      return event.getShiftKey() && isHyphenKeycode(event.getKeyCode());
   }

   public static boolean isPeriod(NativeEvent event)
   {
      return !event.getShiftKey() && isPeriodKeycode(event.getKeyCode());
   }

   public static boolean isPeriodKeycode(int keyCode)
   {
      return keyCode == 190;
   }

   public static boolean isModifierKey(int keyCode)
   {
      switch (keyCode)
      {
      case KeyCodes.KEY_CTRL:
      case KeyCodes.KEY_ALT:
      case KeyCodes.KEY_SHIFT:
      case KeyCodes.KEY_WIN_IME:
      case KeyCodes.KEY_WIN_KEY:
      case KeyCodes.KEY_WIN_KEY_FF_LINUX:
      case KeyCodes.KEY_WIN_KEY_RIGHT:
      case KeyCodes.KEY_WIN_KEY_LEFT_META:
         return true;
      case KeyCodes.KEY_CONTEXT_MENU: // right meta on macOS
         return !BrowseCap.isWindows();
      default:
         return false;
      }
   }

   public static String keyNameFromKeyCode(int keyCode)
   {
      return keyNameFromKeyCode(keyCode, KEY_CODE_TO_KEY_NAME_MAP);
   }

   private static final native String keyNameFromKeyCode(int keyCode, JavaScriptObject map)
   /*-{
      return map[keyCode] || String.fromCharCode(keyCode);
   }-*/;

   public static int keyCodeFromKeyName(String keyName)
   {
      return keyCodeFromKeyName(keyName, KEY_NAME_TO_KEY_CODE_MAP);
   }

   private static final native int keyCodeFromKeyName(String keyName, JavaScriptObject map)
   /*-{
      return map[keyName.toLowerCase()] || -1;
   }-*/;

   private static final native JavaScriptObject makeKeyCodeToKeyNameMap()
   /*-{
      // Map array indices as key codes to corresponding name.
      var map = new Array(256);

      map[8] = "Backspace";
      map[9] = "Tab";
      map[12] = "NumLock";
      map[13] = "Enter";
      map[16] = "Shift";
      map[17] = "Ctrl";
      map[18] = "Alt";
      map[19] = "Pause";
      map[20] = "CapsLock";
      map[27] = "Escape";
      map[32] = "Space";
      map[33] = "PageUp";
      map[34] = "PageDown";
      map[35] = "End";
      map[36] = "Home";
      map[37] = "Left";
      map[38] = "Up";
      map[39] = "Right";
      map[40] = "Down";
      map[45] = "Insert";
      map[46] = "Delete";

      // Add in numbers 0-9
      for (var i = 48; i <= 57; i++)
         map[i] = "" + (i - 48);

      // Add in letters
      for (var i = 65; i <= 90; i++)
         map[i] = String.fromCharCode(i);

      // TODO: Treat these keys differently?
      map[91] = "Meta";
      map[92] = "Meta";
      map[93] = "Meta";

      for (var i = 96; i <= 105; i++)
         map[i] = "NumPad" + (i - 96);

      map[106] = "*";
      map[107] = "+";
      map[109] = "-";

     // NOTE: This is actually 'decimal point' which is
     // distinct as a keycode from '.', but probably easier
     // to just treat them the same.
      map[110] = "."

      map[111] = "/";

      // Function keys (full Mac keyboard supports up to F19, some keyboard go up to F24)
      for (var i = 112; i <= 135; i++)
         map[i] = "F" + (i - 111);

      map[144] = "NumLock";
      map[145] = "ScrollLock";
      map[173] = "-";
      map[186] = ";";
      map[187] = "=";
      map[188] = ",";
      map[189] = "-";
      map[190] = ".";
      map[191] = "/";
      map[192] = "`";
      map[219] = "[";
      map[220] = "\\";
      map[221] = "]";
      map[222] = "'";

      return map;
   }-*/;

   private static final native JavaScriptObject makeKeyNameToKeyCodeMap(JavaScriptObject map)
   /*-{

      var result = {};
      for (var key in map) {
         var value = map[key];
         result[value.toLowerCase()] = parseInt(key);
      }
      return result;

   }-*/;

   private static final JavaScriptObject KEY_CODE_TO_KEY_NAME_MAP =
         makeKeyCodeToKeyNameMap();

   private static final JavaScriptObject KEY_NAME_TO_KEY_CODE_MAP =
         makeKeyNameToKeyCodeMap(KEY_CODE_TO_KEY_NAME_MAP);

   public static final int KEY_HYPHEN = BrowseCap.isFirefox() ? 173 : 189;
}
