package org.unicode.cldr.draft.keyboard;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Functions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * Modifier keys used in the LDML Keyboard XML representation. A modifier key is pressed to change
 * the behavior of the keyboard. For example, pressing the Shift key on most Latin keyboards
 * produces upper-case variants of the characters.
 *
 * <p>
 * It is important NOT to change the ordering of the declared enum members because this enumeration
 * is used for sorting purposes.
 */
public enum ModifierKey {
  COMMAND(Variant.NONE, "cmd"),
  CONTROL(Variant.PARENT, "ctrl"),
  CONTROL_LEFT(Variant.LEFT, "ctrl"),
  CONTROL_RIGHT(Variant.RIGHT, "ctrl"),
  ALT(Variant.PARENT, "alt"),
  ALT_LEFT(Variant.LEFT, "alt"),
  ALT_RIGHT(Variant.RIGHT, "alt"),
  OPTION(Variant.PARENT, "opt"),
  OPTION_LEFT(Variant.LEFT, "opt"),
  OPTION_RIGHT(Variant.RIGHT, "opt"),
  CAPSLOCK(Variant.NONE, "caps"),
  SHIFT(Variant.PARENT, "shift"),
  SHIFT_LEFT(Variant.LEFT, "shift"),
  SHIFT_RIGHT(Variant.RIGHT, "shift");

  // Map of modifier key identifiers (obtained by calling toString()) to the modifier key itself.
  private static final ImmutableMap<String, ModifierKey> STRING_TO_MODIFIER_KEY = Maps.uniqueIndex(
      Lists.newArrayList(ModifierKey.values()), Functions.toStringFunction());
  private static final ImmutableSet<ModifierKey> PARENTS = ImmutableSet.of(CONTROL, ALT, OPTION,
      SHIFT);
  private static final ImmutableSet<ModifierKey> SINGLES = ImmutableSet.of(COMMAND, CAPSLOCK);

  private final Variant variant;
  private final String keyType;

  private ModifierKey(Variant variant, String keyType) {
    this.variant = checkNotNull(variant);
    this.keyType = checkNotNull(keyType);
  }

  /** Retrieves a modifier key from its string identifier. */
  public static ModifierKey fromString(String string) {
    ModifierKey key = STRING_TO_MODIFIER_KEY.get(checkNotNull(string));
    checkArgument(key != null, string);
    return key;
  }

  /** Returns all keys that are parent keys. */
  public static ImmutableSet<ModifierKey> parents() {
    return PARENTS;
  }

  /** Returns all keys that are neither parent keys or children. */
  public static ImmutableSet<ModifierKey> singles() {
    return SINGLES;
  }

  /**
   * Returns the matching sibling of this key. For example, if this key is ctrlR return ctrlL. If
   * the key has no siblings this method simply returns itself.
   */
  public ModifierKey sibling() {
    if (variant == Variant.PARENT) {
      return this;
    }
    return fromString(keyType + variant.opposite());
  }
  
  /**
   * Returns the parent of this key. For example, if this key is ctrlR return ctrl. If the key is
   * already a parent key this method simply returns itself.
   */
  public ModifierKey parent() {
    if (variant == Variant.PARENT) {
      return this;
    }
    return fromString(keyType);
  }

  /**
   * Returns the children of this key. For example if this key is ctrl, return both ctrlL and ctrlR.
   * If this is not a parent key, returns an empty list. The left key is always returned first.
   */
  public ImmutableList<ModifierKey> children() {
    if (variant != Variant.PARENT) {
      return ImmutableList.of();
    }
    return ImmutableList.of(fromString(keyType + Variant.LEFT), fromString(keyType + Variant.RIGHT));
  }

  @Override public String toString() {
    return keyType + variant.value;
  }

  /** The variant of the key. */
  private static enum Variant {
    PARENT(""), LEFT("L"), RIGHT("R"), NONE("");

    final String value;

    Variant(String value) {
      this.value = checkNotNull(value);
    }

    /**
     * Return the opposite variant. Only applicable to the left and right variants. Returns itself
     * otherwise.
     */
    Variant opposite() {
      return this == LEFT ? RIGHT : this == RIGHT ? LEFT : this;
    }

    @Override public String toString() {
      return value;
    }
  }
}
