package juloo.keyboard2;

import android.view.KeyEvent;
import java.util.HashMap;

public final class KeyValue implements Comparable<KeyValue>
{
  public static enum Event
  {
    CONFIG,
    SWITCH_TEXT,
    SWITCH_NUMERIC,
    SWITCH_EMOJI,
    SWITCH_BACK_EMOJI,
    SWITCH_CLIPBOARD,
    SWITCH_BACK_CLIPBOARD,
    CHANGE_METHOD_PICKER,
    CHANGE_METHOD_AUTO,
    ACTION,
    SWITCH_FORWARD,
    SWITCH_BACKWARD,
    SWITCH_GREEKMATH,
    CAPS_LOCK,
    SWITCH_VOICE_TYPING,
    SWITCH_VOICE_TYPING_CHOOSER,
  }

  // Must be evaluated in the reverse order of their values.
  public static enum Modifier
  {
    SHIFT,
    GESTURE,
    CTRL,
    ALT,
    META,
    DOUBLE_AIGU,
    DOT_ABOVE,
    DOT_BELOW,
    GRAVE,
    AIGU,
    CIRCONFLEXE,
    TILDE,
    CEDILLE,
    TREMA,
    HORN,
    HOOK_ABOVE,
    DOUBLE_GRAVE,
    SUPERSCRIPT,
    SUBSCRIPT,
    RING,
    CARON,
    MACRON,
    ORDINAL,
    ARROWS,
    BOX,
    OGONEK,
    SLASH,
    ARROW_RIGHT,
    BREVE,
    BAR,
    FN,
  } // Last is be applied first

  public static enum Editing
  {
    COPY,
    PASTE,
    CUT,
    SELECT_ALL,
    PASTE_PLAIN,
    UNDO,
    REDO,
    // Android context menu actions
    REPLACE,
    SHARE,
    ASSIST,
    AUTOFILL,
  }

  public static enum Placeholder
  {
    REMOVED,
    F11,
    F12,
    SHINDOT,
    SINDOT,
    OLE,
    METEG
  }

  public static enum Kind
  {
    Char, String, Keyevent, Event, Compose_pending, Hangul_initial,
    Hangul_medial, Modifier, Editing, Placeholder,
    Cursor_move, // Value is encoded as a 16-bit integer.
    Complex, // [_payload] is a [KeyValue.Complex], value is [Complex.Kind].
  }

  private static final int FLAGS_OFFSET = 19;
  private static final int KIND_OFFSET = 28;

  // Behavior flags.
  public static final int FLAG_LATCH = (1 << FLAGS_OFFSET << 0);
  // Key can be locked by typing twice
  public static final int FLAG_LOCK = (1 << FLAGS_OFFSET << 1);
  // Special keys are not repeated and don't clear latched modifiers.
  public static final int FLAG_SPECIAL = (1 << FLAGS_OFFSET << 2);
  // Whether the symbol should be greyed out. For example, keys that are not
  // part of the pending compose sequence.
  public static final int FLAG_GREYED = (1 << FLAGS_OFFSET << 3);
  // Rendering flags.
  public static final int FLAG_KEY_FONT = (1 << FLAGS_OFFSET << 4); // special font file
  public static final int FLAG_SMALLER_FONT = (1 << FLAGS_OFFSET << 5); // 25% smaller symbols
  public static final int FLAG_SECONDARY = (1 << FLAGS_OFFSET << 6); // dimmer
  // Used by [Pointers].
  // Free: (1 << FLAGS_OFFSET << 7)
  // Free: (1 << FLAGS_OFFSET << 8)

  // Ranges for the different components
  private static final int FLAGS_BITS =
    FLAG_LATCH | FLAG_LOCK | FLAG_SPECIAL | FLAG_GREYED | FLAG_KEY_FONT |
    FLAG_SMALLER_FONT | FLAG_SECONDARY;
  private static final int KIND_BITS = (0b1111 << KIND_OFFSET); // 4 bits wide
  private static final int VALUE_BITS = ~(FLAGS_BITS | KIND_BITS); // 20 bits wide

  static
  {
    check((FLAGS_BITS & KIND_BITS) == 0); // No overlap
    check((FLAGS_BITS | KIND_BITS | VALUE_BITS) == ~0); // No holes
    // No kind is out of range
    check((((Kind.values().length - 1) << KIND_OFFSET) & ~KIND_BITS) == 0);
  }

  /**
   * The symbol that is rendered on the keyboard as a [String].
   * Except for keys of kind:
   * - [String], this is also the string to output.
   * - [Complex], this is an instance of [KeyValue.Complex].
   */
  private final Object _payload;

  /** This field encodes three things: Kind, flags and value. */
  private final int _code;

  public Kind getKind()
  {
    return Kind.values()[(_code & KIND_BITS) >>> KIND_OFFSET];
  }

  public int getFlags()
  {
    return (_code & FLAGS_BITS);
  }

  public boolean hasFlagsAny(int has)
  {
    return ((_code & has) != 0);
  }

  /** The string to render on the keyboard.
      When [getKind() == Kind.String], also the string to send. */
  public String getString()
  {
    if (getKind() == Kind.Complex)
      return ((Complex)_payload).getSymbol();
    return (String)_payload;
  }

  /** Defined only when [getKind() == Kind.Char]. */
  public char getChar()
  {
    return (char)(_code & VALUE_BITS);
  }

  /** Defined only when [getKind() == Kind.Keyevent]. */
  public int getKeyevent()
  {
    return (_code & VALUE_BITS);
  }

  /** Defined only when [getKind() == Kind.Event]. */
  public Event getEvent()
  {
    return Event.values()[(_code & VALUE_BITS)];
  }

  /** Defined only when [getKind() == Kind.Modifier]. */
  public Modifier getModifier()
  {
    return Modifier.values()[(_code & VALUE_BITS)];
  }

  /** Defined only when [getKind() == Kind.Editing]. */
  public Editing getEditing()
  {
    return Editing.values()[(_code & VALUE_BITS)];
  }

  /** Defined only when [getKind() == Kind.Placeholder]. */
  public Placeholder getPlaceholder()
  {
    return Placeholder.values()[(_code & VALUE_BITS)];
  }

  /** Defined only when [getKind() == Kind.Compose_pending]. */
  public int getPendingCompose()
  {
    return (_code & VALUE_BITS);
  }

  /** Defined only when [getKind()] is [Kind.Hangul_initial] or
      [Kind.Hangul_medial]. */
  public int getHangulPrecomposed()
  {
    return (_code & VALUE_BITS);
  }

  /** Defined only when [getKind() == Kind.Cursor_move]. */
  public short getCursorMove()
  {
    return (short)(_code & VALUE_BITS);
  }

  /** Defined only when [getKind() == Kind.Complex]. */
  public Complex getComplex()
  {
    return (Complex)_payload;
  }

  /** Defined only when [getKind() == Kind.Complex]. */
  public Complex.Kind getComplexKind()
  {
    return Complex.Kind.values()[(_code & VALUE_BITS)];
  }

  /* Update the char and the symbol. */
  public KeyValue withChar(char c)
  {
    return new KeyValue(String.valueOf(c), Kind.Char, c, getFlags());
  }

  public KeyValue withKeyevent(int code)
  {
    return new KeyValue(getString(), Kind.Keyevent, code, getFlags());
  }

  public KeyValue withFlags(int f)
  {
    return new KeyValue(_payload, (_code & KIND_BITS), (_code & VALUE_BITS), f);
  }

  @Override
  public boolean equals(Object obj)
  {
    return sameKey((KeyValue)obj);
  }

  public int compareTo(KeyValue snd)
  {
    // Compare the kind and value first, then the flags.
    int d = (_code & ~FLAGS_BITS) - (snd._code & ~FLAGS_BITS);
    if (d != 0)
      return d;
    d = _code - snd._code;
    if (d != 0)
      return d;
    if (getKind() == Kind.Complex)
      return ((Complex)_payload).compareTo((Complex)snd._payload);
    return ((String)_payload).compareTo((String)snd._payload);
  }

  /** Type-safe alternative to [equals]. */
  public boolean sameKey(KeyValue snd)
  {
    if (snd == null)
      return false;
    return _code == snd._code && _payload.equals(snd._payload);
  }

  @Override
  public int hashCode()
  {
    return _payload.hashCode() + _code;
  }

  public String toString()
  {
    int value = _code & VALUE_BITS;
    return "[KeyValue " + getKind().toString() + "+" + getFlags() + "+" + value + " \"" + getString() + "\"]";
  }

  private KeyValue(Object p, int kind, int value, int flags)
  {
    _payload = p;
    _code = (kind & KIND_BITS) | (flags & FLAGS_BITS) | (value & VALUE_BITS);
  }

  public KeyValue(Complex p, Complex.Kind value, int flags)
  {
    this((Object)p, (Kind.Complex.ordinal() << KIND_OFFSET), value.ordinal(),
        flags);
  }

  public KeyValue(String p, Kind k, int v, int f)
  {
    this(p, (k.ordinal() << KIND_OFFSET), v, f);
  }

  private static KeyValue charKey(String symbol, char c, int flags)
  {
    return new KeyValue(symbol, Kind.Char, c, flags);
  }

  private static KeyValue charKey(int symbol, char c, int flags)
  {
    return charKey(String.valueOf((char)symbol), c, flags);
  }

  private static KeyValue modifierKey(String symbol, Modifier m, int flags)
  {
    if (symbol.length() > 1)
      flags |= FLAG_SMALLER_FONT;
    return new KeyValue(symbol, Kind.Modifier, m.ordinal(),
        FLAG_LATCH | FLAG_SPECIAL | FLAG_SECONDARY | flags);
  }

  private static KeyValue modifierKey(int symbol, Modifier m, int flags)
  {
    return modifierKey(String.valueOf((char)symbol), m, flags | FLAG_KEY_FONT);
  }

  private static KeyValue diacritic(int symbol, Modifier m)
  {
    return new KeyValue(String.valueOf((char)symbol), Kind.Modifier, m.ordinal(),
        FLAG_LATCH | FLAG_SPECIAL | FLAG_KEY_FONT);
  }

  private static KeyValue eventKey(String symbol, Event e, int flags)
  {
    return new KeyValue(symbol, Kind.Event, e.ordinal(), flags | FLAG_SPECIAL | FLAG_SECONDARY);
  }

  private static KeyValue eventKey(int symbol, Event e, int flags)
  {
    return eventKey(String.valueOf((char)symbol), e, flags | FLAG_KEY_FONT);
  }

  public static KeyValue keyeventKey(String symbol, int code, int flags)
  {
    return new KeyValue(symbol, Kind.Keyevent, code, flags | FLAG_SECONDARY);
  }

  public static KeyValue keyeventKey(int symbol, int code, int flags)
  {
    return keyeventKey(String.valueOf((char)symbol), code, flags | FLAG_KEY_FONT);
  }

  private static KeyValue editingKey(String symbol, Editing action, int flags)
  {
    return new KeyValue(symbol, Kind.Editing, action.ordinal(),
        flags | FLAG_SPECIAL | FLAG_SECONDARY);
  }

  private static KeyValue editingKey(String symbol, Editing action)
  {
    return editingKey(symbol, action, FLAG_SMALLER_FONT);
  }

  private static KeyValue editingKey(int symbol, Editing action)
  {
    return editingKey(String.valueOf((char)symbol), action, FLAG_KEY_FONT);
  }

  /** A key that moves the cursor [d] times to the right. If [d] is negative,
      it moves the cursor [abs(d)] times to the left. */
  public static KeyValue cursorMoveKey(int d)
  {
    int symbol = (d < 0) ? 0xE008 : 0xE006;
    return new KeyValue(String.valueOf((char)symbol), Kind.Cursor_move,
        ((short)d) & 0xFFFF,
        FLAG_SPECIAL | FLAG_SECONDARY | FLAG_KEY_FONT);
  }

  /** A key that do nothing but has a unique ID. */
  private static KeyValue placeholderKey(Placeholder id)
  {
    return new KeyValue("", Kind.Placeholder, id.ordinal(), 0);
  }

  public static KeyValue makeStringKey(String str)
  {
    return makeStringKey(str, 0);
  }

  public static KeyValue makeCharKey(char c)
  {
    return makeCharKey(c, null, 0);
  }

  public static KeyValue makeCharKey(char c, String symbol, int flags)
  {
    if (symbol == null)
      symbol = String.valueOf(c);
    return new KeyValue(symbol, Kind.Char, c, flags);
  }

  public static KeyValue makeCharKey(int symbol, char c, int flags)
  {
    return makeCharKey(c, String.valueOf((char)symbol), flags | FLAG_KEY_FONT);
  }

  public static KeyValue makeComposePending(String symbol, int state, int flags)
  {
    return new KeyValue(symbol, Kind.Compose_pending, state,
        flags | FLAG_LATCH);
  }

  public static KeyValue makeComposePending(int symbol, int state, int flags)
  {
    return makeComposePending(String.valueOf((char)symbol), state,
        flags | FLAG_KEY_FONT);
  }

  public static KeyValue makeHangulInitial(String symbol, int initial_idx)
  {
    return new KeyValue(symbol, Kind.Hangul_initial, initial_idx * 588 + 44032,
        FLAG_LATCH);
  }

  public static KeyValue makeHangulMedial(int precomposed, int medial_idx)
  {
    precomposed += medial_idx * 28;
    return new KeyValue(String.valueOf((char)precomposed), Kind.Hangul_medial,
        precomposed, FLAG_LATCH);
  }

  public static KeyValue makeHangulFinal(int precomposed, int final_idx)
  {
    precomposed += final_idx;
    return KeyValue.makeCharKey((char)precomposed);
  }

  public static KeyValue makeActionKey(String symbol)
  {
    return eventKey(symbol, Event.ACTION, FLAG_SMALLER_FONT);
  }

  /** Make a key that types a string. A char key is returned for a string of
      length 1. */
  public static KeyValue makeStringKey(String str, int flags)
  {
    if (str.length() == 1)
      return new KeyValue(str, Kind.Char, str.charAt(0), flags);
    else
      return new KeyValue(str, Kind.String, 0, flags | FLAG_SMALLER_FONT);
  }

  public static KeyValue makeStringKeyWithSymbol(String str, String symbol, int flags)
  {
    return new KeyValue(new Complex.StringWithSymbol(str, symbol),
        Complex.Kind.StringWithSymbol, flags);
  }

  /** Make a modifier key for passing to [KeyModifier]. */
  public static KeyValue makeInternalModifier(Modifier mod)
  {
    return new KeyValue("", Kind.Modifier, mod.ordinal(), 0);
  }

  public static KeyValue parseKeyDefinition(String str)
  {
    if (str.length() < 2 || str.charAt(0) != ':')
      return makeStringKey(str);
    try
    {
      return KeyValueParser.parse(str);
    }
    catch (KeyValueParser.ParseError _e)
    {
      return makeStringKey(str);
    }
  }

  /**
   * Return a key by its name. If the given name doesn't correspond to a key
   * defined in this function, it is passed to [parseStringKey] as a fallback.
   */
  public static KeyValue getKeyByName(String name)
  {
    switch (name)
    {
      /* These symbols have special meaning when in `srcs/layouts` and are
         escaped in standard layouts. The backslash is not stripped when parsed
         from the custom layout option. */
      case "\\?": return makeStringKey("?");
      case "\\#": return makeStringKey("#");
      case "\\@": return makeStringKey("@");
      case "\\\\": return makeStringKey("\\");

      /* Modifiers and dead-keys */
      case "shift": return modifierKey(0xE00A, Modifier.SHIFT, 0);
      case "ctrl": return modifierKey("Ctrl", Modifier.CTRL, 0);
      case "alt": return modifierKey("Alt", Modifier.ALT, 0);
      case "accent_aigu": return diacritic(0xE050, Modifier.AIGU);
      case "accent_caron": return diacritic(0xE051, Modifier.CARON);
      case "accent_cedille": return diacritic(0xE052, Modifier.CEDILLE);
      case "accent_circonflexe": return diacritic(0xE053, Modifier.CIRCONFLEXE);
      case "accent_grave": return diacritic(0xE054, Modifier.GRAVE);
      case "accent_macron": return diacritic(0xE055, Modifier.MACRON);
      case "accent_ring": return diacritic(0xE056, Modifier.RING);
      case "accent_tilde": return diacritic(0xE057, Modifier.TILDE);
      case "accent_trema": return diacritic(0xE058, Modifier.TREMA);
      case "accent_ogonek": return diacritic(0xE059, Modifier.OGONEK);
      case "accent_dot_above": return diacritic(0xE05A, Modifier.DOT_ABOVE);
      case "accent_double_aigu": return diacritic(0xE05B, Modifier.DOUBLE_AIGU);
      case "accent_slash": return diacritic(0xE05C, Modifier.SLASH);
      case "accent_arrow_right": return diacritic(0xE05D, Modifier.ARROW_RIGHT);
      case "accent_breve": return diacritic(0xE05E, Modifier.BREVE);
      case "accent_bar": return diacritic(0xE05F, Modifier.BAR);
      case "accent_dot_below": return diacritic(0xE060, Modifier.DOT_BELOW);
      case "accent_horn": return diacritic(0xE061, Modifier.HORN);
      case "accent_hook_above": return diacritic(0xE062, Modifier.HOOK_ABOVE);
      case "accent_double_grave": return diacritic(0xE063, Modifier.DOUBLE_GRAVE);
      case "superscript": return modifierKey("Sup", Modifier.SUPERSCRIPT, 0);
      case "subscript": return modifierKey("Sub", Modifier.SUBSCRIPT, 0);
      case "ordinal": return modifierKey("Ord", Modifier.ORDINAL, 0);
      case "arrows": return modifierKey("Arr", Modifier.ARROWS, 0);
      case "box": return modifierKey("Box", Modifier.BOX, 0);
      case "fn": return modifierKey("Fn", Modifier.FN, 0);
      case "meta": return modifierKey("Meta", Modifier.META, 0);

      /* Combining diacritics */
      /* Glyphs is the corresponding dead-key + 0x0100. */
      case "combining_dot_above": return makeCharKey(0xE15A, '\u0307', 0);
      case "combining_double_aigu": return makeCharKey(0xE15B, '\u030B', 0);
      case "combining_slash": return makeCharKey(0xE15C, '\u0337', 0);
      case "combining_arrow_right": return makeCharKey(0xE15D, '\u20D7', 0);
      case "combining_breve": return makeCharKey(0xE15E, '\u0306', 0);
      case "combining_bar": return makeCharKey(0xE15F, '\u0335', 0);
      case "combining_aigu": return makeCharKey(0xE150, '\u0301', 0);
      case "combining_caron": return makeCharKey(0xE151, '\u030C', 0);
      case "combining_cedille": return makeCharKey(0xE152, '\u0327', 0);
      case "combining_circonflexe": return makeCharKey(0xE153, '\u0302', 0);
      case "combining_grave": return makeCharKey(0xE154, '\u0300', 0);
      case "combining_macron": return makeCharKey(0xE155, '\u0304', 0);
      case "combining_ring": return makeCharKey(0xE156, '\u030A', 0);
      case "combining_tilde": return makeCharKey(0xE157, '\u0303', 0);
      case "combining_trema": return makeCharKey(0xE158, '\u0308', 0);
      case "combining_ogonek": return makeCharKey(0xE159, '\u0328', 0);
      case "combining_dot_below": return makeCharKey(0xE160, '\u0323', 0);
      case "combining_horn": return makeCharKey(0xE161, '\u031B', 0);
      case "combining_hook_above": return makeCharKey(0xE162, '\u0309', 0);
      /* Combining diacritics that do not have a corresponding dead keys start
         at 0xE200. */
      case "combining_vertical_tilde": return makeCharKey(0xE200, '\u033E', 0);
      case "combining_inverted_breve": return makeCharKey(0xE201, '\u0311', 0);
      case "combining_pokrytie": return makeCharKey(0xE202, '\u0487', 0);
      case "combining_slavonic_psili": return makeCharKey(0xE203, '\u0486', 0);
      case "combining_slavonic_dasia": return makeCharKey(0xE204, '\u0485', 0);
      case "combining_payerok": return makeCharKey(0xE205, '\uA67D', 0);
      case "combining_titlo": return makeCharKey(0xE206, '\u0483', 0);
      case "combining_vzmet": return makeCharKey(0xE207, '\uA66F', 0);
      case "combining_arabic_v": return makeCharKey(0xE208, '\u065A', 0);
      case "combining_arabic_inverted_v": return makeCharKey(0xE209, '\u065B', 0);
      case "combining_shaddah": return makeCharKey(0xE210, '\u0651', 0);
      case "combining_sukun": return makeCharKey(0xE211, '\u0652', 0);
      case "combining_fatha": return makeCharKey(0xE212, '\u064E', 0);
      case "combining_dammah": return makeCharKey(0xE213, '\u064F', 0);
      case "combining_kasra": return makeCharKey(0xE214, '\u0650', 0);
      case "combining_hamza_above": return makeCharKey(0xE215, '\u0654', 0);
      case "combining_hamza_below": return makeCharKey(0xE216, '\u0655', 0);
      case "combining_alef_above": return makeCharKey(0xE217, '\u0670', 0);
      case "combining_fathatan": return makeCharKey(0xE218, '\u064B', 0);
      case "combining_kasratan": return makeCharKey(0xE219, '\u064D', 0);
      case "combining_dammatan": return makeCharKey(0xE220, '\u064C', 0);
      case "combining_alef_below": return makeCharKey(0xE221, '\u0656', 0);

      /* Special event keys */
      case "config": return eventKey(0xE004, Event.CONFIG, FLAG_SMALLER_FONT);
      case "switch_text": return eventKey("ABC", Event.SWITCH_TEXT, FLAG_SMALLER_FONT);
      case "switch_numeric": return eventKey("123+", Event.SWITCH_NUMERIC, FLAG_SMALLER_FONT);
      case "switch_emoji": return eventKey(0xE001, Event.SWITCH_EMOJI, FLAG_SMALLER_FONT);
      case "switch_back_emoji": return eventKey("ABC", Event.SWITCH_BACK_EMOJI, 0);
      case "switch_clipboard": return eventKey(0xE017, Event.SWITCH_CLIPBOARD, 0);
      case "switch_back_clipboard": return eventKey("ABC", Event.SWITCH_BACK_CLIPBOARD, 0);
      case "switch_forward": return eventKey(0xE013, Event.SWITCH_FORWARD, FLAG_SMALLER_FONT);
      case "switch_backward": return eventKey(0xE014, Event.SWITCH_BACKWARD, FLAG_SMALLER_FONT);
      case "switch_greekmath": return eventKey("πλ∇¬", Event.SWITCH_GREEKMATH, FLAG_SMALLER_FONT);
      case "change_method": return eventKey(0xE009, Event.CHANGE_METHOD_PICKER, FLAG_SMALLER_FONT);
      case "change_method_prev": return eventKey(0xE009, Event.CHANGE_METHOD_AUTO, FLAG_SMALLER_FONT);
      case "action": return eventKey("Action", Event.ACTION, FLAG_SMALLER_FONT); // Will always be replaced
      case "capslock": return eventKey(0xE012, Event.CAPS_LOCK, 0);
      case "voice_typing": return eventKey(0xE015, Event.SWITCH_VOICE_TYPING, FLAG_SMALLER_FONT);
      case "voice_typing_chooser": return eventKey(0xE015, Event.SWITCH_VOICE_TYPING_CHOOSER, FLAG_SMALLER_FONT);

      /* Key events */
      case "esc": return keyeventKey("Esc", KeyEvent.KEYCODE_ESCAPE, FLAG_SMALLER_FONT);
      case "enter": return keyeventKey(0xE00E, KeyEvent.KEYCODE_ENTER, 0);
      case "up": return keyeventKey(0xE005, KeyEvent.KEYCODE_DPAD_UP, 0);
      case "right": return keyeventKey(0xE006, KeyEvent.KEYCODE_DPAD_RIGHT, 0);
      case "down": return keyeventKey(0xE007, KeyEvent.KEYCODE_DPAD_DOWN, 0);
      case "left": return keyeventKey(0xE008, KeyEvent.KEYCODE_DPAD_LEFT, 0);
      case "page_up": return keyeventKey(0xE002, KeyEvent.KEYCODE_PAGE_UP, 0);
      case "page_down": return keyeventKey(0xE003, KeyEvent.KEYCODE_PAGE_DOWN, 0);
      case "home": return keyeventKey(0xE00B, KeyEvent.KEYCODE_MOVE_HOME, FLAG_SMALLER_FONT);
      case "end": return keyeventKey(0xE00C, KeyEvent.KEYCODE_MOVE_END, FLAG_SMALLER_FONT);
      case "backspace": return keyeventKey(0xE011, KeyEvent.KEYCODE_DEL, 0);
      case "delete": return keyeventKey(0xE010, KeyEvent.KEYCODE_FORWARD_DEL, 0);
      case "insert": return keyeventKey("Ins", KeyEvent.KEYCODE_INSERT, FLAG_SMALLER_FONT);
      case "f1": return keyeventKey("F1", KeyEvent.KEYCODE_F1, 0);
      case "f2": return keyeventKey("F2", KeyEvent.KEYCODE_F2, 0);
      case "f3": return keyeventKey("F3", KeyEvent.KEYCODE_F3, 0);
      case "f4": return keyeventKey("F4", KeyEvent.KEYCODE_F4, 0);
      case "f5": return keyeventKey("F5", KeyEvent.KEYCODE_F5, 0);
      case "f6": return keyeventKey("F6", KeyEvent.KEYCODE_F6, 0);
      case "f7": return keyeventKey("F7", KeyEvent.KEYCODE_F7, 0);
      case "f8": return keyeventKey("F8", KeyEvent.KEYCODE_F8, 0);
      case "f9": return keyeventKey("F9", KeyEvent.KEYCODE_F9, 0);
      case "f10": return keyeventKey("F10", KeyEvent.KEYCODE_F10, 0);
      case "f11": return keyeventKey("F11", KeyEvent.KEYCODE_F11, FLAG_SMALLER_FONT);
      case "f12": return keyeventKey("F12", KeyEvent.KEYCODE_F12, FLAG_SMALLER_FONT);
      case "tab": return keyeventKey(0xE00F, KeyEvent.KEYCODE_TAB, FLAG_SMALLER_FONT);
      case "menu": return keyeventKey("Menu", KeyEvent.KEYCODE_MENU, FLAG_SMALLER_FONT);
      case "scroll_lock": return keyeventKey("Scrl", KeyEvent.KEYCODE_SCROLL_LOCK, FLAG_SMALLER_FONT);

      /* Spaces */
      case "\\t": return charKey("\\t", '\t', 0); // Send the tab character
      case "\\n": return charKey("\\n", '\n', 0); // Send the newline character
      case "space": return charKey(0xE00D, ' ', FLAG_KEY_FONT | FLAG_SMALLER_FONT | FLAG_GREYED);
      case "nbsp": return charKey("\u237d", '\u00a0', FLAG_SMALLER_FONT);
      case "nnbsp": return charKey("\u2423", '\u202F', FLAG_SMALLER_FONT);

      /* bidi */
      case "lrm": return charKey("↱", '\u200e', 0); // Send left-to-right mark
      case "rlm": return charKey("↰", '\u200f', 0); // Send right-to-left mark
      case "b(": return charKey("(", ')', 0);
      case "b)": return charKey(")", '(', 0);
      case "b[": return charKey("[", ']', 0);
      case "b]": return charKey("]", '[', 0);
      case "b{": return charKey("{", '}', 0);
      case "b}": return charKey("}", '{', 0);
      case "blt": return charKey("<", '>', 0);
      case "bgt": return charKey(">", '<', 0);

      /* hebrew niqqud */
      case "qamats": return charKey("\u05E7\u05B8", '\u05B8', 0); // kamatz
      case "patah": return charKey("\u05E4\u05B7", '\u05B7', 0); // patach
      case "sheva": return charKey("\u05E9\u05B0", '\u05B0', 0);
      case "dagesh": return charKey("\u05D3\u05BC", '\u05BC', 0); // or mapiq
      case "hiriq": return charKey("\u05D7\u05B4", '\u05B4', 0);
      case "segol": return charKey("\u05E1\u05B6", '\u05B6', 0);
      case "tsere": return charKey("\u05E6\u05B5", '\u05B5', 0);
      case "holam": return charKey("\u05D5\u05B9", '\u05B9', 0);
      case "qubuts": return charKey("\u05E7\u05BB", '\u05BB', 0); // kubuts
      case "hataf_patah": return charKey("\u05D7\u05B2\u05E4\u05B7", '\u05B2', 0); // reduced patach
      case "hataf_qamats": return charKey("\u05D7\u05B3\u05E7\u05B8", '\u05B3', 0); // reduced kamatz
      case "hataf_segol": return charKey("\u05D7\u05B1\u05E1\u05B6", '\u05B1', 0); // reduced segol
      case "shindot": return charKey("\u05E9\u05C1", '\u05C1', 0);
      case "shindot_placeholder": return placeholderKey(Placeholder.SHINDOT);
      case "sindot": return charKey("\u05E9\u05C2", '\u05C2', 0);
      case "sindot_placeholder": return placeholderKey(Placeholder.SINDOT);
      /* hebrew punctuation */
      case "geresh": return charKey("\u05F3", '\u05F3', 0);
      case "gershayim": return charKey("\u05F4", '\u05F4', 0);
      case "maqaf": return charKey("\u05BE", '\u05BE', 0);
      /* hebrew biblical */
      case "rafe": return charKey("\u05E4\u05BF", '\u05BF', 0);
      case "ole": return charKey("\u05E2\u05AB", '\u05AB', 0);
      case "ole_placeholder": return placeholderKey(Placeholder.OLE);
      case "meteg": return charKey("\u05DE\u05BD", '\u05BD', 0); // or siluq or sof-pasuq
      case "meteg_placeholder": return placeholderKey(Placeholder.METEG);
      /* intending/preventing ligature - supported by many scripts*/
      case "zwj": return charKey("zwj", '\u200D', 0); // zero-width joiner (provides ligature)
      case "zwnj":
      case "halfspace": return charKey("⸽", '\u200C', 0); // zero-width non joiner

      /* Editing keys */
      case "copy": return editingKey(0xE030, Editing.COPY);
      case "paste": return editingKey(0xE032, Editing.PASTE);
      case "cut": return editingKey(0xE031, Editing.CUT);
      case "selectAll": return editingKey(0xE033, Editing.SELECT_ALL);
      case "shareText": return editingKey(0xE034, Editing.SHARE);
      case "pasteAsPlainText": return editingKey(0xE035, Editing.PASTE_PLAIN);
      case "undo": return editingKey(0xE036, Editing.UNDO);
      case "redo": return editingKey(0xE037, Editing.REDO);
      case "cursor_left": return cursorMoveKey(-1);
      case "cursor_right": return cursorMoveKey(1);
      // These keys are not used
      case "replaceText": return editingKey("repl", Editing.REPLACE);
      case "textAssist": return editingKey(0xE038, Editing.ASSIST);
      case "autofill": return editingKey("auto", Editing.AUTOFILL);

      /* The compose key */
      case "compose": return makeComposePending(0xE016, ComposeKeyData.compose, FLAG_SECONDARY | FLAG_SMALLER_FONT | FLAG_SPECIAL);

      /* Placeholder keys */
      case "removed": return placeholderKey(Placeholder.REMOVED);
      case "f11_placeholder": return placeholderKey(Placeholder.F11);
      case "f12_placeholder": return placeholderKey(Placeholder.F12);

      // Korean Hangul
      case "ㄱ": return makeHangulInitial("ㄱ", 0);
      case "ㄲ": return makeHangulInitial("ㄲ", 1);
      case "ㄴ": return makeHangulInitial("ㄴ", 2);
      case "ㄷ": return makeHangulInitial("ㄷ", 3);
      case "ㄸ": return makeHangulInitial("ㄸ", 4);
      case "ㄹ": return makeHangulInitial("ㄹ", 5);
      case "ㅁ": return makeHangulInitial("ㅁ", 6);
      case "ㅂ": return makeHangulInitial("ㅂ", 7);
      case "ㅃ": return makeHangulInitial("ㅃ", 8);
      case "ㅅ": return makeHangulInitial("ㅅ", 9);
      case "ㅆ": return makeHangulInitial("ㅆ", 10);
      case "ㅇ": return makeHangulInitial("ㅇ", 11);
      case "ㅈ": return makeHangulInitial("ㅈ", 12);
      case "ㅉ": return makeHangulInitial("ㅉ", 13);
      case "ㅊ": return makeHangulInitial("ㅊ", 14);
      case "ㅋ": return makeHangulInitial("ㅋ", 15);
      case "ㅌ": return makeHangulInitial("ㅌ", 16);
      case "ㅍ": return makeHangulInitial("ㅍ", 17);
      case "ㅎ": return makeHangulInitial("ㅎ", 18);

      /* Tamil letters should be smaller on the keyboard. */
      case "ஔ": case "ந": case "ல": case "ழ": case "௯": case "க":
      case "ஷ": case "ே": case "௨": case "ஜ": case "ங": case "ன":
      case "௦": case "ை": case "ூ": case "ம": case "ஆ": case "௭":
      case "௪": case "ா": case "ஶ": case "௬": case "வ": case "ஸ":
      case "௮": case "ட": case "ப": case "ஈ": case "௩": case "ஒ":
      case "ௌ": case "உ": case "௫": case "ய": case "ர": case "ு":
      case "இ": case "ோ": case "ஓ": case "ஃ": case "ற": case "த":
      case "௧": case "ண": case "ஏ": case "ஊ": case "ொ": case "ஞ":
      case "அ": case "எ": case "ச": case "ெ": case "ஐ": case "ி":
      case "௹": case "ள": case "ஹ": case "௰": case "ௐ": case "௱":
      case "௲": case "௳":
        return makeStringKey(name, FLAG_SMALLER_FONT);

      /* The key is not one of the special ones. */
      default: return parseKeyDefinition(name);
    }
  }

  // Substitute for [assert], which has no effect on Android.
  private static void check(boolean b)
  {
    if (!b)
      throw new RuntimeException("Assertion failure");
  }

  public static abstract class Complex
  {
    public abstract String getSymbol();

    /** [compareTo] can assume that [snd] is an instance of the same class. */
    public abstract int compareTo(Complex snd);

    public boolean equals(Object snd)
    {
      if (snd instanceof Complex)
        return compareTo((Complex)snd) == 0;
      return false;
    }

    /** [hashCode] will be called on this class. */

    /** The kind is stored in the [value] field of the key. */
    public static enum Kind
    {
      StringWithSymbol,
    }

    public static final class StringWithSymbol extends Complex
    {
      public final String str;
      private final String _symbol;

      public StringWithSymbol(String _str, String _sym)
      {
        str = _str;
        _symbol = _sym;
      }

      public String getSymbol() { return _symbol; }

      public int compareTo(Complex _snd)
      {
        StringWithSymbol snd = (StringWithSymbol)_snd;
        int d = str.compareTo(snd.str);
        if (d != 0) return d;
        return _symbol.compareTo(snd._symbol);
      }
    }
  };
}
