package jetbrains.mps.java.platform.index;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.baseLanguage.search.ClassifierSuccessors;
import com.intellij.openapi.Disposable;
import jetbrains.mps.project.MPSProject;
import com.intellij.openapi.startup.StartupActivity;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.project.Project;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.ide.MPSCoreComponents;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.project.DumbService;
import java.util.List;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.ide.vfs.FileSystemBridge;
import java.util.Set;
import com.intellij.openapi.vfs.VirtualFile;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import java.util.HashSet;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.ArrayList;
import org.jetbrains.mps.openapi.model.SModel;
import jetbrains.mps.workbench.ProjectModelFilter;
import jetbrains.mps.util.IterableUtil;
import org.jetbrains.mps.openapi.model.EditableSModel;
import org.jetbrains.mps.openapi.persistence.DataSource;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.extapi.persistence.FileDataSource;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SModelOperations;
import java.util.Queue;
import jetbrains.mps.internal.collections.runtime.QueueSequence;
import java.util.LinkedList;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import java.util.Map;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import com.intellij.util.indexing.FileBasedIndex;
import jetbrains.mps.workbench.index.SNodeEntry;
import org.jetbrains.mps.openapi.module.SRepository;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.openapi.module.Module;
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;

@GeneratedClass(node = "r:2ba45a7a-594f-4a4d-be5c-07edf2b58826(jetbrains.mps.java.platform.index)/4183391441819863852", model = "r:2ba45a7a-594f-4a4d-be5c-07edf2b58826(jetbrains.mps.java.platform.index)")
public final class ClassifierSuccessorsFinder implements ClassifierSuccessors.Finder, Disposable {
  private final MPSProject myProject;

  public static final class Plug implements StartupActivity.Background {

    @Override
    public void runActivity(@NotNull Project project) {
      MPSProject mpsProject = ProjectHelper.fromIdeaProjectOrFail(project);
      ClassifierSuccessors cc = MPSCoreComponents.getInstance().getPlatform().findComponent(ClassifierSuccessors.class);
      if (cc != null) {
        ClassifierSuccessorsFinder ccf = new ClassifierSuccessorsFinder(mpsProject);
        cc.addFinder(mpsProject, ccf);
        Disposer.register(project, ccf);
      }
    }
  }


  private ClassifierSuccessorsFinder(MPSProject mpsProject) {
    // I've got several options here how to approach IDEA's component->service transition and to address indexer need for project:
    //  - follow MPSModelsFastFindSupport$Plug approach with postStartupActivity (or some projectListener) and keep Project
    //    right inside Finder instance. This would require change in ClassifierSuccessor to support multiple Finders; besides,
    //    there was already Project exposed in CS API (isIndexReady(Project)), that's why I decided not to follow 'per-project' 
    //    instance trail.
    //  - Keep single Finder (CS.setFinder()), and use AppLifecycleListener. For our purpose here, just to setFinder(), I see
    //    no apparent benefit in replacing AppComponent with an AppLifecycleListener.
    // [2022] Now it seems that we can no longer afford 'no project' scenario (guessing project from VF).
    myProject = mpsProject;
  }

  @Override
  public boolean isIndexReady() {
    return !(DumbService.getInstance(myProject.getProject()).isDumb());
  }

  @Override
  public List<SNode> getDerivedClassifiers(SNode classifier, org.jetbrains.mps.openapi.module.SearchScope scope) {
    Project ideaProject = myProject.getProject();

    final FileSystemBridge fsBridge = myProject.getFileSystem();
    // FIXME In fact, I don't see any reason to pass VFs into indexing at all. The idea of the index, as I see
    //      it, is not to keep per-file key-value Map, but to answer 'key' queries fast without the need
    //      to pass specific files. We should do it other way round, get all possible values for a key,
    //      and then filter out those matching MPS search scope, rather than constructing IDEA's
    //      scope with VFs and Project we don't have anyway.
    //      However, it's a bigger activity and I can not afford addressing this right now.
    Set<VirtualFile> unModifiedModelFiles = SetSequence.fromSet(new HashSet<VirtualFile>());
    List<SNode> modifiedClasses = ListSequence.fromList(new ArrayList<SNode>());
    List<SNode> modifiedInterfaces = ListSequence.fromList(new ArrayList<SNode>());
    for (SModel md : new ProjectModelFilter(myProject).projectModelsOnly(IterableUtil.asCollection(scope.getModels()))) {
      if (!(md instanceof EditableSModel)) {
        continue;
      }
      EditableSModel emd = (EditableSModel) md;
      DataSource source = emd.getSource();
      IFile modelFile = (source instanceof FileDataSource ? ((FileDataSource) source).getFile() : null);
      if (modelFile == null) {
        continue;
      }
      if (emd.isChanged()) {
        ListSequence.fromList(modifiedClasses).addSequence(ListSequence.fromList(SModelOperations.nodes(md, CONCEPTS.ClassConcept$bK)));
        ListSequence.fromList(modifiedInterfaces).addSequence(ListSequence.fromList(SModelOperations.nodes(md, CONCEPTS.Interface$db)));
      } else {
        VirtualFile vf = fsBridge.asVirtualFile(modelFile);
        if (vf != null) {
          SetSequence.fromSet(unModifiedModelFiles).addElement(vf);
        }
      }
    }
    List<SNode> result = new ArrayList<SNode>();
    Queue<SNode> queue = QueueSequence.fromQueue(new LinkedList<SNode>());
    QueueSequence.fromQueue(queue).addLastElement(classifier);
    ValueProcessor valueProcessor = new ValueProcessor(result, queue, SNodeOperations.getModel(classifier).getRepository());
    ModifiedSuccessorFinder modifiedSuccessorFinder = new ModifiedSuccessorFinder(modifiedClasses, modifiedInterfaces, result, queue);
    SearchScope unModifiedFilesSearchScope = new SearchScope(ideaProject, unModifiedModelFiles);
    while (!(QueueSequence.fromQueue(queue).isEmpty())) {
      SNode nextClassifier = QueueSequence.fromQueue(queue).removeFirstElement();
      ClassifierSuccessorsIndexer.processValues(nextClassifier, valueProcessor, unModifiedFilesSearchScope);
      modifiedSuccessorFinder.process(nextClassifier);
    }
    return result;
  }

  @Override
  public void dispose() {
    ClassifierSuccessors cc = MPSCoreComponents.getInstance().getPlatform().findComponent(ClassifierSuccessors.class);
    if (cc != null) {
      cc.removeFinder(myProject, this);
    }
  }

  private static class ModifiedSuccessorFinder {
    private List<SNode> myModifiedClasses;
    private List<SNode> myModifiedInterfaces;
    private Queue<SNode> myClassifiersQueue;
    private List<SNode> myResult;
    private Set<SNode> myProcessedNodes = SetSequence.fromSet(new HashSet<SNode>());
    private Map<SNode, List<SNode>> mySuccessorsMap = MapSequence.fromMap(new HashMap<SNode, List<SNode>>());
    private boolean myInterfacesMapped;
    private boolean myClassesMapped;

    /*package*/ ModifiedSuccessorFinder(List<SNode> modifiedClasses, List<SNode> modifiedInterfaces, List<SNode> result, Queue<SNode> classifiersQueue) {
      myModifiedClasses = modifiedClasses;
      myModifiedInterfaces = modifiedInterfaces;
      myClassifiersQueue = classifiersQueue;
      myResult = result;
    }

    public void process(SNode superClassifier) {
      if (SetSequence.fromSet(myProcessedNodes).contains(superClassifier)) {
        return;
      }
      SetSequence.fromSet(myProcessedNodes).addElement(superClassifier);
      if (SNodeOperations.isInstanceOf(superClassifier, CONCEPTS.Interface$db)) {
        mapInterfaces();
        mapClasses();
      } else if (SNodeOperations.isInstanceOf(superClassifier, CONCEPTS.ClassConcept$bK)) {
        mapClasses();
      } else {
        return;
      }
      List<SNode> successors = MapSequence.fromMap(mySuccessorsMap).get(superClassifier);
      if (successors != null) {
        for (SNode successor : ListSequence.fromList(successors)) {
          ListSequence.fromList(myResult).addElement(successor);
          QueueSequence.fromQueue(myClassifiersQueue).addLastElement(successor);
        }
      }
    }

    private void mapClasses() {
      if (myClassesMapped) {
        return;
      }
      myClassesMapped = true;
      for (SNode aClass : ListSequence.fromList(myModifiedClasses)) {
        SNode superClass = SLinkOperations.getTarget(aClass, LINKS.superclass$Mp9$);
        if (superClass != null) {
          safeMap(SLinkOperations.getTarget(superClass, LINKS.classifier$cxMr), aClass);
        }
        if (SNodeOperations.isInstanceOf(aClass, CONCEPTS.AnonymousClass$Bt)) {
          safeMap(SLinkOperations.getTarget(SNodeOperations.cast(aClass, CONCEPTS.AnonymousClass$Bt), LINKS.classifier$q_Y$), aClass);
        }
        for (SNode implementedInterface : ListSequence.fromList(SLinkOperations.getChildren(aClass, LINKS.implementedInterface$rujG))) {
          safeMap(SLinkOperations.getTarget(implementedInterface, LINKS.classifier$cxMr), aClass);
        }
      }
    }

    private void mapInterfaces() {
      if (myInterfacesMapped) {
        return;
      }
      myInterfacesMapped = true;
      for (SNode anInterface : ListSequence.fromList(myModifiedInterfaces)) {
        for (SNode extendedInterface : ListSequence.fromList(SLinkOperations.getChildren(anInterface, LINKS.extendedInterface$PDVO))) {
          safeMap(SLinkOperations.getTarget(extendedInterface, LINKS.classifier$cxMr), anInterface);
        }
      }
    }

    private void safeMap(SNode predecessor, SNode successor) {
      if (predecessor == null) {
        return;
      }
      List<SNode> successors = MapSequence.fromMap(mySuccessorsMap).get(predecessor);
      if (successors == null) {
        successors = new ArrayList<SNode>();
        MapSequence.fromMap(mySuccessorsMap).put(predecessor, successors);
      }
      ListSequence.fromList(successors).addElement(successor);
    }
  }

  private static class ValueProcessor implements FileBasedIndex.ValueProcessor<List<SNodeEntry>> {
    private List<SNode> myResult;
    private Queue<SNode> myQueue;
    private final SRepository myRepo;
    private Set<SNodeEntry> myProcessedNodes = SetSequence.fromSet(new HashSet<SNodeEntry>());

    /*package*/ ValueProcessor(List<SNode> result, Queue<SNode> queue, SRepository repository) {
      myResult = result;
      myQueue = queue;
      myRepo = repository;
    }

    @Override
    public boolean process(VirtualFile file, List<SNodeEntry> successors) {
      for (SNodeEntry sNodeId : successors) {
        if (SetSequence.fromSet(myProcessedNodes).contains(sNodeId)) {
          continue;
        }
        SetSequence.fromSet(myProcessedNodes).addElement(sNodeId);
        SNode node = sNodeId.getNodePointer().resolve(myRepo);
        if (SNodeOperations.isInstanceOf(node, CONCEPTS.Classifier$Ix)) {
          SNode classifier = SNodeOperations.cast(node, CONCEPTS.Classifier$Ix);
          ListSequence.fromList(myResult).addElement(classifier);
          QueueSequence.fromQueue(myQueue).addLastElement(classifier);
        }
      }
      return true;
    }
  }

  private static class SearchScope extends GlobalSearchScope {
    private Set<VirtualFile> myFilesInScope;

    /*package*/ SearchScope(Project ideaProject, Set<VirtualFile> notModifiedModelFiles) {
      super(ideaProject);
      myFilesInScope = notModifiedModelFiles;
    }

    @Override
    public boolean contains(VirtualFile file) {
      return SetSequence.fromSet(myFilesInScope).contains(file);
    }

    @Override
    public int compare(VirtualFile file1, VirtualFile file2) {
      return file1.getPath().compareTo(file2.getPath());
    }

    @Override
    public boolean isSearchInModuleContent(@NotNull Module aModule) {
      return true;
    }

    @Override
    public boolean isSearchInLibraries() {
      return false;
    }
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept ClassConcept$bK = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, "jetbrains.mps.baseLanguage.structure.ClassConcept");
    /*package*/ static final SConcept Interface$db = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101edd46144L, "jetbrains.mps.baseLanguage.structure.Interface");
    /*package*/ static final SConcept AnonymousClass$Bt = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x1107e0cb103L, "jetbrains.mps.baseLanguage.structure.AnonymousClass");
    /*package*/ static final SConcept Classifier$Ix = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101d9d3ca30L, "jetbrains.mps.baseLanguage.structure.Classifier");
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink superclass$Mp9$ = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0x10f6353296dL, "superclass");
    /*package*/ static final SReferenceLink classifier$cxMr = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101de48bf9eL, 0x101de490babL, "classifier");
    /*package*/ static final SReferenceLink classifier$q_Y$ = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x1107e0cb103L, 0x1107e0fd2a0L, "classifier");
    /*package*/ static final SContainmentLink implementedInterface$rujG = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0xff2ac0b419L, "implementedInterface");
    /*package*/ static final SContainmentLink extendedInterface$PDVO = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101edd46144L, 0x101eddadad7L, "extendedInterface");
  }
}
