package jetbrains.mps.baseLanguage.methodReferences.intentions;

/*Generated by MPS */

import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.baseLanguage.behavior.IMethodLike__BehaviorDescriptor;
import jetbrains.mps.baseLanguage.behavior.StatementList__BehaviorDescriptor;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import java.util.List;
import java.util.ArrayList;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import jetbrains.mps.baseLanguage.behavior.IVariableReference__BehaviorDescriptor;
import java.util.Iterator;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SInterfaceConcept;

public class ClosureToMethodRefHelper {
  /**
   * Returns a single returned expression contained in a closure if the closure only contains one, otherwise returns null
   * 
   * @param closure closure to get the returned expression from
   * @return expression that the closure will return
   */
  public static SNode getSingleExpression(SNode closure) {
    SNode body = IMethodLike__BehaviorDescriptor.getBody_idi2fhZ_m.invoke(closure);
    if (!((boolean) StatementList__BehaviorDescriptor.isOneLiner_idi0z3USV.invoke(body))) {
      return null;
    }

    SNode firstStement = ListSequence.fromList(SLinkOperations.getChildren(body, LINKS.statement$53DE)).first();
    SNode containedExpression = null;
    {
      final SNode returnStmt = firstStement;
      if (SNodeOperations.isInstanceOf(returnStmt, CONCEPTS.ReturnStatement$lt)) {
        containedExpression = SLinkOperations.getTarget(returnStmt, LINKS.expression$eJ92);
      }
    }

    {
      final SNode exprStmt = firstStement;
      if (SNodeOperations.isInstanceOf(exprStmt, CONCEPTS.ExpressionStatement$O8)) {
        containedExpression = SLinkOperations.getTarget(exprStmt, LINKS.expression$5L7M);
      }
    }

    return containedExpression;
  }

  /**
   * Returns a method call extracted from the expression, or null if not applicable
   * 
   * @param expression closure to get the method call from
   * @return method call
   */
  public static SNode getSingleMethodCall(SNode expression) {
    SNode methodCall = SNodeOperations.as(expression, CONCEPTS.IMethodCall$M9);
    {
      final SNode dotExpr = expression;
      if (SNodeOperations.isInstanceOf(dotExpr, CONCEPTS.DotExpression$yW)) {
        methodCall = SNodeOperations.as(SLinkOperations.getTarget(dotExpr, LINKS.operation$gs9E), CONCEPTS.IMethodCall$M9);
      }
    }
    {
      final SNode newExpr = expression;
      if (SNodeOperations.isInstanceOf(newExpr, CONCEPTS.GenericNewExpression$Fh)) {
        methodCall = SNodeOperations.as(SLinkOperations.getTarget(newExpr, LINKS.creator$BsHW), CONCEPTS.IMethodCall$M9);
      }
    }

    return methodCall;
  }

  /**
   * Checks whether the given closure literal can be converted to method reference
   * 
   * @param node closure literal
   */
  public static boolean canBeConverted(final SNode node) {
    SNode expression = getSingleExpression(node);
    SNode exprAsDotExpr = SNodeOperations.as(expression, CONCEPTS.DotExpression$yW);
    SNode methodCall = getSingleMethodCall(expression);

    if ((methodCall == null)) {
      return false;
    }

    List<SNode> actualArguments = new ArrayList<SNode>();
    ListSequence.fromList(actualArguments).addSequence(ListSequence.fromList(SLinkOperations.getChildren(methodCall, LINKS.actualArgument$pzdx)));

    // One special case : static call of instance method
    if (ListSequence.fromList(actualArguments).count() + 1 == ListSequence.fromList(SLinkOperations.getChildren(node, LINKS.parameter$b4Y3)).count() && SNodeOperations.isInstanceOf(methodCall, CONCEPTS.InstanceMethodCallOperation$uu) && (exprAsDotExpr != null)) {
      ListSequence.fromList(actualArguments).insertElement(0, SLinkOperations.getTarget(exprAsDotExpr, LINKS.operand$w6IR));
    } else if (ListSequence.fromList(actualArguments).count() != ListSequence.fromList(SLinkOperations.getChildren(node, LINKS.parameter$b4Y3)).count()) {
      return false;
    } else if ((exprAsDotExpr != null)) {
      // Time to check operand does not contains references to lambda
      boolean dependsOnParameter = ListSequence.fromList(SNodeOperations.getNodeDescendants(SLinkOperations.getTarget(exprAsDotExpr, LINKS.operand$w6IR), CONCEPTS.IVariableReference$C, false, new SAbstractConcept[]{})).any((it) -> SNodeOperations.getNodeAncestor(IVariableReference__BehaviorDescriptor.getVariable_idSORzhOpB6t.invoke(it), CONCEPTS.ClosureLiteral$rp, false, false) == node);

      if (dependsOnParameter) {
        return false;
      }
    }

    {
      Iterator<SNode> arg_it = ListSequence.fromList(actualArguments).iterator();
      Iterator<SNode> closureArg_it = ListSequence.fromList(SLinkOperations.getChildren(node, LINKS.parameter$b4Y3)).iterator();
      SNode arg_var;
      SNode closureArg_var;
      while (arg_it.hasNext() && closureArg_it.hasNext()) {
        arg_var = arg_it.next();
        closureArg_var = closureArg_it.next();
        if (!(SNodeOperations.isInstanceOf(arg_var, CONCEPTS.VariableReference$TC)) || SLinkOperations.getTarget(SNodeOperations.cast(arg_var, CONCEPTS.VariableReference$TC), LINKS.variableDeclaration$N1XG) != closureArg_var) {
          return false;
        }
      }
    }

    return true;
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink statement$53DE = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b200L, 0xf8cc6bf961L, "statement");
    /*package*/ static final SContainmentLink expression$eJ92 = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc67c7feL, 0xf8cc6bf96cL, "expression");
    /*package*/ static final SContainmentLink expression$5L7M = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b213L, 0xf8cc56b214L, "expression");
    /*package*/ static final SContainmentLink operation$gs9E = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x116b46a08c4L, 0x116b46b36c4L, "operation");
    /*package*/ static final SContainmentLink creator$BsHW = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10ab8473cc5L, 0x10ab847b486L, "creator");
    /*package*/ static final SContainmentLink actualArgument$pzdx = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11857355952L, 0xf8c78301aeL, "actualArgument");
    /*package*/ static final SContainmentLink operand$w6IR = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x116b46a08c4L, 0x116b46a4416L, "operand");
    /*package*/ static final SContainmentLink parameter$b4Y3 = MetaAdapterFactory.getContainmentLink(0xfd3920347849419dL, 0x907112563d152375L, 0x1174bed3125L, 0x1174bf02c34L, "parameter");
    /*package*/ static final SReferenceLink variableDeclaration$N1XG = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c77f1e98L, 0xf8cc6bf960L, "variableDeclaration");
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept ReturnStatement$lt = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc67c7feL, "jetbrains.mps.baseLanguage.structure.ReturnStatement");
    /*package*/ static final SConcept ExpressionStatement$O8 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b213L, "jetbrains.mps.baseLanguage.structure.ExpressionStatement");
    /*package*/ static final SInterfaceConcept IMethodCall$M9 = MetaAdapterFactory.getInterfaceConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11857355952L, "jetbrains.mps.baseLanguage.structure.IMethodCall");
    /*package*/ static final SConcept DotExpression$yW = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x116b46a08c4L, "jetbrains.mps.baseLanguage.structure.DotExpression");
    /*package*/ static final SConcept GenericNewExpression$Fh = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10ab8473cc5L, "jetbrains.mps.baseLanguage.structure.GenericNewExpression");
    /*package*/ static final SConcept InstanceMethodCallOperation$uu = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x118154a6332L, "jetbrains.mps.baseLanguage.structure.InstanceMethodCallOperation");
    /*package*/ static final SInterfaceConcept IVariableReference$C = MetaAdapterFactory.getInterfaceConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xe34de34746464f2L, "jetbrains.mps.baseLanguage.structure.IVariableReference");
    /*package*/ static final SConcept ClosureLiteral$rp = MetaAdapterFactory.getConcept(0xfd3920347849419dL, 0x907112563d152375L, 0x1174bed3125L, "jetbrains.mps.baseLanguage.closures.structure.ClosureLiteral");
    /*package*/ static final SConcept VariableReference$TC = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c77f1e98L, "jetbrains.mps.baseLanguage.structure.VariableReference");
  }
}
