/*
 * Copyright 2000-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 com.intellij.codeInsight.editorActions;

import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.TransactionGuard;
import com.intellij.openapi.application.TransactionKind;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.text.CharArrayUtil;

import java.util.concurrent.atomic.AtomicBoolean;

public class EndHandler extends EditorActionHandler {
  private final EditorActionHandler myOriginalHandler;

  public EndHandler(EditorActionHandler originalHandler) {
    super(true);
    myOriginalHandler = originalHandler;
  }

  @Override
  protected void doExecute(final Editor editor, Caret caret, DataContext dataContext) {
    CodeInsightSettings settings = CodeInsightSettings.getInstance();
    if (!settings.SMART_END_ACTION) {
      if (myOriginalHandler != null) {
        myOriginalHandler.execute(editor, caret, dataContext);
      }
      return;
    }

    final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(editor.getComponent()));
    if (project == null) {
      if (myOriginalHandler != null) {
        myOriginalHandler.execute(editor, caret, dataContext);
      }
      return;
    }
    final Document document = editor.getDocument();
    final PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(document);

    if (file == null) {
      if (myOriginalHandler != null){
        myOriginalHandler.execute(editor, caret, dataContext);
      }
      return;
    }

    final EditorNavigationDelegate[] extensions = EditorNavigationDelegate.EP_NAME.getExtensions();
    for (EditorNavigationDelegate delegate : extensions) {
      if (delegate.navigateToLineEnd(editor, dataContext) == EditorNavigationDelegate.Result.STOP) {
        return;
      }
    }

    final CaretModel caretModel = editor.getCaretModel();
    final int caretOffset = caretModel.getOffset();
    CharSequence chars = editor.getDocument().getCharsSequence();
    int length = editor.getDocument().getTextLength();

    if (caretOffset < length) {
      final int offset1 = CharArrayUtil.shiftBackward(chars, caretOffset - 1, " \t");
      if (offset1 < 0 || chars.charAt(offset1) == '\n' || chars.charAt(offset1) == '\r') {
        final int offset2 = CharArrayUtil.shiftForward(chars, offset1 + 1, " \t");
        boolean isEmptyLine = offset2 >= length || chars.charAt(offset2) == '\n' || chars.charAt(offset2) == '\r';
        if (isEmptyLine) {

          // There is a possible case that indent string is not calculated for particular document (that is true at least for plain text
          // documents). Hence, we check that and don't finish processing in case we have such a situation. AtomicBoolean is used
          // here just as a boolean value holder due to requirement to declare variable used from inner class as final.
          final AtomicBoolean stopProcessing = new AtomicBoolean(true);
          TransactionGuard.syncTransaction(TransactionKind.TEXT_EDITING, () -> {
            PsiDocumentManager.getInstance(project).commitAllDocuments();
            ApplicationManager.getApplication().runWriteAction(() -> {
              CodeStyleManager styleManager = CodeStyleManager.getInstance(project);
              final String lineIndent = styleManager.getLineIndent(file, caretOffset);
              if (lineIndent != null) {
                int col = calcColumnNumber(lineIndent, editor.getSettings().getTabSize(project));
                int line = caretModel.getVisualPosition().line;
                caretModel.moveToVisualPosition(new VisualPosition(line, col));

                if (caretModel.getLogicalPosition().column != col){
                  if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), project)) {
                    return;
                  }
                  editor.getSelectionModel().removeSelection();
                  document.replaceString(offset1 + 1, offset2, lineIndent);
                }
              }
              else {
                stopProcessing.set(false);
              }

              editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
              editor.getSelectionModel().removeSelection();
            });
          });
          if (stopProcessing.get()) {
            return;
          }
        }
      }
    }

    if (myOriginalHandler != null){
      myOriginalHandler.execute(editor, caret, dataContext);
    }
  }

  private int calcColumnNumber(final String lineIndent, final int tabSize) {
    int result = 0;
    for (char c : lineIndent.toCharArray()) {
      if (c == ' ') result++;
      if (c == '\t') result += tabSize;
    }
    return result;
  }

}

@@ -845,16 +845,69 @@
 ontext;%0A
+import com.intellij.openapi.application.AccessToken;%0A
 import c
@@ -4071,24 +4071,25 @@
 lean(true);%0A
+%0A
           Tr
@@ -4106,13 +4106,251 @@
 uard
-.sync
+ guard = TransactionGuard.getInstance();%0A          // sometimes this handler is invoked from other actions, then we're already inside a transaction%0A          try (AccessToken ignore = guard.isInsideTransaction() ? null : guard.startSynchronous
 Tran
@@ -4389,15 +4389,10 @@
 TING
-, () -%3E
+))
  %7B%0A 
@@ -5623,34 +5623,32 @@
  %7D);%0A          %7D
-);
 %0A          if (s

allow EndHandler to be executed inside another transaction