/*
 * Copyright 2003-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.nodeEditor;


import jetbrains.mps.editor.runtime.commands.EditorComputable;
import jetbrains.mps.nodeEditor.cells.APICellAdapter;
import jetbrains.mps.nodeEditor.keymaps.KeymapHandler;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.KeyMapAction;
import jetbrains.mps.util.Pair;

import java.awt.event.InputMethodEvent;
import java.awt.event.KeyEvent;
import java.util.Collection;

public class EditorComponentKeyboardHandler implements KeyboardHandler {
  private final KeymapHandler<KeyEvent> myKeymapHandler;

  public EditorComponentKeyboardHandler(KeymapHandler<KeyEvent> keymapHandler) {
    myKeymapHandler = keymapHandler;
  }

  @Override
  public boolean processKeyPressed(final EditorContext editorContext, final KeyEvent keyEvent) {
    EditorComponent nodeEditor = editorContext.getNodeEditorComponent();
    nodeEditor.hideMessageToolTip();

    if (processKeyMaps(editorContext, keyEvent)) {
      return true;
    }

    jetbrains.mps.openapi.editor.cells.CellActionType actionType = editorContext.getNodeEditorComponent().getActionType(keyEvent, editorContext);
    EditorCell selectedCell = editorContext.getSelectedCell();

    // process action
    if (selectedCell != null && actionType != null && editorContext.getEditorComponent().getActionHandler().executeAction(selectedCell, actionType)) {
      return true;
    }

    // special case - don't allow kbd focus to leave editor area
    if (keyEvent.getKeyCode() == KeyEvent.VK_UP && keyEvent.isControlDown() && !keyEvent.isAltDown() && !keyEvent.isShiftDown()) {
      keyEvent.consume();
    }

    return false;
  }

  @Override
  public boolean processKeyTyped(EditorContext editorContext, final KeyEvent keyEvent) {
    EditorComponent nodeEditor = editorContext.getNodeEditorComponent();
    nodeEditor.hideMessageToolTip();

    if (processKeyMaps(editorContext, keyEvent)) {
      return true;
    }

    final EditorCell selectedCell = editorContext.getSelectedCell();
    if (selectedCell != null && ((jetbrains.mps.nodeEditor.cells.EditorCell) selectedCell).processKeyTyped(keyEvent, false)) {
      keyEvent.consume();
      return true;
    }

    jetbrains.mps.openapi.editor.cells.CellActionType actionType = editorContext.getNodeEditorComponent().getActionType(keyEvent, editorContext);

    if (selectedCell != null) {
      final boolean strictMatching = jetbrains.mps.openapi.editor.cells.CellActionType.RIGHT_TRANSFORM.equals(actionType) ||
                                     jetbrains.mps.openapi.editor.cells.CellActionType.LEFT_TRANSFORM.equals(actionType);

      if (selectedCell.isErrorState() && strictMatching) {
        EditorComputable<Boolean> validateCommand = new EditorComputable<Boolean>(editorContext) {
          @Override
          protected Boolean doCompute() {
            return APICellAdapter.validate(selectedCell, true, false);
          }
        };
        editorContext.getRepository().getModelAccess().executeCommand(validateCommand);

        if (validateCommand.getResult()) {
          return true;
        }
      }

      if (actionType != null
          && !(selectedCell.isErrorState() && jetbrains.mps.openapi.editor.cells.CellActionType.RIGHT_TRANSFORM.equals(actionType))) {
        if (editorContext.getEditorComponent().getActionHandler().executeAction(selectedCell, actionType)) {
          return true;
        }
      }
    }

    if (selectedCell != null && ((jetbrains.mps.nodeEditor.cells.EditorCell) selectedCell).processKeyTyped(keyEvent, true)) {
      keyEvent.consume();
      return true;
    }

    return false;
  }

  @Override
  public boolean processKeyReleased(EditorContext editorContext, KeyEvent keyEvent) {
    return false;
  }

  private boolean processKeyMaps(EditorContext editorContext, KeyEvent keyEvent) {
    EditorCell selectedCell = editorContext.getSelectedCell();
    if (selectedCell == null) {
      return false;
    }

    if (keyEvent.getID() != KeyEvent.KEY_PRESSED && keyEvent.getID() != KeyEvent.KEY_TYPED) {
      // Looks like unnecessary check
      return false;
    }

    int keyCode = keyEvent.getKeyCode();
    if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT || keyCode == KeyEvent.VK_SHIFT ||
        keyCode == KeyEvent.VK_PAGE_UP || keyCode == KeyEvent.VK_PAGE_DOWN) {
      return false;
    }

    Collection<Pair<KeyMapAction, EditorCell>> actionCellPairs = myKeymapHandler.getActions(selectedCell, keyEvent, editorContext);
    if (actionCellPairs.size() == 0) {
      return false;
    }
    Pair<KeyMapAction, EditorCell> first = actionCellPairs.iterator().next();
    if (actionCellPairs.size() == 1 && !first.o1.isMenuAlwaysShown()) {
      myKeymapHandler.executeAction(first.o1, first.o2, editorContext);
    } else {
      // show menu
      myKeymapHandler.showActionsMenu(actionCellPairs, editorContext, selectedCell);
    }
    return true;
  }

  @Override
  public boolean processTextChanged(EditorContext editorContext, InputMethodEvent inputEvent) {
    EditorCell selectedCell = editorContext.getSelectedCell();
    return selectedCell != null && ((jetbrains.mps.nodeEditor.cells.EditorCell) selectedCell).processTextChanged(inputEvent);
  }
}
