/*
 * 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.jetbrains.python.inspections;

import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.util.InspectionMessage;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.inspections.quickfix.ConvertIndentsFix;
import com.jetbrains.python.lexer.PythonIndentingLexer;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;


public final class PyInconsistentIndentationInspection extends PyInspection {

  @Override
  public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) {
    if (file.getLanguage() instanceof PythonLanguage) {
      return new IndentValidator(file, manager, isOnTheFly).invoke();
    }
    return ProblemDescriptor.EMPTY_ARRAY;
  }

  private static class IndentValidator {
    private final PsiFile myFile;
    private final InspectionManager myManager;
    private final boolean myOnTheFly;
    private final List<ProblemDescriptor> myProblems;
    private int myLastTabs = 0;
    private int myLastSpaces = 0;

    IndentValidator(PsiFile file, InspectionManager manager, boolean isOnTheFly) {
      myFile = file;
      myManager = manager;
      myOnTheFly = isOnTheFly;
      myProblems = new ArrayList<>();
    }

    public ProblemDescriptor[] invoke() {
      PythonIndentingLexer lexer = new PythonIndentingLexer();
      final String text = myFile.getText();
      lexer.start(text);
      while (lexer.getTokenType() != null) {
        final IElementType tokenType = lexer.getTokenType();
        if (tokenType == PyTokenTypes.STATEMENT_BREAK) {
          lexer.advance();
          while (lexer.getTokenType() != null && lexer.getTokenType() != PyTokenTypes.LINE_BREAK) {
            lexer.advance();
          }
          if (lexer.getTokenType() == PyTokenTypes.LINE_BREAK) {
            String indent = text.substring(lexer.getTokenStart(), lexer.getTokenEnd());
            validateIndent(lexer.getTokenStart(), indent);
          }
        }
        lexer.advance();
      }
      return myProblems.toArray(ProblemDescriptor.EMPTY_ARRAY);
    }

    private void validateIndent(final int tokenStart, String indent) {
      int lastLF = indent.lastIndexOf('\n');
      String lastLineIndent = indent.substring(lastLF + 1);
      int spaces = 0;
      int tabs = 0;
      final int length = lastLineIndent.length();
      for (int i = 0; i < length; i++) {
        final char c = lastLineIndent.charAt(i);
        if (c == ' ') {
          spaces++;
        }
        else if (c == '\t') tabs++;
      }
      final int problemStart = tokenStart + lastLF + 1;
      if (spaces > 0 && tabs > 0) {
        reportProblem(PyPsiBundle.message("INSP.inconsistent.indentation.mix.tabs.spaces"), problemStart, length);
        // don't know which one is correct => don't complain about inconsistent indentation on subsequent lines which use
        // either tabs or spaces
        myLastSpaces = 0;
        myLastTabs = 0;
      }
      else {
        if (spaces > 0 && myLastTabs > 0) {
          reportProblem(PyPsiBundle.message("INSP.inconsistent.indentation.previous.line.used.tabs.this.line.uses.spaces"),
                        problemStart, length);
        }
        else if (tabs > 0 && myLastSpaces > 0) {
          reportProblem(PyPsiBundle.message("INSP.inconsistent.indentation.previous.line.used.spaces.this.line.uses.tabs"),
                        problemStart, length);
        }
        if (spaces > 0 || tabs > 0) {
          myLastTabs = tabs;
          myLastSpaces = spaces;
        }
      }
    }

    private void reportProblem(@InspectionMessage String descriptionTemplate, final int problemStart, final int problemLength) {
      PsiElement elt = myFile.findElementAt(problemStart);
      int startOffset = problemStart - elt.getTextRange().getStartOffset();
      int endOffset = startOffset + problemLength;
      myProblems.add(myManager.createProblemDescriptor(elt, new TextRange(startOffset, endOffset),
                                                       descriptionTemplate,
                                                       ProblemHighlightType.GENERIC_ERROR_OR_WARNING, myOnTheFly,
                                                       new ConvertIndentsFix(false), new ConvertIndentsFix(true)));
    }
  }
}
