// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInspection;

import com.intellij.lang.Commenter;
import com.intellij.lang.LanguageCommenters;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PsiJavaElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;

import static com.intellij.patterns.PsiJavaPatterns.*;

/**
 * @author Dmitry Batkovich
 */
public class BlockMarkerCommentsInspection extends AbstractBaseJavaLocalInspectionTool {
  private static final PsiJavaElementPattern ANONYMOUS_CLASS_MARKER_PATTERN = psiElement().
    withParent(psiElement(PsiDeclarationStatement.class, PsiExpressionStatement.class))
    .afterSiblingSkipping(or(psiElement(PsiWhiteSpace.class), psiElement(PsiJavaToken.class).with(new PatternCondition<PsiJavaToken>(null) {
                            @Override
                            public boolean accepts(@NotNull final PsiJavaToken psiJavaToken, final ProcessingContext context) {
                              return psiJavaToken.getTokenType().equals(JavaTokenType.SEMICOLON);
                            }
                          })),
                          psiElement(PsiLocalVariable.class, PsiAssignmentExpression.class)
                            .withChild(psiElement(PsiNewExpression.class).withChild(psiElement(PsiAnonymousClass.class))));
  private static final PsiJavaElementPattern CLASS_MARKER_PATTERN = psiElement().
    withParent(PsiClass.class).
    afterSiblingSkipping(psiElement(PsiWhiteSpace.class), psiElement(PsiJavaToken.class).with(new PatternCondition<PsiJavaToken>(null) {
      @Override
      public boolean accepts(@NotNull final PsiJavaToken token, final ProcessingContext context) {
        return JavaTokenType.RBRACE.equals(token.getTokenType());
      }
    }));
  private static final PsiJavaElementPattern TRY_CATCH_MARKER_PATTERN = psiElement().
    withParent(PsiTryStatement.class).
    afterSiblingSkipping(psiElement(PsiWhiteSpace.class), psiElement(PsiCodeBlock.class, PsiCatchSection.class));
  private static final PsiJavaElementPattern LOOP_OR_IF_MARKER =
    psiElement().afterSiblingSkipping(psiElement(PsiWhiteSpace.class), psiElement(PsiCodeBlock.class)).
      withParent(psiElement(PsiBlockStatement.class).withParent(psiElement(PsiLoopStatement.class, PsiIfStatement.class)));
  private static final PsiJavaElementPattern METHOD_MARKER_PATTERN =
    psiElement().withParent(PsiMethod.class).afterSiblingSkipping(psiElement(PsiWhiteSpace.class), psiElement(PsiCodeBlock.class));

  private static final ElementPattern MARKER_PATTERN = or(ANONYMOUS_CLASS_MARKER_PATTERN,
                                                          CLASS_MARKER_PATTERN,
                                                          TRY_CATCH_MARKER_PATTERN,
                                                          LOOP_OR_IF_MARKER,
                                                          METHOD_MARKER_PATTERN);

  private static final String END_WORD = "end";

  @NotNull
  @Override
  public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
    return new PsiElementVisitor() {
      @Override
      public void visitComment(final PsiComment element) {
        final IElementType tokenType = element.getTokenType();
        if (!(tokenType.equals(JavaTokenType.END_OF_LINE_COMMENT))) {
          return;
        }
        final Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(element.getLanguage());
        String rawCommentText = element.getText();
        final String prefix = commenter.getLineCommentPrefix();
        if (prefix != null && rawCommentText.startsWith(prefix)) {
          rawCommentText = rawCommentText.substring(prefix.length());
        }
        final String commentText = rawCommentText.trim().toLowerCase();
        if (!commentText.startsWith(END_WORD) || StringUtil.split(commentText, " ").size() > 3) {
          return;
        }
        if (MARKER_PATTERN.accepts(element)) {
          holder.registerProblem(element, "Redundant block marker", new LocalQuickFix() {
            @NotNull
            @Override
            public String getFamilyName() {
              return "Remove block marker comments";
            }

            @Override
            public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) {
              descriptor.getPsiElement().delete();
            }
          });
        }
      }
    };
  }

  @Nls
  @NotNull
  @Override
  public String getDisplayName() {
    return "Block marker comment";
  }
}
