// Copyright 2017 Google Inc.
//
// 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 com.google.bamboo.soy.insight.typedhandlers;

import com.google.bamboo.soy.elements.TagElement;
import com.google.bamboo.soy.file.SoyFile;
import com.google.bamboo.soy.file.SoyFileType;
import com.google.bamboo.soy.parser.SoyBeginChoiceClause;
import com.google.bamboo.soy.parser.SoyBeginParamTag;
import com.google.bamboo.soy.parser.SoyChoiceClause;
import com.google.bamboo.soy.parser.SoyParamListElement;
import com.google.bamboo.soy.parser.SoyStatementList;
import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Inserts appropriate characters and indentation after pressing "Enter" in a closure template file.
 *
 * <p>In comments this handler inserts "*" on the next line and moves the cursor behind it when
 * pressing "Enter".
 *
 * <p>If pressed right after an opening tag this handler will indent the cursor on the next line.
 */
public class EnterHandler extends EnterHandlerDelegateAdapter {

  private static final Logger LOG = Logger.getInstance(EnterHandler.class);

  private static void handleEnterInComment(
      PsiElement element, @NotNull PsiFile file, @NotNull Editor editor) {
    if (element.getText().startsWith("/*")) {
      Document document = editor.getDocument();

      int caretOffset = editor.getCaretModel().getOffset();
      int lineNumber = document.getLineNumber(caretOffset);

      String lineTextBeforeCaret =
          document.getText(new TextRange(document.getLineStartOffset(lineNumber), caretOffset));
      String lineTextAfterCaret =
          document.getText(new TextRange(caretOffset, document.getLineEndOffset(lineNumber)));

      if (lineTextAfterCaret.equals("*/")) {
        return;
      }

      String toInsert = lineTextBeforeCaret.equals("") ? " * " : "* ";
      insertText(file, editor, toInsert, toInsert.length());
    }
  }

  private static void insertText(PsiFile file, Editor editor, String text, int numChar) {
    EditorModificationUtil.insertStringAtCaret(editor, text, false, numChar);
    PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument());
  }

  /**
   * Method deciding whether the following transformation is applicable: from
   *
   * <pre><code>
   *   {left}<caret>{right}
   * </code></pre>
   *
   * to
   *
   * <pre<code>
   * {left}
   * <caret>
   * {right}
   * </code></pre>
   */
  private static boolean isBetweenSiblingTags(PsiFile psiFile, int caretOffset) {
    PsiElement nextElement = psiFile.findElementAt(caretOffset);
    if (nextElement == null || nextElement.getParent() == null) {
      return false;
    }
    nextElement = nextElement.getParent();
    if (nextElement instanceof SoyBeginChoiceClause || nextElement instanceof SoyBeginParamTag) {
      nextElement = nextElement.getParent();
      PsiElement prevElement = PsiTreeUtil
          .skipSiblingsBackward(nextElement, SoyStatementList.class);
      return prevElement instanceof SoyChoiceClause || prevElement instanceof SoyParamListElement;
    } else {
      PsiElement prevElement = PsiTreeUtil
          .skipSiblingsBackward(nextElement, SoyStatementList.class);

      return prevElement instanceof TagElement && nextElement instanceof TagElement;
    }
  }

  @Override
  public Result preprocessEnter(
      @NotNull PsiFile psiFile,
      @NotNull Editor editor,
      @NotNull Ref<Integer> caretOffset,
      @NotNull Ref<Integer> caretOffsetChange,
      @NotNull DataContext dataContext,
      @Nullable EditorActionHandler originalHandler) {
    if (psiFile instanceof SoyFile && isBetweenSiblingTags(psiFile, caretOffset.get())) {
      if (originalHandler != null) {
        originalHandler.execute(editor, dataContext);
      }
      return Result.Default;
    }
    return Result.Continue;
  }

  @Override
  public Result postProcessEnter(
      @NotNull PsiFile file, @NotNull Editor editor, @NotNull DataContext dataContext) {
    if (file.getFileType() != SoyFileType.INSTANCE) {
      return Result.Continue;
    }

    int caretOffset = editor.getCaretModel().getOffset();
    PsiElement element = file.findElementAt(caretOffset);
    Document document = editor.getDocument();

    int lineNumber = document.getLineNumber(caretOffset) - 1;
    int lineStartOffset = document.getLineStartOffset(lineNumber);
    String lineTextBeforeCaret = document.getText(new TextRange(lineStartOffset, caretOffset));

    if (element instanceof PsiComment && element.getTextOffset() < caretOffset) {
      handleEnterInComment(element, file, editor);
    } else if (lineTextBeforeCaret.startsWith("/*")) {
      insertText(file, editor, " * \n ", 3);
    }

    return Result.Continue;
  }
}