package jetbrains.mps.baseLanguage.util.plugin.refactorings;

/*Generated by MPS */

import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes;
import java.util.Objects;
import jetbrains.mps.lang.dataFlow.framework.Program;
import jetbrains.mps.lang.dataFlow.MPSProgramBuilder;
import jetbrains.mps.lang.dataFlow.framework.AnalysisResult;
import java.util.Set;
import jetbrains.mps.lang.dataFlow.framework.instructions.WriteInstruction;
import jetbrains.mps.lang.dataFlow.framework.analyzers.ReachingDefinitionsAnalyzer;
import jetbrains.mps.lang.dataFlow.framework.instructions.Instruction;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SConcept;

public class InlineVariableReferenceRefactoring extends InlineVariableRefactoring {
  private SNode myReference;
  private SNode myAssignment;
  public InlineVariableReferenceRefactoring(SNode node) {
    if (!(SNodeOperations.isInstanceOf(SLinkOperations.getTarget(node, LINKS.variableDeclaration$N1XG), CONCEPTS.LocalVariableDeclaration$41))) {
      throw new IllegalArgumentException();
    }

    this.myReference = node;
    this.findAssignment(node);
  }
  @Override
  public SNode doRefactoring() {
    final SNode variable = SNodeOperations.as(SLinkOperations.getTarget(this.myReference, LINKS.variableDeclaration$N1XG), CONCEPTS.LocalVariableDeclaration$41);
    SNode nodeToSelect;
    if (myAssignment == null) {
      nodeToSelect = SNodeOperations.copyNode(SLinkOperations.getTarget(variable, LINKS.initializer$2twD));
      SNodeOperations.replaceWithAnother(this.myReference, nodeToSelect);
    } else
    if (SNodeOperations.isInstanceOf(myAssignment, CONCEPTS.VariableDeclaration$Y0)) {
      nodeToSelect = SNodeOperations.copyNode(SLinkOperations.getTarget(SNodeOperations.cast(myAssignment, CONCEPTS.VariableDeclaration$Y0), LINKS.initializer$2twD));
      SNodeOperations.replaceWithAnother(this.myReference, nodeToSelect);
    } else if (SNodeOperations.isInstanceOf(myAssignment, CONCEPTS.AssignmentExpression$SE)) {
      if (ListSequence.fromList(SNodeOperations.getNodeDescendants(SLinkOperations.getTarget(SNodeOperations.cast(myAssignment, CONCEPTS.AssignmentExpression$SE), LINKS.rValue$spNK), CONCEPTS.VariableReference$TC, false, new SAbstractConcept[]{})).where(new _FunctionTypes._return_P1_E0<Boolean, SNode>() {
        public Boolean invoke(SNode it) {
          return Objects.equals(SLinkOperations.getTarget(it, LINKS.variableDeclaration$N1XG), variable);
        }
      }).isNotEmpty()) {
        // Assignments referring to the variable from their right side should not be inlined, since the resulting code will have different semantics than the original
        return myReference;
      }
      nodeToSelect = SNodeOperations.copyNode(SLinkOperations.getTarget(SNodeOperations.cast(myAssignment, CONCEPTS.AssignmentExpression$SE), LINKS.rValue$spNK));
      SNodeOperations.replaceWithAnother(this.myReference, nodeToSelect);
      this.optimizeAssignment(SNodeOperations.cast(myAssignment, CONCEPTS.AssignmentExpression$SE), variable);
    } else {
      // ATM we do not inline if the last update was through a++ nor a+=1 type-of expressions
      return myReference;
    }
    InlinePrecedenceUtil.parenthesiseIfNecessary(nodeToSelect);
    this.optimizeDeclaration(variable);
    return nodeToSelect;
  }
  private void findAssignment(SNode node) {
    SNode variable = SLinkOperations.getTarget(node, LINKS.variableDeclaration$N1XG);
    myAssignment = null;
    SNode currentList = SNodeOperations.getNodeAncestor(node, CONCEPTS.StatementList$m_, false, false);
    SNode currentStatement;
    while ((myAssignment == null) && (currentList != null)) {
      currentStatement = getParentStatement(node, currentList);
      Program program = new MPSProgramBuilder().buildProgram(currentList);
      AnalysisResult<Set<WriteInstruction>> definitions = program.analyze(new ReachingDefinitionsAnalyzer());
      for (Instruction nodeInstruction : ListSequence.fromList(program.getInstructionsFor(currentStatement))) {
        for (WriteInstruction instruction : SetSequence.fromSet(definitions.get(nodeInstruction))) {
          if (instruction.getVariable() == variable) {
            SNode assignmentNode = (SNode) instruction.getSource();
            // We need to avoid inlining a self-assignment
            if (!(ListSequence.fromList(SNodeOperations.getNodeAncestors(node, null, false)).contains(assignmentNode))) {
              myAssignment = (assignmentNode);
            }
          }
        }
      }
      currentList = SNodeOperations.getNodeAncestor(currentList, CONCEPTS.StatementList$m_, false, false);
    }
  }
  private SNode getParentStatement(SNode node, SNode list) {
    SNode curParent = node;
    while ((curParent != null) && SNodeOperations.getParent(curParent) != list) {
      curParent = SNodeOperations.getParent(curParent);
    }
    return SNodeOperations.cast(curParent, CONCEPTS.Statement$P6);
  }

  private static final class LINKS {
    /*package*/ static final SReferenceLink variableDeclaration$N1XG = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c77f1e98L, 0xf8cc6bf960L, "variableDeclaration");
    /*package*/ static final SContainmentLink initializer$2twD = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c37a7f6eL, 0xf8c37f506eL, "initializer");
    /*package*/ static final SContainmentLink rValue$spNK = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11b0d00332cL, 0xf8c77f1e99L, "rValue");
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept LocalVariableDeclaration$41 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc67c7efL, "jetbrains.mps.baseLanguage.structure.LocalVariableDeclaration");
    /*package*/ static final SConcept VariableDeclaration$Y0 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c37a7f6eL, "jetbrains.mps.baseLanguage.structure.VariableDeclaration");
    /*package*/ static final SConcept AssignmentExpression$SE = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c77f1e96L, "jetbrains.mps.baseLanguage.structure.AssignmentExpression");
    /*package*/ static final SConcept VariableReference$TC = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c77f1e98L, "jetbrains.mps.baseLanguage.structure.VariableReference");
    /*package*/ static final SConcept StatementList$m_ = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b200L, "jetbrains.mps.baseLanguage.structure.StatementList");
    /*package*/ static final SConcept Statement$P6 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b215L, "jetbrains.mps.baseLanguage.structure.Statement");
  }
}
