package com.jetbrains.python.codeInsight;

import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actions.EditorActionUtil;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.codeInsight.completion.PythonLookupElement;
import com.jetbrains.python.psi.PyStatementWithElse;
import com.jetbrains.python.psi.PyTryExceptStatement;
import com.jetbrains.python.psi.patterns.Matcher;
import com.jetbrains.python.psi.patterns.ParentMatcher;

import java.util.List;

/**
 * Adjusts indentation after a final part keyword is inserted, e.g. an "else:".
 * User: dcheryasov
 * Date: Mar 2, 2010 6:48:40 PM
 */
public class UnindentingInsertHandler implements InsertHandler<PythonLookupElement> {

  private final static ParentMatcher TRY_MATCHER = new ParentMatcher(PyTryExceptStatement.class);
  private final static ParentMatcher ELSE_MATCHER = new ParentMatcher(PyStatementWithElse.class);

  public final static UnindentingInsertHandler INSTANCE = new UnindentingInsertHandler();

  private UnindentingInsertHandler() {
  }

  public void handleInsert(InsertionContext context, PythonLookupElement item) {
    unindentAsNeeded(context.getProject(), context.getEditor(), context.getFile());
  }

  /**
   * Unindent current line to be flush with a starting part, detecting the part if necessary.
   * @param project
   * @param editor
   * @param file
   * @return true if unindenting succeeded
   */
  public static boolean unindentAsNeeded(Project project, Editor editor, PsiFile file) {
    // TODO: handle things other than "else"
    final Document document = editor.getDocument();
    int offset = editor.getCaretModel().getOffset();
    CharSequence text = document.getCharsSequence();
    if (offset >= text.length()) offset = text.length() - 1;

    int line_start_offset = document.getLineStartOffset(document.getLineNumber(offset));
    int nonspace_offset = findBeginning(line_start_offset, text);


    Matcher matcher = null;

    int last_offset = nonspace_offset + "finally".length(); // the longest of all
    if (last_offset > offset) last_offset = offset;
    int local_length = last_offset - nonspace_offset + 1;
    if (local_length > 0) {
      String piece = text.subSequence(nonspace_offset, last_offset+1).toString();
      final int else_len = "else".length();
      if (local_length >= else_len) {
        if ((piece.startsWith("else") || piece.startsWith("elif")) && (else_len == piece.length() || piece.charAt(else_len) < 'a' || piece.charAt(else_len) > 'z')) {
          matcher = ELSE_MATCHER;
        }
      }
      final int except_len = "except".length();
      if (local_length >= except_len) {
        if (piece.startsWith("except") && (except_len == piece.length() || piece.charAt(except_len) < 'a' || piece.charAt(except_len) > 'z')) {
          matcher = TRY_MATCHER;
        }
      }
      final int finally_len = "finally".length();
      if (local_length >= finally_len) {
        if (piece.startsWith("finally") && (finally_len == piece.length() || piece.charAt(finally_len) < 'a' || piece.charAt(finally_len) > 'z')) {
          matcher = TRY_MATCHER;
        }
      }
    }


    if (matcher == null) return false; // failed

    PsiDocumentManager.getInstance(project).commitDocument(document); // reparse

    PsiElement token = file.findElementAt(offset-2); // -1 is our ':'; -2 is even safer.
    List<? extends PsiElement> result = matcher.search(token);
    if (result != null && result.size() > 0) {
      PsiElement outer = result.get(0);
      int outer_offset = outer.getTextOffset();
      int outer_indent = outer_offset - document.getLineStartOffset(document.getLineNumber(outer_offset));
      assert outer_indent >= 0;
      int current_indent = nonspace_offset - line_start_offset;
      EditorActionUtil.indentLine(project, editor, document.getLineNumber(offset), outer_indent - current_indent);
      return true;
    }
    return false;
  }


  // finds offset of first non-space in the line
  private static int findBeginning(int start_offset, CharSequence text) {
    int current_offset = start_offset;
    int text_length = text.length();
    while (current_offset < text_length) {
      char current_char = text.charAt(current_offset);
      if (current_char != ' ' && current_char != '\t' && current_char != '\n') break;
      current_offset += 1;
    }
    return current_offset;
  }
}

@@ -462,16 +462,58 @@
 siFile;%0A
+import com.intellij.psi.util.PsiTreeUtil;%0A
 import c
@@ -686,138 +686,8 @@
 ent;
-%0Aimport com.jetbrains.python.psi.patterns.Matcher;%0Aimport com.jetbrains.python.psi.patterns.ParentMatcher;%0A%0Aimport java.util.List;
 %0A%0A/*
@@ -914,206 +914,8 @@
 %3E %7B%0A
-%0A  private final static ParentMatcher TRY_MATCHER = new ParentMatcher(PyTryExceptStatement.class);%0A  private final static ParentMatcher ELSE_MATCHER = new ParentMatcher(PyStatementWithElse.class);%0A%0A
   pu
@@ -1939,23 +1939,47 @@
     
-Matcher matcher
+Class%3C? extends PsiElement%3E parentClass
  = n
@@ -2548,30 +2548,47 @@
     
-matcher = ELSE_MATCHER
+parentClass = PyStatementWithElse.class
 ;%0A  
@@ -2845,37 +2845,56 @@
         
-matcher = TRY_MATCHER
+parentClass = PyTryExceptStatement.class
 ;%0A      
@@ -3158,29 +3158,48 @@
     
-matcher = TRY_MATCHER
+parentClass = PyTryExceptStatement.class
 ;%0A  
@@ -3230,23 +3230,27 @@
     if (
-matcher
+parentClass
  == null
@@ -3455,153 +3455,100 @@
     
-List%3C? extends PsiElement%3E result = matcher.search(token);%0A    if (result != null && result.size() %3E 0) %7B%0A      PsiElement 
+PsiElement outer = PsiTreeUtil.getParentOfType(token, parentClass);%0A    if (
 outer 
+!
 = 
-result.get(0);
+null) %7B
 %0A   

simplify UnindentingInsertHandler by not using ParentMatcher