// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.

package org.jetbrains.plugins.groovy.lang.psi.impl;

import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.VolatileNotNullLazyValue;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiType;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;

/**
 * @author ven
 */
public abstract class GrTupleType extends GrLiteralClassType {
  private final VolatileNotNullLazyValue<PsiType[]> myParameters = new VolatileNotNullLazyValue<PsiType[]>() {
    @NotNull
    @Override
    protected PsiType[] compute() {
      PsiType[] types = getComponentTypes();
      if (types.length == 0) return PsiType.EMPTY_ARRAY;
      final PsiType leastUpperBound = getLeastUpperBound(types);
      if (leastUpperBound == PsiType.NULL) return EMPTY_ARRAY;
      return new PsiType[]{leastUpperBound};
    }
  };

  private final VolatileNotNullLazyValue<PsiType[]> myComponents = new VolatileNotNullLazyValue<PsiType[]>() {
    @NotNull
    @Override
    protected PsiType[] compute() {
      return inferComponents();
    }
  };

  public GrTupleType(@NotNull GlobalSearchScope scope, @NotNull JavaPsiFacade facade) {
    this(scope, facade, LanguageLevel.JDK_1_5);
  }

  public GrTupleType(@NotNull GlobalSearchScope scope,
                     @NotNull JavaPsiFacade facade,
                     @NotNull LanguageLevel level) {
    super(level, scope, facade);
  }

  @NotNull
  @Override
  protected String getJavaClassName() {
    return CommonClassNames.JAVA_UTIL_LIST;
  }

  @Override
  @NotNull
  public PsiType[] getParameters() {
    return myParameters.getValue();
  }

  @Override
  @NotNull
  public String getInternalCanonicalText() {
    PsiType[] types = getComponentTypes();

    StringBuilder builder = new StringBuilder();
    builder.append("[");
    for (int i = 0; i < types.length; i++) {
      if (i >= 2) {
        builder.append(",...");
        break;
      }

      if (i > 0) builder.append(", ");
      builder.append(getInternalCanonicalText(types[i]));
    }
    builder.append("]");
    return builder.toString();
  }

  public boolean equals(Object obj) {
    if (obj instanceof GrTupleType) {
      PsiType[] componentTypes = getComponentTypes();
      PsiType[] otherComponents = ((GrTupleType)obj).getComponentTypes();
      for (int i = 0; i < Math.min(componentTypes.length, otherComponents.length); i++) {
        if (!Comparing.equal(componentTypes[i], otherComponents[i])) return false;
      }
      return true;
    }
    return super.equals(obj);
  }

  @Override
  public boolean isAssignableFrom(@NotNull PsiType type) {
    if (type instanceof GrTupleType) {
      PsiType[] otherComponents = ((GrTupleType)type).getComponentTypes();
      PsiType[] componentTypes = getComponentTypes();
      for (int i = 0; i < Math.min(componentTypes.length, otherComponents.length); i++) {
        PsiType componentType = componentTypes[i];
        PsiType otherComponent = otherComponents[i];
        if (otherComponent == null) {
          if (componentType != null && !TypesUtil.isClassType(componentType, CommonClassNames.JAVA_LANG_OBJECT)) return false;
        }
        else if (componentType != null && !componentType.isAssignableFrom(otherComponent)) return false;
      }
      return true;
    }

    return super.isAssignableFrom(type);
  }

  @NotNull
  public PsiType[] getComponentTypes() {
    return myComponents.getValue();
  }

  @NotNull
  protected abstract  PsiType[] inferComponents();

  @NotNull
  @Override
  public PsiClassType setLanguageLevel(@NotNull LanguageLevel languageLevel) {
    return new GrImmediateTupleType(getComponentTypes(), myFacade, getResolveScope());
  }
}

@@ -134,17 +134,16 @@
 E file.%0A
-%0A
 package 
@@ -667,31 +667,8 @@
 l;%0A%0A
-/**%0A * @author ven%0A */%0A
 publ
@@ -726,16 +726,17 @@
 sType %7B%0A
+%0A
   privat
@@ -793,20 +793,16 @@
 meters =
- new
  Volatil
@@ -822,89 +822,29 @@
 alue
-%3CPsiType%5B%5D%3E() %7B%0A    @NotNull%0A    @Override%0A    protected PsiType%5B%5D compute() %7B%0A  
+.createValue(() -%3E %7B%0A
     
@@ -878,26 +878,24 @@
 entTypes();%0A
-  
     if (type
@@ -941,18 +941,16 @@
 AY;%0A    
-  
 final Ps
@@ -1000,26 +1000,24 @@
 types);%0A    
-  
 if (leastUpp
@@ -1061,18 +1061,16 @@
 _ARRAY;%0A
-  
     retu
@@ -1102,31 +1102,26 @@
 erBound%7D;%0A  
-  %7D%0A  
 %7D
+)
 ;%0A%0A  private
@@ -1177,20 +1177,16 @@
 onents =
- new
  Volatil
@@ -1206,100 +1206,27 @@
 alue
-%3CPsiType%5B%5D%3E() %7B%0A    @NotNull%0A    @Override%0A    protected PsiType%5B%5D compute() %7B%0A      return 
+.createValue(this::
 infe
@@ -1240,21 +1240,9 @@
 ents
-();%0A    %7D%0A  %7D
+)
 ;%0A%0A 

use VolatileNotNullLazyValue factory method with lambda in GrTupleType