package jetbrains.mps.kotlin.stubs.jvmStubs;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.baseLanguage.javastub.ClassifierUpdater;
import jetbrains.mps.kotlin.stubs.common.metadata.VisitorContext;
import jetbrains.mps.baseLanguage.javastub.asm.ASMClass;
import org.jetbrains.annotations.Nullable;
import jetbrains.mps.baseLanguage.javastub.Documentation;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.baseLanguage.javastub.asm.ASMAnnotation;
import jetbrains.mps.baseLanguage.javastub.asm.ASMClassType;
import java.util.Map;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import org.jetbrains.mps.openapi.model.ResolveInfo;
import org.jetbrains.mps.openapi.model.SModelReference;
import jetbrains.mps.smodel.SNodePointer;
import jetbrains.mps.baseLanguage.javastub.ASMNodeId;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.baseLanguage.javastub.asm.ASMEnumValue;
import jetbrains.mps.kotlin.stubs.common.references.StereotypeReference;
import jetbrains.mps.baseLanguage.javastub.asm.ASMType;
import jetbrains.mps.baseLanguage.javastub.asm.ASMParameterizedType;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import java.util.List;
import java.util.ArrayList;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import jetbrains.mps.smodel.builder.SNodeBuilder;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SConcept;

/**
 * Loads deeply java content from a class file. This extends regular ClassifierUpdater to use VisitorContext API and
 * create references to either kotlin or java declarations when necessary.
 */
@GeneratedClass(node = "r:bdaa2532-d0d0-46ce-8145-d47be9b807a4(jetbrains.mps.kotlin.stubs.jvmStubs)/3833041373521274818", model = "r:bdaa2532-d0d0-46ce-8145-d47be9b807a4(jetbrains.mps.kotlin.stubs.jvmStubs)")
public class KotlinAwareClassifierUpdater extends ClassifierUpdater {
  private final VisitorContext mySolvingContext;

  public KotlinAwareClassifierUpdater(ASMClass asmClass, boolean skipPrivate, @Nullable Documentation doc, VisitorContext solvingContext) {
    super(asmClass, skipPrivate, null, doc);
    mySolvingContext = solvingContext;
  }

  public static String convertToKotlinNorm(String name) {
    return name.replaceAll("\\.", "/").replaceAll("\\$", ".");
  }

  protected SNode createAnnotation(final ASMAnnotation annotation) {
    final ASMClassType c = (ASMClassType) annotation.getType();

    // We create annotation depending on concept
    return mySolvingContext.createClassReference(convertToKotlinNorm(c.getName()), (stereotype, resolveInfo) -> {
      SNode node = stereotype.createJavaAnnotation(resolveInfo);

      if ((node != null)) {
        // here, it assumes annotation is a java one, because kotlin is not supported there yet
        Map<String, Object> values = ((Map<String, Object>) annotation.getValues());
        for (String method : MapSequence.fromMap(values).keySet()) {
          SNode value = _quotation_createNode_uk01hx_a0a0c0c0b0a3a6(getValueAsExpression(MapSequence.fromMap(values).get(method)));

          // Bypass addAnnotationMethodReference
          if (resolveInfo instanceof ResolveInfo.PS && ((ResolveInfo.PS) resolveInfo).getTargetNode().getModelReference() != null) {
            // Should be in same model as the declaring class
            SModelReference modelReference = ((ResolveInfo.PS) resolveInfo).getTargetNode().getModelReference();
            value.setReference(LINKS.key$bSmV, ResolveInfo.of(new SNodePointer(modelReference, ASMNodeId.createAnnotationMethodId(c.getName(), method)), method));
          } else {
            value.setReference(LINKS.key$bSmV, ResolveInfo.of(method));
          }

          ListSequence.fromList(SLinkOperations.getChildren(node, LINKS.value$uK2B)).addElement(value);
        }
        return node;
      }

      return node;
    });
  }

  @Override
  protected SNode getValueAsExpression(Object value) {
    if (value instanceof ASMEnumValue) {
      final ASMEnumValue enumValue = (ASMEnumValue) value;
      ASMClassType c = (ASMClassType) enumValue.getType();
      final String enumClassName = convertToKotlinNorm(c.getName());

      // TODO here, we get two stereotype variables, though it is unlikely we get both java and kotlin results at once, we may find a better way to handle that (getting the node id, adding .name for instance?)
      return mySolvingContext.createClassReference(enumClassName, (StereotypeReference.ClassStereotype s, final ResolveInfo enumResolveInfo) -> {
        // Resolve constant reference as well
        return mySolvingContext.createClassReference(enumClassName + "." + enumValue.getConstant(), (stereotype, constantResolveInfo) -> {
          // Create from second stereotype
          return stereotype.createJavaEnumConstantReference(enumResolveInfo, constantResolveInfo);
        });
      });
    }

    if (value instanceof ASMClassType) {
      return mySolvingContext.createClassReference(convertToKotlinNorm(((ASMClassType) value).getName()), (this0, classResolveInfo) -> this0.createJavaClassQualifierReference(classResolveInfo));
    }

    return super.getValueAsExpression(value);
  }
  protected SNode getTypeByASMType(ASMType type, SNode method, SNode classifier) {
    if (type instanceof ASMClassType) {
      ASMClassType c = (ASMClassType) type;
      SNode typeNode = mySolvingContext.createClassReference(convertToKotlinNorm(c.getName()), (this0, resolved) -> this0.createJavaType(resolved));
      return typeNode;
    }

    // Supertype only supports classifier type
    if (type instanceof ASMParameterizedType) {
      ASMParameterizedType pt = (ASMParameterizedType) type;
      SNode rawType = getTypeByASMType(pt.getRawType(), method, classifier);
      if (SNodeOperations.isInstanceOf(rawType, CONCEPTS.ClassifierType$bL)) {
        addTypeParameters(pt.getActualTypeArguments(), method, classifier, SNodeOperations.cast(rawType, CONCEPTS.ClassifierType$bL));
      } else if (SNodeOperations.isInstanceOf(rawType, CONCEPTS.KotlinClassifierType$jV)) {
        addKtTypeParameters(pt.getActualTypeArguments(), method, classifier, SNodeOperations.cast(rawType, CONCEPTS.KotlinClassifierType$jV));
      }
      return rawType;
    }
    return super.getTypeByASMType(type, method, classifier);
  }

  protected void addKtTypeParameters(List<? extends ASMType> typeParameters, SNode method, SNode classifier, SNode result) {
    // TODO make parent method generic? only the link changes
    List<SNode> toAdd = new ArrayList<SNode>();
    for (ASMType tv : typeParameters) {
      SNode type = getTypeByASMType(tv, method, classifier);
      if (type == null) {
        toAdd.clear();
        break;
      }
      toAdd.add(type);
    }
    ListSequence.fromList(SLinkOperations.getChildren(result, LINKS.parameter$EcOI)).addSequence(ListSequence.fromList(toAdd));
  }

  @Override
  protected void addClassifierReference(SNode sourceNode, SReferenceLink role, ASMClassType clsType) {
    // no-op: overriden case by case
  }
  private static SNode _quotation_createNode_uk01hx_a0a0c0c0b0a3a6(Object parameter_1) {
    SNode quotedNode_2 = null;
    SNode quotedNode_3 = null;
    SNodeBuilder nb = new SNodeBuilder(null, null).init(MetaAdapterFactory.getConcept(MetaAdapterFactory.getLanguage(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, "jetbrains.mps.baseLanguage"), 0x114a71b1af4L, "AnnotationInstanceValue"));
    quotedNode_2 = nb.getResult();
    quotedNode_3 = (SNode) parameter_1;
    if (quotedNode_3 != null) {
      quotedNode_2.addChild(MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x114a71b1af4L, 0x114a71c0fc4L, "value"), SNodeOperations.copyIfNecessary(quotedNode_3));
    }
    return quotedNode_2;
  }

  private static final class LINKS {
    /*package*/ static final SReferenceLink key$bSmV = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x114a71b1af4L, 0x114a71b44e3L, "key");
    /*package*/ static final SContainmentLink value$uK2B = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x114a6b4ccabL, 0x114a71c697fL, "value");
    /*package*/ static final SContainmentLink parameter$EcOI = MetaAdapterFactory.getContainmentLink(0x2405a196e75d462cL, 0x938bae8e3fac20aaL, 0x68fcefc6c20b5c6cL, 0x102419671abL, "parameter");
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept ClassifierType$bL = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101de48bf9eL, "jetbrains.mps.baseLanguage.structure.ClassifierType");
    /*package*/ static final SConcept KotlinClassifierType$jV = MetaAdapterFactory.getConcept(0x2405a196e75d462cL, 0x938bae8e3fac20aaL, 0x68fcefc6c20b5c6cL, "jetbrains.mps.baseLanguage.kotlinRefs.structure.KotlinClassifierType");
  }
}
