package jetbrains.mps.ide.modelchecker.actions;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.ide.modelchecker.platform.actions.SpecificChecker;
import jetbrains.mps.errors.item.IssueKindReportItem;
import java.util.List;
import jetbrains.mps.errors.item.NodeReportItem;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.ArrayList;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SModuleOperations;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SModelOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SEnumOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.errors.item.NodeReportItemBase;
import jetbrains.mps.smodel.behaviour.BHReflection;
import jetbrains.mps.core.aspects.behaviour.SMethodIdV2;
import org.jetbrains.mps.openapi.model.SReference;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.IAttributeDescriptor;
import jetbrains.mps.errors.item.UnresolvedReferenceReportItem;
import org.jetbrains.mps.openapi.language.SConcept;
import jetbrains.mps.project.validation.ConceptMissingError;
import org.jetbrains.mps.openapi.language.SInterfaceConcept;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SConceptOperations;
import jetbrains.mps.smodel.runtime.ConceptDescriptor;
import jetbrains.mps.smodel.adapter.structure.concept.SAbstractConceptAdapter;
import jetbrains.mps.smodel.runtime.StaticScope;
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.SProperty;

@GeneratedClass(node = "r:e2c8c94a-404b-4b97-a3a4-c76946bd1913(jetbrains.mps.ide.modelchecker.actions)/1833497744617099302", model = "r:e2c8c94a-404b-4b97-a3a4-c76946bd1913(jetbrains.mps.ide.modelchecker.actions)")
public class ReferenceableConceptsChecker extends SpecificChecker {
  public ReferenceableConceptsChecker() {
  }
  public static final IssueKindReportItem.CheckerCategory ILLEGAL_REFERENCES = new IssueKindReportItem.CheckerCategory(IssueKindReportItem.KindLevel.MANUAL, "illegal references");
  @Override
  public IssueKindReportItem.CheckerCategory getCategory() {
    return ILLEGAL_REFERENCES;
  }
  @Override
  public List<NodeReportItem> checkModel(final SModel model, final ProgressMonitor monitor) {
    final List<NodeReportItem> results = ListSequence.fromList(new ArrayList<NodeReportItem>());
    if (model == null || model.getModule() == null) {
      return results;
    }

    if (monitor.isCanceled()) {
      return results;
    }

    if (SModuleOperations.isAspect(model, "structure")) {
      IssueKindReportItem.ItemKind itemKind1 = getCategory().deriveItemKind("reference to a non-referenceable concept");
      IssueKindReportItem.ItemKind itemKind2 = getCategory().deriveItemKind("non-referenceable named concept");

      for (SNode concept : ListSequence.fromList(SModelOperations.roots(model, CONCEPTS.AbstractConceptDeclaration$KA))) {
        for (SNode ref : ListSequence.fromList(SLinkOperations.getChildren(concept, LINKS.linkDeclaration$YU1f)).where((it) -> SEnumOperations.isMember(SPropertyOperations.getEnum(it, PROPS.metaClass$PeKc), 0xfc6f4e95b8L))) {
          SNode target = SLinkOperations.getTarget(ref, LINKS.target$m40F);
          if (SNodeOperations.isInstanceOf(target, CONCEPTS.ConceptDeclaration$gH)) {
            SNode decl = SNodeOperations.cast(target, CONCEPTS.ConceptDeclaration$gH);
            if (SEnumOperations.isMember(SPropertyOperations.getEnum(decl, PROPS.staticScope$PjQk), 0x4b014033eedc8becL)) {
              String m = String.format("Can not reference concept %s (scope: none)", SPropertyOperations.getString(target, PROPS.name$MnvL));
              ListSequence.fromList(results).addElement(NodeReportItemBase.error(m, SNodeOperations.getPointer(ref), itemKind1));
            }
          }
        }
        if (SNodeOperations.isInstanceOf(concept, CONCEPTS.ConceptDeclaration$gH) && SEnumOperations.isMember(SPropertyOperations.getEnum(SNodeOperations.cast(concept, CONCEPTS.ConceptDeclaration$gH), PROPS.staticScope$PjQk), 0x4b014033eedc8becL)) {
          if (((boolean) (Boolean) BHReflection.invoke0(concept, CONCEPTS.AbstractConceptDeclaration$KA, SMethodIdV2.create("isSubconceptOf", 5672696027940190895L, 0x44a456bea0df1cf0L), CONCEPTS.INamedConcept$Kd))) {
            ListSequence.fromList(results).addElement(NodeReportItemBase.warn("INamedConcept inheritors are usually reference targets, change scope from none", SNodeOperations.getPointer(concept), itemKind2));
          }
        }
      }
    }

    for (SNode node : ListSequence.fromList(SModelOperations.nodes(model, null))) {
      if (monitor.isCanceled()) {
        break;
      }
      // Check for unresolved references
      // FIXME How does this work together with UnresolvedReferencesChecker? Both are specific checkers.
      //      Seems that this one is in use from FindReferencesToNonReferenceable action only
      for (SReference ref : ListSequence.fromList(SNodeOperations.getReferences(node))) {
        if ((new IAttributeDescriptor.LinkAttribute(CONCEPTS.ReferenceMacro$30, ref.getLink()).get(node) != null)) {
          continue;
        }
        SNode target = jetbrains.mps.util.SNodeOperations.getTargetNodeSilently(ref);
        if (target == null) {
          ListSequence.fromList(results).addElement(new UnresolvedReferenceReportItem(ref, null));
          continue;
        }
        checkNode(results, target, node, false, target);
        SNode curr = target;
        while (!(SNodeOperations.isAttribute(curr))) {
          curr = SNodeOperations.getParent(curr);
          if (curr == null) {
            break;
          }
          checkNode(results, curr, node, true, target);
        }
      }
    }
    return results;
  }

  private void checkNode(List<NodeReportItem> results, SNode node, SNode refNode, boolean isAncestor, SNode anchor) {
    SConcept cncpt = node.getConcept();
    if (!(cncpt.isValid())) {
      ListSequence.fromList(results).addElement(new ConceptMissingError(node, cncpt));
      return;
    }
    if (cncpt instanceof SInterfaceConcept) {
      ListSequence.fromList(results).addElement(NodeReportItemBase.error("Interface instance found! " + node.toString(), SNodeOperations.getPointer(node), IssueKindReportItem.STRUCTURE.deriveItemKind("interface instance")));
      return;
    }
    if (cncpt.isAbstract()) {
      ListSequence.fromList(results).addElement(NodeReportItemBase.error("Abstract concept instance found! " + node.toString(), SNodeOperations.getPointer(node), IssueKindReportItem.STRUCTURE.deriveItemKind("abstract concept instance")));
      return;
    }
    final IssueKindReportItem.ItemKind itemKind = getCategory().deriveItemKind("reference to a non-referenceable node");
    if (isAncestor) {
      if (SConceptOperations.isSubConceptOf(SNodeOperations.asSConcept(cncpt), CONCEPTS.ScopeFacade$Mm) && !(ListSequence.fromList(SNodeOperations.getNodeAncestors(refNode, null, true)).contains(node))) {
        ListSequence.fromList(results).addElement(NodeReportItemBase.error("Reference from outside to a node under ScopeFacade: facade=" + cncpt.getName(), SNodeOperations.getPointer(anchor), itemKind));
      }
    } else {
      ConceptDescriptor cd = ((SAbstractConceptAdapter) cncpt).getConceptDescriptor();
      if (cd.getStaticScope() == StaticScope.NONE) {
        ListSequence.fromList(results).addElement(NodeReportItemBase.error("Reference to a non-referenceable node found: " + cncpt.getName(), SNodeOperations.getPointer(anchor), itemKind));
      } else if (cd.getStaticScope() == StaticScope.ROOT && !(SNodeOperations.getContainingRoot(node) == SNodeOperations.getContainingRoot(refNode))) {
        ListSequence.fromList(results).addElement(NodeReportItemBase.error("Cross-root reference to a locally referenceable node found: " + cncpt.getName(), SNodeOperations.getPointer(anchor), itemKind));
      }
    }
  }

  private static final class LINKS {
    /*package*/ static final SReferenceLink target$m40F = MetaAdapterFactory.getReferenceLink(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979bd086aL, 0xf98055fef0L, "target");
    /*package*/ static final SContainmentLink linkDeclaration$YU1f = MetaAdapterFactory.getContainmentLink(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0x1103553c5ffL, 0xf979c3ba6bL, "linkDeclaration");
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept ConceptDeclaration$gH = MetaAdapterFactory.getConcept(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979ba0450L, "jetbrains.mps.lang.structure.structure.ConceptDeclaration");
    /*package*/ static final SConcept AbstractConceptDeclaration$KA = MetaAdapterFactory.getConcept(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0x1103553c5ffL, "jetbrains.mps.lang.structure.structure.AbstractConceptDeclaration");
    /*package*/ static final SInterfaceConcept INamedConcept$Kd = MetaAdapterFactory.getInterfaceConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, "jetbrains.mps.lang.core.structure.INamedConcept");
    /*package*/ static final SConcept ReferenceMacro$30 = MetaAdapterFactory.getConcept(0xb401a68083254110L, 0x8fd384331ff25befL, 0xfd7f44d616L, "jetbrains.mps.lang.generator.structure.ReferenceMacro");
    /*package*/ static final SInterfaceConcept ScopeFacade$Mm = MetaAdapterFactory.getInterfaceConcept(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x2ea65c0b397bd5beL, "jetbrains.mps.lang.core.structure.ScopeFacade");
  }

  private static final class PROPS {
    /*package*/ static final SProperty staticScope$PjQk = MetaAdapterFactory.getProperty(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979ba0450L, 0x4b014033eedc8a48L, "staticScope");
    /*package*/ static final SProperty name$MnvL = MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, 0x110396ec041L, "name");
    /*package*/ static final SProperty metaClass$PeKc = MetaAdapterFactory.getProperty(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979bd086aL, 0xf980556927L, "metaClass");
  }
}
