/**
  * Current handler inserts closing curly brace (right brace) if necessary. There is a possible
  * case that it should be located more than one line forward.
  *
  * <p><b>Example</b>
  *
  * <pre>
  *     if (test1()) {
  *     } else {<caret> if (test2()) {
  *         foo();
  *     }
  * </pre>
  *
  * <p>We want to get this after the processing:
  *
  * <pre>
  *     if (test1()) {
  *     } else {
  *         if (test2()) {
  *             foo();
  *         }
  *     }
  * </pre>
  *
  * I.e. closing brace should be inserted two lines below current caret line. Hence, we need to
  * calculate correct offset to use for brace inserting. This method is responsible for that.
  *
  * <p>In essence it inspects PSI structure and finds PSE elements with the max length that starts
  * at caret offset. End offset of that element is used as an insertion point.
  *
  * @param file target PSI file
  * @param text text from the given file
  * @param offset target offset where line feed will be inserted
  * @return offset to use for inserting closing brace
  */
 protected int calculateOffsetToInsertClosingBrace(
     PsiFile file, CharSequence text, final int offset) {
   PsiElement element = PsiUtilCore.getElementAtOffset(file, offset);
   ASTNode node = element.getNode();
   if (node != null && node.getElementType() == TokenType.WHITE_SPACE) {
     return CharArrayUtil.shiftForwardUntil(text, offset, "\n");
   }
   for (PsiElement parent = element.getParent(); parent != null; parent = parent.getParent()) {
     ASTNode parentNode = parent.getNode();
     if (parentNode == null || parentNode.getStartOffset() != offset) {
       break;
     }
     element = parent;
   }
   if (element.getTextOffset() != offset) {
     return CharArrayUtil.shiftForwardUntil(text, offset, "\n");
   }
   return element.getTextRange().getEndOffset();
 }
    @Nullable
    private PsiComment createJavaDocStub(
        final CodeInsightSettings settings, final PsiComment comment, final Project project) {
      if (settings.JAVADOC_STUB_ON_ENTER) {
        final DocumentationProvider langDocumentationProvider =
            LanguageDocumentation.INSTANCE.forLanguage(comment.getParent().getLanguage());

        @Nullable final CodeDocumentationProvider docProvider;
        if (langDocumentationProvider instanceof CompositeDocumentationProvider) {
          docProvider =
              ((CompositeDocumentationProvider) langDocumentationProvider)
                  .getFirstCodeDocumentationProvider();
        } else {
          docProvider =
              langDocumentationProvider instanceof CodeDocumentationProvider
                  ? (CodeDocumentationProvider) langDocumentationProvider
                  : null;
        }

        if (docProvider != null) {
          if (docProvider.findExistingDocComment(comment) != comment) return comment;
          String docStub = docProvider.generateDocumentationContentStub(comment);

          if (docStub != null && docStub.length() != 0) {
            myOffset =
                CharArrayUtil.shiftForwardUntil(
                    myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR);
            myOffset =
                CharArrayUtil.shiftForward(myDocument.getCharsSequence(), myOffset, LINE_SEPARATOR);
            myDocument.insertString(myOffset, docStub);
          }
        }

        PsiDocumentManager.getInstance(project).commitAllDocuments();
        return PsiTreeUtil.getNonStrictParentOfType(
            myFile.findElementAt(myOffset), PsiComment.class);
      }
      return comment;
    }
  /**
   * There is a possible case that target code block already starts with the empty line:
   *
   * <pre>
   *   void test(int i) {
   *     if (i > 1[caret]) {
   *
   *     }
   *   }
   * </pre>
   *
   * We want just move caret to correct position at that empty line without creating additional
   * empty line then.
   *
   * @param editor target editor
   * @param codeBlock target code block to which new empty line is going to be inserted
   * @param element target element under caret
   * @return <code>true</code> if it was found out that the given code block starts with the empty
   *     line and caret is pointed to correct position there, i.e. no additional processing is
   *     required; <code>false</code> otherwise
   */
  private static boolean processExistingBlankLine(
      @NotNull Editor editor, @Nullable PsiCodeBlock codeBlock, @Nullable PsiElement element) {
    PsiWhiteSpace whiteSpace = null;
    if (codeBlock == null) {
      if (element != null) {
        final PsiElement next = PsiTreeUtil.nextLeaf(element);
        if (next instanceof PsiWhiteSpace) {
          whiteSpace = (PsiWhiteSpace) next;
        }
      }
    } else {
      whiteSpace = PsiTreeUtil.findChildOfType(codeBlock, PsiWhiteSpace.class);
      if (whiteSpace == null) {
        return false;
      }

      PsiElement lbraceCandidate = whiteSpace.getPrevSibling();
      if (lbraceCandidate == null) {
        return false;
      }

      ASTNode node = lbraceCandidate.getNode();
      if (node == null || node.getElementType() != JavaTokenType.LBRACE) {
        return false;
      }
    }

    if (whiteSpace == null) {
      return false;
    }

    final TextRange textRange = whiteSpace.getTextRange();
    final Document document = editor.getDocument();
    final CharSequence whiteSpaceText =
        document
            .getCharsSequence()
            .subSequence(textRange.getStartOffset(), textRange.getEndOffset());
    if (StringUtil.countNewLines(whiteSpaceText) < 2) {
      return false;
    }

    int i = CharArrayUtil.shiftForward(whiteSpaceText, 0, " \t");
    if (i >= whiteSpaceText.length() - 1) {
      assert false
          : String.format(
              "code block: %s, white space: %s",
              codeBlock == null ? "undefined" : codeBlock.getTextRange(),
              whiteSpace.getTextRange());
      return false;
    }

    editor.getCaretModel().moveToOffset(i + 1 + textRange.getStartOffset());
    EditorActionManager actionManager = EditorActionManager.getInstance();
    EditorActionHandler actionHandler =
        actionManager.getActionHandler(IdeActions.ACTION_EDITOR_MOVE_LINE_END);
    final DataContext dataContext = DataManager.getInstance().getDataContext(editor.getComponent());
    if (dataContext == null) {
      i = CharArrayUtil.shiftForwardUntil(whiteSpaceText, i, "\n");
      if (i >= whiteSpaceText.length()) {
        i = whiteSpaceText.length();
      }
      editor.getCaretModel().moveToOffset(i + textRange.getStartOffset());
    } else {
      actionHandler.execute(editor, dataContext);
    }
    return true;
  }
    private void generateJavadoc(CodeDocumentationAwareCommenter commenter)
        throws IncorrectOperationException {
      CodeInsightSettings settings = CodeInsightSettings.getInstance();
      StringBuilder buffer = new StringBuilder();
      final String docCommentLinePrefix = commenter.getDocumentationCommentLinePrefix();
      if (docCommentLinePrefix == null) {
        return;
      }

      // There are at least two approaches for completing javadoc in case there is a text between
      // current caret position and line end:
      //     1. Move that tail text below the javadoc. Use-case:
      //         Before:
      //             /**<caret>public void foo() {}
      //         After:
      //             /**
      //              */
      //             public void foo() {}
      //     2. Move the tail text inside the javadoc. Use-case:
      //          Before:
      //             /**This is <caret>javadoc description
      //          After:
      //             /** This is
      //              * javadoc description
      //              */
      // The later is most relevant when we have 'auto wrap when typing reaches right margin' option
      // set, i.e. user starts javadoc
      // and types until right margin is reached. We want the wrapped text tail to be located inside
      // javadoc and continue typing
      // inside it. So, we have a control flow branch below that does the trick.
      buffer.append(docCommentLinePrefix);
      if (DataManager.getInstance()
              .loadFromDataContext(
                  myDataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY)
          == Boolean.TRUE) {
        myDocument.insertString(myOffset, buffer);

        // We create new buffer here because the one referenced by current 'buffer' variable value
        // may be already referenced at another
        // place (e.g. 'undo' processing stuff).
        buffer =
            new StringBuilder(LINE_SEPARATOR).append(commenter.getDocumentationCommentSuffix());
        int line = myDocument.getLineNumber(myOffset);
        myOffset = myDocument.getLineEndOffset(line);
      } else {
        buffer.append(LINE_SEPARATOR);
        buffer.append(commenter.getDocumentationCommentSuffix());
      }

      PsiComment comment = createComment(buffer, settings);
      if (comment == null) {
        return;
      }

      myOffset = comment.getTextRange().getStartOffset();
      CharSequence text = myDocument.getCharsSequence();
      myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, LINE_SEPARATOR);
      myOffset = CharArrayUtil.shiftForward(text, myOffset, LINE_SEPARATOR);
      myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, docCommentLinePrefix) + 1;
      removeTrailingSpaces(myDocument, myOffset);

      if (!CodeStyleSettingsManager.getSettings(getProject()).JD_LEADING_ASTERISKS_ARE_ENABLED) {
        LOG.assertTrue(
            CharArrayUtil.regionMatches(
                myDocument.getCharsSequence(),
                myOffset - docCommentLinePrefix.length(),
                docCommentLinePrefix));
        myDocument.deleteString(myOffset - docCommentLinePrefix.length(), myOffset);
        myOffset--;
      } else {
        myDocument.insertString(myOffset, " ");
        myOffset++;
      }

      PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
    }
    @Override
    public void run() {
      CaretModel caretModel = myEditor.getCaretModel();
      try {
        final CharSequence chars = myDocument.getCharsSequence();
        int i = CharArrayUtil.shiftBackwardUntil(chars, myOffset - 1, LINE_SEPARATOR) - 1;
        i = CharArrayUtil.shiftBackwardUntil(chars, i, LINE_SEPARATOR) + 1;
        if (i < 0) i = 0;
        int lineStart = CharArrayUtil.shiftForward(chars, i, " \t");
        CodeDocumentationUtil.CommentContext commentContext =
            CodeDocumentationUtil.tryParseCommentContext(myFile, chars, myOffset, lineStart);

        PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(getProject());
        if (commentContext.docStart) {
          PsiElement element = myFile.findElementAt(commentContext.lineStart);
          final String text = element.getText();
          final PsiElement parent = element.getParent();

          if (text.equals(commentContext.commenter.getDocumentationCommentPrefix())
                  && isDocComment(parent, commentContext.commenter)
              || text.startsWith(commentContext.commenter.getDocumentationCommentPrefix())
                  && element instanceof PsiComment) {
            PsiComment comment =
                isDocComment(parent, commentContext.commenter)
                    ? (PsiComment) parent
                    : (PsiComment) element;
            int commentEnd = comment.getTextRange().getEndOffset();

            if (myOffset >= commentEnd) {
              commentContext.docStart = false;
            } else {
              if (isCommentComplete(comment, commentContext.commenter, myEditor)) {
                if (myOffset >= commentEnd) {
                  commentContext.docAsterisk = false;
                  commentContext.docStart = false;
                } else {
                  commentContext.docAsterisk = true;
                  commentContext.docStart = false;
                }
              } else {
                generateJavadoc(commentContext.commenter);
              }
            }
          } else {
            commentContext.docStart = false;
          }
        } else if (commentContext.cStyleStart) {
          PsiElement element = myFile.findElementAt(commentContext.lineStart);
          if (element instanceof PsiComment
              && commentContext.commenter.getBlockCommentTokenType()
                  == ((PsiComment) element).getTokenType()) {
            final PsiComment comment = (PsiComment) element;
            int commentEnd = comment.getTextRange().getEndOffset();
            if (myOffset >= commentEnd && myOffset < myFile.getTextRange().getEndOffset()) {
              commentContext.docStart = false;
            } else {
              if (isCommentComplete(comment, commentContext.commenter, myEditor)) {
                if (myOffset >= commentEnd) {
                  commentContext.docAsterisk = false;
                  commentContext.docStart = false;
                } else {
                  commentContext.docAsterisk = true;
                  commentContext.docStart = false;
                }
              } else {
                final int currentEndOfLine = CharArrayUtil.shiftForwardUntil(chars, myOffset, "\n");
                myDocument.insertString(
                    currentEndOfLine, " " + commentContext.commenter.getBlockCommentSuffix());
                int lstart = CharArrayUtil.shiftBackwardUntil(chars, myOffset, "\n");
                myDocument.insertString(currentEndOfLine, chars.subSequence(lstart, myOffset));
                psiDocumentManager.commitDocument(myDocument);
              }
            }
          } else {
            commentContext.docStart = false;
          }
        }

        String indentInsideJavadoc = null;
        if (myOffset < myDocument.getTextLength()) {
          final int line = myDocument.getLineNumber(myOffset);
          if (line > 0 && (commentContext.docAsterisk || commentContext.docStart)) {
            indentInsideJavadoc =
                CodeDocumentationUtil.getIndentInsideJavadoc(
                    myDocument, myDocument.getLineStartOffset(line - 1));
          }
        }

        if (commentContext.docAsterisk) {
          commentContext.docAsterisk =
              insertDocAsterisk(
                  commentContext.lineStart,
                  commentContext.docAsterisk,
                  !StringUtil.isEmpty(indentInsideJavadoc),
                  commentContext.commenter);
        }

        boolean docIndentApplied = false;
        CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
        if (codeInsightSettings.SMART_INDENT_ON_ENTER
            || myForceIndent
            || commentContext.docStart
            || commentContext.docAsterisk
            || commentContext.slashSlash) {
          final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(getProject());
          myOffset = codeStyleManager.adjustLineIndent(myFile, myOffset);
          psiDocumentManager.commitAllDocuments();

          if (!StringUtil.isEmpty(indentInsideJavadoc) && myOffset < myDocument.getTextLength()) {
            myDocument.insertString(myOffset + 1, indentInsideJavadoc);
            myOffset += indentInsideJavadoc.length();
            docIndentApplied = true;
          }

          if (myForceIndent && indentInsideJavadoc != null) {
            int indentSize =
                CodeStyleSettingsManager.getSettings(myProject).getIndentSize(myFile.getFileType());
            myDocument.insertString(myOffset + 1, StringUtil.repeatSymbol(' ', indentSize));
            myCaretAdvance += indentSize;
          }
        }

        if ((commentContext.docAsterisk || commentContext.docStart || commentContext.slashSlash)
            && !docIndentApplied) {
          if (myInsertSpace) {
            if (myOffset == myDocument.getTextLength()) {
              myDocument.insertString(myOffset, " ");
            }
            myDocument.insertString(myOffset + 1, " ");
          }

          final char c = myDocument.getCharsSequence().charAt(myOffset);
          if (c != '\n') {
            myOffset += 1;
          }
        }

        if ((commentContext.docAsterisk || commentContext.slashSlash) && !commentContext.docStart) {
          myCaretAdvance +=
              commentContext.slashSlash
                  ? commentContext.commenter.getLineCommentPrefix().length()
                  : 1;
        }
      } catch (IncorrectOperationException e) {
        LOG.error(e);
      }

      myOffset = Math.min(myOffset, myDocument.getTextLength());
      caretModel.moveToOffset(myOffset);
      myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
      myEditor.getSelectionModel().removeSelection();
      if (myCaretAdvance != 0) {
        LogicalPosition caretPosition = caretModel.getLogicalPosition();
        LogicalPosition pos =
            new LogicalPosition(caretPosition.line, caretPosition.column + myCaretAdvance);
        caretModel.moveToLogicalPosition(pos);
      }
    }