/*
 * Copyright 2000-2007 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 org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary;

import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrExpressionImpl;

/**
 * @author ilyas
 */
public class GrListOrMapImpl extends GrExpressionImpl implements GrListOrMap {
  private static final TokenSet MAP_LITERAL_TOKEN_SET = TokenSet.create(GroovyElementTypes.ARGUMENT, GroovyTokenTypes.mCOLON);
  private static final Function<GrListOrMapImpl, PsiType> TYPES_CALCULATOR = new MyTypesCalculator();

  public GrListOrMapImpl(@NotNull ASTNode node) {
    super(node);
  }

  public void accept(GroovyElementVisitor visitor) {
    visitor.visitListOrMap(this);
  }

  public String toString() {
    return "Generalized list";
  }

  public PsiType getType() {
    return GroovyPsiManager.getInstance(getProject()).getType(this, TYPES_CALCULATOR);
  }

  public boolean isMap() {
    return findChildByType(MAP_LITERAL_TOKEN_SET) != null;
  }

  @NotNull
  public GrExpression[] getInitializers() {
    return findChildrenByClass(GrExpression.class);
  }

  @NotNull
  public GrNamedArgument[] getNamedArguments() {
    return findChildrenByClass(GrNamedArgument.class);
  }

  private static class MyTypesCalculator implements Function<GrListOrMapImpl, PsiType> {
    public PsiType fun(GrListOrMapImpl listOrMap) {
      PsiManager manager = listOrMap.getManager();
      final GlobalSearchScope scope = listOrMap.getResolveScope();
      if (listOrMap.isMap()) {
        return inferMapInitializerType(listOrMap, manager, scope);
      }

      PsiElement parent = listOrMap.getParent();
      if (parent.getParent() instanceof GrVariableDeclaration) {
        GrTypeElement typeElement = ((GrVariableDeclaration) parent.getParent()).getTypeElementGroovy();
        if (typeElement != null) {
          PsiType declaredType = typeElement.getType();
          if (declaredType instanceof PsiArrayType) return declaredType;
        }
      }

      return getTupleType(listOrMap.getInitializers(), listOrMap.getManager(), listOrMap.getResolveScope());
    }

    private PsiClassType inferMapInitializerType(GrListOrMapImpl listOrMap, PsiManager manager, GlobalSearchScope scope) {
      PsiClass mapClass = manager.findClass("java.util.Map", scope);
      PsiElementFactory factory = manager.getElementFactory();
      if (mapClass != null) {
        PsiTypeParameter[] typeParameters = mapClass.getTypeParameters();
        if (typeParameters.length == 2) {
          GrNamedArgument[] namedArgs = listOrMap.getNamedArguments();
          GrExpression[] values = new GrExpression[namedArgs.length];
          for (int i = 0; i < values.length; i++) {
            GrExpression expr = namedArgs[i].getExpression();
            if (expr == null) return null;
            values[i] = expr;
          }
          PsiType initializerType = getInitializerType(values);
          PsiSubstitutor substitutor = PsiSubstitutor.EMPTY.
              put(typeParameters[0], factory.createTypeByFQClassName("java.lang.Object", scope)). //todo detail key type
              put(typeParameters[1], initializerType);
          return factory.createType(mapClass, substitutor);
        } else {
          return manager.getElementFactory().createType(mapClass);
        }
      }
      return null;
    }

    private PsiClassType getTupleType(GrExpression[] initializers, PsiManager manager, GlobalSearchScope scope) {
      PsiType[] result = new PsiType[initializers.length];
      for (int i = 0; i < result.length; i++) {
        result[i] = initializers[i].getType();
      }
      return new GrTupleType(result, manager, scope);
    }

    private PsiType getInitializerType(GrExpression[] initializers) {
      if (initializers.length == 0) return null;
      PsiManager manager = initializers[0].getManager();
      PsiType result = initializers[0].getType();
      for (int i = 1; i < initializers.length; i++) {
        final PsiType other = initializers[i].getType();
        if (other == null) continue;
        if (result == null) result = other;
        if (result.isAssignableFrom(other)) continue;
        if (other.isAssignableFrom(result)) result = other;
        result = GenericsUtil.getLeastUpperBound(result, other, manager);
      }

      return result;
    }
  }
}

@@ -1082,16 +1082,75 @@
 isitor;%0A
+import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;%0A
 import o
@@ -3605,50 +3605,8 @@
 rMap
-.getManager(), listOrMap.getResolveScope()
 );%0A 
@@ -4912,51 +4912,29 @@
 rs, 
-PsiManager manager, GlobalSearchScope scope
+GrListOrMap listOrMap
 ) %7B%0A
@@ -4988,24 +4988,78 @@
 rs.length%5D;%0A
+      boolean isLValue = PsiUtil.isLValue(listOrMap);%0A
       for (i
@@ -5113,16 +5113,62 @@
 ult%5Bi%5D =
+ isLValue ? initializers%5Bi%5D.getNominalType() :
  initial
@@ -5232,30 +5232,67 @@
 result, 
-manager, s
+listOrMap.getManager(), listOrMap.getResolveS
 cope
+()
 );%0A    %7D

fix type system