/*
 * 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.codeInspection;

import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorSettings;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import org.jetbrains.annotations.NotNull;

/**
 * @author Bas Leijdekkers
 */
public class ProblematicWhitespaceInspection extends LocalInspectionTool {

  private static class ShowWhitespaceFix implements LocalQuickFix {

    @NotNull
    @Override
    public String getFamilyName() {
      return InspectionsBundle.message("problematic.whitespace.show.whitespaces.quickfix");
    }

    @Override
    public boolean startInWriteAction() {
      return false;
    }

    @Override
    public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
      final FileEditorManager editorManager = FileEditorManager.getInstance(project);
      final Editor editor = editorManager.getSelectedTextEditor();
      if (editor == null) {
        return;
      }
      final EditorSettings settings = editor.getSettings();
      settings.setWhitespacesShown(!settings.isWhitespacesShown());
      editor.getComponent().repaint();
    }
  }

  @NotNull
  @Override
  public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
    return new ProblematicWhitespaceVisitor(holder, isOnTheFly);
  }

  private class ProblematicWhitespaceVisitor extends PsiElementVisitor {

    private final ProblemsHolder myHolder;
    private final boolean myIsOnTheFly;

    public ProblematicWhitespaceVisitor(ProblemsHolder holder, boolean isOnTheFly) {
      myHolder = holder;
      myIsOnTheFly = isOnTheFly;
    }

    @Override
    public void visitFile(PsiFile file) {
      super.visitFile(file);
      final FileType fileType = file.getFileType();
      if (!(fileType instanceof LanguageFileType)) {
        return;
      }
      final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(file.getProject());
      final CommonCodeStyleSettings.IndentOptions indentOptions = settings.getIndentOptions(fileType);
      final boolean useTabs = indentOptions.USE_TAB_CHARACTER;
      final boolean smartTabs = indentOptions.SMART_TABS;
      final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
      if (document == null) {
        return;
      }
      final int lineCount = document.getLineCount();
      int previousLineIndent = 0;
      for (int i = 0; i < lineCount; i++) {
        final int startOffset = document.getLineStartOffset(i);
        final int endOffset = document.getLineEndOffset(i);
        final String line = document.getText(new TextRange(startOffset, endOffset));
        boolean spaceSeen = false;
        for (int j = 0, length = line.length(); j < length; j++) {
          final char c = line.charAt(j);
          if (c == '\t') {
            if (useTabs) {
              if (smartTabs && spaceSeen) {
                if (registerError(file, startOffset, true)) {
                  return;
                }
              }
            }
            else {
              if (registerError(file, startOffset, false)) {
                return;
              }
            }
          }
          else if (c == ' ') {
            if (useTabs) {
              if (!smartTabs) {
                if (registerError(file, startOffset, true)) {
                  return;
                }
              }
              else if (!spaceSeen) {
                final int currentIndent = Math.max(0, j);
                if (currentIndent < previousLineIndent) {
                  if (registerError(file, startOffset, true)) {
                    return;
                  }
                }
                previousLineIndent = currentIndent;
              }
            }
            spaceSeen = true;
          }
          else {
            if (!spaceSeen) {
              previousLineIndent = Math.max(0, j);
            }
            break;
          }
        }
      }
    }

    private boolean registerError(PsiFile file, int startOffset, boolean tab) {
      final PsiElement element = file.findElementAt(startOffset);
      if (element != null && isSuppressedFor(element)) {
        return false;
      }
      final String description = tab
                                 ? InspectionsBundle.message("problematic.whitespace.spaces.problem.descriptor", file.getName())
                                 : InspectionsBundle.message("problematic.whitespace.tabs.problem.descriptor", file.getName());
      if (myIsOnTheFly) {
        myHolder.registerProblem(file, description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new ShowWhitespaceFix());
      }
      else {
        myHolder.registerProblem(file, description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
      }
      return true;
    }
  }
}
