// Copyright 2010-2016, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package org.mozc.android.inputmethod.japanese;

import org.mozc.android.inputmethod.japanese.KeycodeConverter.KeyEventInterface;
import org.mozc.android.inputmethod.japanese.keyboard.Keyboard;
import org.mozc.android.inputmethod.japanese.keyboard.ProbableKeyEventGuesser;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.Input.TouchEvent;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCommands.KeyEvent.ProbableKeyEvent;
import org.mozc.android.inputmethod.japanese.resources.R;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.view.KeyEvent;

import java.util.List;

/**
 * Converter from primary key code (≒ code point) to Mozc's key event.
 *
 * TODO(matsuzakit): Move to util package
 */
public class PrimaryKeyCodeConverter {

  public final int keyCodeUp;
  public final int keyCodeLeft;
  public final int keyCodeRight;
  public final int keyCodeDown;
  public final int keyCodeBackspace;
  public final int keyCodeEnter;
  public final int keyCodeChartypeToKana;
  public final int keyCodeChartypeToAbc;
  public final int keyCodeSymbol;
  public final int keyCodeUndo;
  public final int keyCodeCapslock;
  public final int keyCodeAlt;
  public final int keyCodeMenuDialog;

  private final ProbableKeyEventGuesser guesser;

  /**
   * Constructs with default guesser.
   */
  public PrimaryKeyCodeConverter(Context context) {
    this(context, new ProbableKeyEventGuesser(context.getAssets()));
  }

  public PrimaryKeyCodeConverter(Context context, ProbableKeyEventGuesser guesser) {
    // Prefetch keycodes from resource
    Preconditions.checkNotNull(context);
    Resources res = Preconditions.checkNotNull(context.getResources());
    keyCodeUp = res.getInteger(R.integer.key_up);
    keyCodeLeft = res.getInteger(R.integer.key_left);
    keyCodeRight = res.getInteger(R.integer.key_right);
    keyCodeDown = res.getInteger(R.integer.key_down);
    keyCodeBackspace = res.getInteger(R.integer.key_backspace);
    keyCodeEnter = res.getInteger(R.integer.key_enter);
    keyCodeChartypeToKana = res.getInteger(R.integer.key_chartype_to_kana);
    keyCodeChartypeToAbc = res.getInteger(R.integer.key_chartype_to_abc);
    keyCodeSymbol = res.getInteger(R.integer.key_symbol);
    keyCodeUndo = res.getInteger(R.integer.key_undo);
    keyCodeCapslock = res.getInteger(R.integer.key_capslock);
    keyCodeAlt = res.getInteger(R.integer.key_alt);
    keyCodeMenuDialog = res.getInteger(R.integer.key_menu_dialog);

    this.guesser = Preconditions.checkNotNull(guesser);
  }

  public void setKeyboard(Keyboard keyboard) {
    guesser.setKeyboard(Preconditions.checkNotNull(keyboard));
  }

  public void setConfiguration(Configuration newConfig) {
    guesser.setConfiguration(Optional.of(newConfig));
  }

  public Optional<ProtoCommands.KeyEvent> createMozcKeyEvent(
      int primaryCode, List<TouchEvent> touchEventList) {
    Preconditions.checkNotNull(touchEventList);

    // Space
    if (primaryCode == ' ') {
      return Optional.of(KeycodeConverter.SPECIALKEY_SPACE);
    }

    // Enter
    if (primaryCode == keyCodeEnter) {
      return Optional.of(KeycodeConverter.SPECIALKEY_VIRTUAL_ENTER);
    }

    // Backspace
    if (primaryCode == keyCodeBackspace) {
      return Optional.of(KeycodeConverter.SPECIALKEY_BACKSPACE);
    }

    // Up arrow.
    if (primaryCode == keyCodeUp) {
      return Optional.of(KeycodeConverter.SPECIALKEY_UP);
    }

    // Left arrow.
    if (primaryCode == keyCodeLeft) {
      return Optional.of(KeycodeConverter.SPECIALKEY_VIRTUAL_LEFT);
    }

    // Right arrow.
    if (primaryCode == keyCodeRight) {
      return Optional.of(KeycodeConverter.SPECIALKEY_VIRTUAL_RIGHT);
    }

    // Down arrow.
    if (primaryCode == keyCodeDown) {
      return Optional.of(KeycodeConverter.SPECIALKEY_DOWN);
    }

    if (primaryCode > 0) {
      ProtoCommands.KeyEvent.Builder builder =
          ProtoCommands.KeyEvent.newBuilder().setKeyCode(primaryCode);
      if (!touchEventList.isEmpty()) {
        List<ProbableKeyEvent> probableKeyEvents = guesser.getProbableKeyEvents(touchEventList);
        builder.addAllProbableKeyEvent(probableKeyEvents);
      }
      return Optional.of(builder.build());
    }
    return Optional.<ProtoCommands.KeyEvent>absent();
  }

  public KeyEventInterface getPrimaryCodeKeyEvent(int primaryCode) {
    return new PrimaryCodeKeyEvent(primaryCode);
  }

  // Just used to InputMethodService.sendDownUpKeyEvents
  private class PrimaryCodeKeyEvent implements KeyEventInterface {

    private final int primaryCode;

    private PrimaryCodeKeyEvent(int primaryCode) {
      this.primaryCode = primaryCode;
    }

    @Override
    public int getKeyCode() {
      // Hack: as a work around for conflict between unicode-region and android's key event
      // code, make a reverse mapping from unicode to key event code.
      switch (primaryCode) {
        // Map of numbers.
        case '1': case '!': return KeyEvent.KEYCODE_1;
        case '2': return KeyEvent.KEYCODE_2;
        case '3': return KeyEvent.KEYCODE_3;
        case '4': case '$': return KeyEvent.KEYCODE_4;
        case '5': case '%': return KeyEvent.KEYCODE_5;
        case '6': case '^': return KeyEvent.KEYCODE_6;
        case '7': case '&': return KeyEvent.KEYCODE_7;
        case '8': return KeyEvent.KEYCODE_8;
        case '9': case '(': return KeyEvent.KEYCODE_9;
        case '0': case ')': return KeyEvent.KEYCODE_0;

        // Maps of latin alphabets.
        case 'a': case 'A': return KeyEvent.KEYCODE_A;
        case 'b': case 'B': return KeyEvent.KEYCODE_B;
        case 'c': case 'C': return KeyEvent.KEYCODE_C;
        case 'd': case 'D': return KeyEvent.KEYCODE_D;
        case 'e': case 'E': return KeyEvent.KEYCODE_E;
        case 'f': case 'F': return KeyEvent.KEYCODE_F;
        case 'g': case 'G': return KeyEvent.KEYCODE_G;
        case 'h': case 'H': return KeyEvent.KEYCODE_H;
        case 'i': case 'I': return KeyEvent.KEYCODE_I;
        case 'j': case 'J': return KeyEvent.KEYCODE_J;
        case 'k': case 'K': return KeyEvent.KEYCODE_K;
        case 'l': case 'L': return KeyEvent.KEYCODE_L;
        case 'm': case 'M': return KeyEvent.KEYCODE_M;
        case 'n': case 'N': return KeyEvent.KEYCODE_N;
        case 'o': case 'O': return KeyEvent.KEYCODE_O;
        case 'p': case 'P': return KeyEvent.KEYCODE_P;
        case 'q': case 'Q': return KeyEvent.KEYCODE_Q;
        case 'r': case 'R': return KeyEvent.KEYCODE_R;
        case 's': case 'S': return KeyEvent.KEYCODE_S;
        case 't': case 'T': return KeyEvent.KEYCODE_T;
        case 'u': case 'U': return KeyEvent.KEYCODE_U;
        case 'v': case 'V': return KeyEvent.KEYCODE_V;
        case 'w': case 'W': return KeyEvent.KEYCODE_W;
        case 'x': case 'X': return KeyEvent.KEYCODE_X;
        case 'y': case 'Y': return KeyEvent.KEYCODE_Y;
        case 'z': case 'Z': return KeyEvent.KEYCODE_Z;

        // Map of symbols.
        case '*': return KeyEvent.KEYCODE_STAR;
        case '#': return KeyEvent.KEYCODE_POUND;
        case '-': case '_': return KeyEvent.KEYCODE_MINUS;
        case '+': return KeyEvent.KEYCODE_PLUS;
        case '/': case '?': return KeyEvent.KEYCODE_SLASH;
        case '=': return KeyEvent.KEYCODE_EQUALS;
        case ';': case ':': return KeyEvent.KEYCODE_SEMICOLON;
        case '[': case '{': return KeyEvent.KEYCODE_LEFT_BRACKET;
        case ']': case '}': return KeyEvent.KEYCODE_RIGHT_BRACKET;
        case '.': case '>': return KeyEvent.KEYCODE_PERIOD;
        case ',': case '<': return KeyEvent.KEYCODE_COMMA;
        case '`': case '~': return KeyEvent.KEYCODE_GRAVE;
        case '\\': case '|': return KeyEvent.KEYCODE_BACKSLASH;
        case '@': return KeyEvent.KEYCODE_AT;
        case '\'': case '\"': return KeyEvent.KEYCODE_APOSTROPHE;
        // Space
        case ' ': return KeyEvent.KEYCODE_SPACE;
      }

      // Enter
      if (primaryCode == keyCodeEnter) {
        return KeyEvent.KEYCODE_ENTER;
      }
      // Backspace
      if (primaryCode == keyCodeBackspace) {
        return KeyEvent.KEYCODE_DEL;
      }
      // Up arrow.
      if (primaryCode == keyCodeUp) {
        return KeyEvent.KEYCODE_DPAD_UP;
      }
      // Left arrow.
      if (primaryCode == keyCodeLeft) {
        return KeyEvent.KEYCODE_DPAD_LEFT;
      }
      // Right arrow.
      if (primaryCode == keyCodeRight) {
        return KeyEvent.KEYCODE_DPAD_RIGHT;
      }
      // Down arrow.
      if (primaryCode == keyCodeDown) {
        return KeyEvent.KEYCODE_DPAD_DOWN;
      }
      return primaryCode;
    }

    @Override
    public Optional<KeyEvent> getNativeEvent() {
      return Optional.<KeyEvent>absent();
    }
  }
}
