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

import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Set;

/**
 * @author Maxim.Mossienko
 */
public class ParameterInfoUtils {
  public static final String DEFAULT_PARAMETER_CLOSE_CHARS = ",){}";

  @Nullable
  public static <T extends PsiElement> T findParentOfType (PsiFile file, int offset, Class<T> parentClass) {
    return findParentOfTypeWithStopElements(file, offset, parentClass);
  }

  @Nullable
  public static <T extends PsiElement> T findParentOfTypeWithStopElements (PsiFile file, int offset, Class<T> parentClass, @NotNull Class<? extends PsiElement>... stopAt) {
    PsiElement element = file.findElementAt(offset);
    if (element == null) return null;

    T parentOfType = PsiTreeUtil.getParentOfType(element, parentClass, true, stopAt);
    if (element instanceof PsiWhiteSpace) {
      parentOfType = PsiTreeUtil.getParentOfType(PsiTreeUtil.prevLeaf(element), parentClass, true, stopAt);
    }
    return parentOfType;
  }

  public static int getCurrentParameterIndex(ASTNode argList, int offset, IElementType delimiterType) {
    int curOffset = argList.getTextRange().getStartOffset();
    if (offset < curOffset) return -1;
    ASTNode[] children = argList.getChildren(null);
    int index = 0;

    for (ASTNode child : children) {
      curOffset += child.getTextLength();
      if (offset < curOffset) break;

      IElementType type = child.getElementType();
      if (type == delimiterType) index++;
    }

    return index;
  }

  @Nullable
  public static <E extends PsiElement> E findArgumentList(PsiFile file, int offset, int lbraceOffset,
                                                          @NotNull ParameterInfoHandlerWithTabActionSupport findArgumentListHelper){
    if (file == null) return null;

    CharSequence chars = file.getViewProvider().getContents();
    if (offset >= chars.length()) offset = chars.length() - 1;
    int offset1 = CharArrayUtil.shiftBackward(chars, offset, " \t\n\r");
    if (offset1 < 0) return null;
    boolean acceptRparenth = true;
    boolean acceptLparenth = false;
    if (offset1 != offset){
      offset = offset1;
      acceptRparenth = false;
      acceptLparenth = true;
    }

    PsiElement element = file.findElementAt(offset);
    if (element == null) return null;
    PsiElement parent = element.getParent();

    while(true){
      if (findArgumentListHelper.getArgumentListClass().isInstance(parent)) {
        TextRange range = parent.getTextRange();
        if (!acceptRparenth){
          if (offset == range.getEndOffset() - 1){
            PsiElement[] children = parent.getChildren();
            if (children.length == 0) return null;
            PsiElement last = children[children.length - 1];
            if (last.getNode().getElementType() == findArgumentListHelper.getActualParametersRBraceType()){
              parent = parent.getParent();
              continue;
            }
          }
        }
        if (!acceptLparenth){
          if (offset == range.getStartOffset()){
            parent = parent.getParent();
            continue;
          }
        }
        if (lbraceOffset >= 0 && range.getStartOffset() != lbraceOffset){
          parent = parent.getParent();
          continue;
        }
        break;
      }
      if (parent instanceof PsiFile || parent == null) return null;

      final Set<? extends Class> set = findArgumentListHelper.getArgListStopSearchClasses();
      for (Class aClass : set) {
        if (aClass.isInstance(parent)) return null;
      }

      parent = parent.getParent();
    }

    PsiElement listParent = parent.getParent();
    for(Class c: (Set<Class>)findArgumentListHelper.getArgumentListAllowedParentClasses()) {
      if (c.isInstance(listParent)) return (E)parent;
    }

    return null;
  }
}

@@ -671,16 +671,61 @@
 STNode;%0A
+import com.intellij.openapi.util.Conditions;%0A
 import c
@@ -868,16 +868,57 @@
 eSpace;%0A
+import com.intellij.psi.SyntaxTraverser;%0A
 import c
@@ -2148,154 +2148,416 @@
     
-int curOffset = argList.getTextRange().getStartOffset();%0A    if (offset %3C curOffset) return -1;%0A    ASTNode%5B%5D children = argList.getChildren(null)
+SyntaxTraverser%3CASTNode%3E s = SyntaxTraverser.astTraverser(argList).expandAndSkip(Conditions.is(argList));%0A    return getCurrentParameterIndex(s, offset, delimiterType);%0A  %7D%0A%0A  public static %3CV%3E int getCurrentParameterIndex(SyntaxTraverser%3CV%3E s, int offset, IElementType delimiterType) %7B%0A    V root = s.getRoot();%0A    int curOffset = s.api.rangeOf(root).getStartOffset();%0A    if (offset %3C curOffset) return -1
 ;%0A  
@@ -2583,23 +2583,17 @@
    for (
-ASTNode
+V
  child :
@@ -2593,24 +2593,17 @@
 child : 
-children
+s
 ) %7B%0A    
@@ -2621,21 +2621,32 @@
  += 
+s.api.rangeOf(
 child
+)
 .get
-Text
 Leng
@@ -2719,29 +2719,26 @@
 e = 
-child.getElementType(
+s.api.typeOf(child
 );%0A 

support non-flat structures