package jetbrains.mps.baseLanguage.kotlinRefs.constraints;

/*Generated by MPS */

import jetbrains.mps.kotlin.scopes.SignatureFilterImpl;
import jetbrains.mps.kotlin.signatures.PropertySignature;
import jetbrains.mps.kotlin.signatures.AccessorKind;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.scope.Scope;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import jetbrains.mps.kotlin.scopes.SignatureFilter;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.scope.EmptyScope;
import jetbrains.mps.typechecking.TypecheckingFacade;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.kotlin.scopes.TypeMembersVisitor;
import jetbrains.mps.kotlin.scopes.VisibilityAccess;
import jetbrains.mps.kotlin.behavior.IType__BehaviorDescriptor;
import jetbrains.mps.kotlin.baseLanguage.toKotlin.JavaToKtConversion;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.lang.scopes.runtime.NamedElementsScope;
import jetbrains.mps.internal.collections.runtime.Sequence;
import org.jetbrains.mps.openapi.language.SConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SReferenceLink;

public class KotlinInBaseLanguageScopeHelper {
  /*package*/ static class AccessorFilter extends SignatureFilterImpl<PropertySignature> {
    private final AccessorKind myKind;
    public AccessorFilter(AccessorKind kind) {
      super(PropertySignature.class);
      myKind = kind;
    }
    @Override
    protected boolean accept(PropertySignature signature, SNode source) {
      return signature.getKind() == myKind;
    }
  }

  public static Scope getScopeForAccessor(SNode referenceNode, SNode contextNode, AccessorKind kind) {
    return getScopeForMember(referenceNode, contextNode, CONCEPTS.VariableDeclaration$DF, new AccessorFilter(kind));
  }

  public static Scope getScopeForMember(SNode referenceNode, SNode contextNode, SAbstractConcept filteringConcept, SignatureFilter signatureFilter) {
    final SNode enclosingNode = (((referenceNode == null) ? contextNode : SNodeOperations.getParent(referenceNode)));
    if (!(SNodeOperations.isInstanceOf(enclosingNode, CONCEPTS.DotExpression$yW))) {
      return new EmptyScope();
    }
    SNode instanceType = TypecheckingFacade.getFromContext().computeIsolated(() -> TypecheckingFacade.getFromContext().getTypeOf(SLinkOperations.getTarget(SNodeOperations.cast(enclosingNode, CONCEPTS.DotExpression$yW), LINKS.operand$w6IR)));

    // We go through kotlin types as they are aware of their properties members (we don't care about java variables here)
    // TODO use TOP_LEVEL if not called from a class inheriting the current class
    TypeMembersVisitor visitor = new TypeMembersVisitor(signatureFilter, contextNode, VisibilityAccess.TYPE_PROTECTED);

    // Main type
    IType__BehaviorDescriptor.visitHierarchy_id5q426iHtYvR.invoke(JavaToKtConversion.convert(SNodeOperations.as(instanceType, CONCEPTS.Type$bu)), visitor);

    // Scopes from bounds
    // TODO is that going to be handled by the converted type of TypeVariableReference?
    if (ListSequence.fromList(SLinkOperations.getChildren(SLinkOperations.getTarget(SNodeOperations.as(instanceType, CONCEPTS.TypeVariableReference$WL), LINKS.typeVariableDeclaration$Lz1I), LINKS.auxBounds$jgLr)).isNotEmpty()) {
      for (SNode t : SLinkOperations.getChildren(SLinkOperations.getTarget(SNodeOperations.as(instanceType, CONCEPTS.TypeVariableReference$WL), LINKS.typeVariableDeclaration$Lz1I), LINKS.auxBounds$jgLr)) {
        // Reusing same visitor (should be ok, right?)
        IType__BehaviorDescriptor.visitHierarchy_id5q426iHtYvR.invoke(JavaToKtConversion.convert(t), visitor);
      }
    }

    return new NamedElementsScope(SNodeOperations.ofConcept(Sequence.fromIterable(visitor.getMembers()).select((it) -> it.getSource()), SNodeOperations.asSConcept(filteringConcept)));
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept VariableDeclaration$DF = MetaAdapterFactory.getConcept(0x6b3888c1980244d8L, 0x8baff8e6c33ed689L, 0x28bef6d7551af571L, "jetbrains.mps.kotlin.structure.VariableDeclaration");
    /*package*/ static final SConcept DotExpression$yW = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x116b46a08c4L, "jetbrains.mps.baseLanguage.structure.DotExpression");
    /*package*/ static final SConcept Type$bu = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c37f506dL, "jetbrains.mps.baseLanguage.structure.Type");
    /*package*/ static final SConcept TypeVariableReference$WL = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x102467229d8L, "jetbrains.mps.baseLanguage.structure.TypeVariableReference");
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink operand$w6IR = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x116b46a08c4L, 0x116b46a4416L, "operand");
    /*package*/ static final SReferenceLink typeVariableDeclaration$Lz1I = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x102467229d8L, 0x1024673a581L, "typeVariableDeclaration");
    /*package*/ static final SContainmentLink auxBounds$jgLr = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x1024639ed74L, 0x11ae913a476L, "auxBounds");
  }
}
