package jetbrains.mps.baseLanguage.typesystem;

/*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.lang.dataFlow.framework.Program;
import jetbrains.mps.lang.dataFlow.MPSProgramBuilder;
import jetbrains.mps.lang.dataFlow.DataFlow;
import jetbrains.mps.lang.typesystem.dependencies.CheckingMethod;
import jetbrains.mps.typesystem.inference.TypeCheckingContext;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.errors.messageTargets.MessageTarget;
import jetbrains.mps.errors.messageTargets.NodeMessageTarget;
import jetbrains.mps.errors.IErrorReporter;
import jetbrains.mps.errors.BaseQuickFixProvider;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import java.util.Set;
import jetbrains.mps.baseLanguage.behavior.StatementList__BehaviorDescriptor;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.baseLanguage.behavior.Classifier__BehaviorDescriptor;
import java.util.List;
import java.util.ArrayList;
import jetbrains.mps.baseLanguage.behavior.ConstructorDeclaration__BehaviorDescriptor;
import jetbrains.mps.lang.dataFlow.framework.instructions.Instruction;
import java.util.Iterator;
import jetbrains.mps.lang.dataFlow.framework.instructions.WriteInstruction;
import java.util.Objects;
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.SInterfaceConcept;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SProperty;

public class VariableReferenceUtil {
  public VariableReferenceUtil() {
  }
  public static boolean isUninitializedOrBad(SNode thisNode) {
    SNode declContainer = SNodeOperations.getNodeAncestor(SLinkOperations.getTarget(thisNode, LINKS.variableDeclaration$N1XG), CONCEPTS.IStatementListContainer$xz, false, false);
    if (declContainer == null) {
      return true;
    }
    SNode currentContainer = SNodeOperations.getNodeAncestor(thisNode, CONCEPTS.IStatementListContainer$xz, false, false);
    SNode ourContainer = null;

    SNode assignmentExpression = SNodeOperations.as(SNodeOperations.getParent(thisNode), CONCEPTS.BaseAssignmentExpression$PA);
    if (SLinkOperations.getTarget(assignmentExpression, LINKS.lValue$splI) != thisNode) {
      return true;
    }

    while ((currentContainer != null)) {
      if (currentContainer == declContainer) {
        ourContainer = currentContainer;
        break;
      }
      currentContainer = SNodeOperations.getNodeAncestor(currentContainer, CONCEPTS.IStatementListContainer$xz, false, false);
    }
    if ((ourContainer != null)) {
      Program program = new MPSProgramBuilder().buildProgram(ourContainer);
      return !(DataFlow.isInitializedRewritten(program, assignmentExpression));
    } else {
      return true;
    }
  }
  public static boolean isAssigned(Iterable<SNode> references) {
    for (SNode ref : references) {
      if (CheckingUtil.isAssigned(ref)) {
        return true;
      }
    }
    return false;
  }
  public static boolean isRead(Iterable<SNode> references) {
    for (SNode ref : references) {
      if (CheckingUtil.isRead(ref)) {
        return true;
      }
    }
    return false;
  }
  @CheckingMethod
  public static void checkField(final TypeCheckingContext typeCheckingContext, SNode field, Iterable<SNode> references) {
    if (Sequence.fromIterable(references).isEmpty()) {
      {
        final MessageTarget errorTarget = new NodeMessageTarget();
        IErrorReporter _reporter_2309309498 = typeCheckingContext.reportWarning(field, "Field is never used", "r:00000000-0000-4000-0000-011c895902c5(jetbrains.mps.baseLanguage.typesystem)", "7581428506283755675", null, errorTarget);
        {
          BaseQuickFixProvider intentionProvider = new BaseQuickFixProvider("jetbrains.mps.baseLanguage.typesystem.RemoveUnusedField_QuickFix", "6640766779604118713", false);
          _reporter_2309309498.addIntentionProvider(intentionProvider);
        }
      }
    } else {
      boolean isAssigned = VariableReferenceUtil.isAssigned(references);
      boolean isRead = VariableReferenceUtil.isRead(references);
      if (SLinkOperations.getTarget(field, LINKS.initializer$2twD) != null) {
        isAssigned = true;
      }
      if (!(isAssigned)) {
        {
          final MessageTarget errorTarget = new NodeMessageTarget();
          IErrorReporter _reporter_2309309498 = typeCheckingContext.reportWarning(field, "Field " + SPropertyOperations.getString(field, PROPS.name$MnvL) + " is never assigned", "r:00000000-0000-4000-0000-011c895902c5(jetbrains.mps.baseLanguage.typesystem)", "7581428506283755703", null, errorTarget);
        }
      } else if (!(isRead)) {
        {
          final MessageTarget errorTarget = new NodeMessageTarget();
          IErrorReporter _reporter_2309309498 = typeCheckingContext.reportWarning(field, "Field " + SPropertyOperations.getString(field, PROPS.name$MnvL) + " is assigned but never accessed", "r:00000000-0000-4000-0000-011c895902c5(jetbrains.mps.baseLanguage.typesystem)", "7581428506283755712", null, errorTarget);
        }
      }
    }
  }

  @CheckingMethod
  public static void checkPrecedingWriteInstructions(final TypeCheckingContext typeCheckingContext, SNode contextReference, SNode currentBody, SNode field) {

    Program program = new MPSProgramBuilder().buildProgram(currentBody);
    Set<SNode> uninitializedReads = DataFlow.getUninitializedReads(program);
    if (uninitializedReads.contains(contextReference)) {
      final boolean onlyInstanceInitializers = VariableReferenceUtil.isReferredFromThisInvocation(currentBody, contextReference);
      if (isInitializedInPrecedingInitializersOrConstructors(StatementList__BehaviorDescriptor.getFirstStatement_id4GU1DgEHJ2u.invoke(currentBody), field, onlyInstanceInitializers)) {
        return;
      }
      if (SPropertyOperations.getBoolean(field, PROPS.isFinal$gvTP)) {
        {
          final MessageTarget errorTarget = new NodeMessageTarget();
          IErrorReporter _reporter_2309309498 = typeCheckingContext.reportTypeError(contextReference, "Field " + SPropertyOperations.getString(field, PROPS.name$MnvL) + " used before initialized", "r:00000000-0000-4000-0000-011c895902c5(jetbrains.mps.baseLanguage.typesystem)", "8129007564265130269", null, errorTarget);
        }
      } else {
        {
          final MessageTarget errorTarget = new NodeMessageTarget();
          IErrorReporter _reporter_2309309498 = typeCheckingContext.reportWarning(contextReference, "Field " + SPropertyOperations.getString(field, PROPS.name$MnvL) + " used before initialized", "r:00000000-0000-4000-0000-011c895902c5(jetbrains.mps.baseLanguage.typesystem)", "6516159782899570422", null, errorTarget);
        }
      }
    }
  }
  private static boolean isReferredFromThisInvocation(SNode currentBody, SNode contextReference) {
    return SNodeOperations.isInstanceOf(StatementList__BehaviorDescriptor.getFirstStatement_id4GU1DgEHJ2u.invoke(currentBody), CONCEPTS.ThisConstructorInvocation$q$) && ListSequence.fromList(SNodeOperations.getNodeAncestors(contextReference, null, false)).contains(StatementList__BehaviorDescriptor.getFirstStatement_id4GU1DgEHJ2u.invoke(currentBody));
  }

  private static boolean isInitializedInPrecedingInitializersOrConstructors(SNode firstStatement, SNode field) {
    return isInitializedInPrecedingInitializersOrConstructors(firstStatement, field, false);
  }
  private static boolean isInitializedInPrecedingInitializersOrConstructors(SNode firstStatement, final SNode field, boolean instanceInitializersOnly) {

    if (SNodeOperations.isInstanceOf(firstStatement, CONCEPTS.ThisConstructorInvocation$q$) && !(instanceInitializersOnly)) {
      SNode thisConstructor = SLinkOperations.getTarget(SNodeOperations.cast(firstStatement, CONCEPTS.ThisConstructorInvocation$q$), LINKS.baseMethodDeclaration$pyYw);
      if (hasCycleInThisInvocations(thisConstructor)) {
        return false;
      }
      return containsWrite(SLinkOperations.getTarget(thisConstructor, LINKS.body$5xQk), field) || isInitializedInPrecedingInitializersOrConstructors(ListSequence.fromList(SLinkOperations.getChildren(SLinkOperations.getTarget(thisConstructor, LINKS.body$5xQk), LINKS.statement$53DE)).first(), field);
    } else {
      SNode myInitializer = SNodeOperations.getNodeAncestor(firstStatement, CONCEPTS.InstanceInitializer$4x, false, false);
      final int myIndexInClass = ((myInitializer != null) ? SNodeOperations.getIndexInParent(myInitializer) : Integer.MAX_VALUE);

      Iterable<SNode> initializers = Sequence.fromIterable(Classifier__BehaviorDescriptor.members_id1hodSy8nQmC.invoke(SNodeOperations.cast(SNodeOperations.getParent(field), CONCEPTS.Classifier$Ix))).where((it) -> SNodeOperations.isInstanceOf(it, CONCEPTS.InstanceInitializer$4x) && SNodeOperations.getIndexInParent(it) < myIndexInClass).select((it) -> SNodeOperations.as(it, CONCEPTS.InstanceInitializer$4x));

      return Sequence.fromIterable(initializers).any((initializer) -> containsWrite(SLinkOperations.getTarget(initializer, LINKS.statementList$fANs), field));
    }
  }

  private static boolean hasCycleInThisInvocations(SNode thisConstructor) {
    List<SNode> constructors = ListSequence.fromList(new ArrayList<SNode>());
    ListSequence.fromList(constructors).addElement(thisConstructor);
    SNode calledThis = ConstructorDeclaration__BehaviorDescriptor.getThisConstructorInvocation_id5e6QuLS30e$.invoke(thisConstructor);
    while ((calledThis != null)) {
      if (ListSequence.fromList(constructors).contains(calledThis)) {
        return true;
      }
      ListSequence.fromList(constructors).addElement(calledThis);
      calledThis = ConstructorDeclaration__BehaviorDescriptor.getThisConstructorInvocation_id5e6QuLS30e$.invoke(calledThis);
    }
    return false;
  }

  public static boolean containsWrite(SNode statements, SNode field) {
    Program body = new MPSProgramBuilder().buildProgram(statements);
    List<Instruction> instructions = body.getInstructions();
    Iterator<Instruction> iterator = instructions.iterator();
    while (iterator.hasNext()) {
      Instruction next = iterator.next();
      if (next instanceof WriteInstruction && Objects.equals(((WriteInstruction) next).getVariable(), field)) {
        return true;
      }
    }
    return false;
  }

  private static final class LINKS {
    /*package*/ static final SReferenceLink variableDeclaration$N1XG = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c77f1e98L, 0xf8cc6bf960L, "variableDeclaration");
    /*package*/ static final SContainmentLink lValue$splI = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11b0d00332cL, 0xf8c77f1e97L, "lValue");
    /*package*/ static final SContainmentLink initializer$2twD = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c37a7f6eL, 0xf8c37f506eL, "initializer");
    /*package*/ static final SReferenceLink baseMethodDeclaration$pyYw = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11857355952L, 0xf8c78301adL, "baseMethodDeclaration");
    /*package*/ static final SContainmentLink body$5xQk = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b1fcL, 0xf8cc56b1ffL, "body");
    /*package*/ static final SContainmentLink statement$53DE = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b200L, 0xf8cc6bf961L, "statement");
    /*package*/ static final SContainmentLink statementList$fANs = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x118f0b909f7L, 0x118f0b95a3bL, "statementList");
  }

  private static final class CONCEPTS {
    /*package*/ static final SInterfaceConcept IStatementListContainer$xz = MetaAdapterFactory.getInterfaceConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11750ef8265L, "jetbrains.mps.baseLanguage.structure.IStatementListContainer");
    /*package*/ static final SConcept BaseAssignmentExpression$PA = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x11b0d00332cL, "jetbrains.mps.baseLanguage.structure.BaseAssignmentExpression");
    /*package*/ static final SConcept ThisConstructorInvocation$q$ = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x1127b878882L, "jetbrains.mps.baseLanguage.structure.ThisConstructorInvocation");
    /*package*/ static final SConcept InstanceInitializer$4x = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x118f0b909f7L, "jetbrains.mps.baseLanguage.structure.InstanceInitializer");
    /*package*/ static final SConcept Classifier$Ix = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101d9d3ca30L, "jetbrains.mps.baseLanguage.structure.Classifier");
  }

  private static final class PROPS {
    /*package*/ static final SProperty name$MnvL = MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, 0x110396ec041L, "name");
    /*package*/ static final SProperty isFinal$gvTP = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c37a7f6eL, 0x111f9e9f00cL, "isFinal");
  }
}
