package jetbrains.mps.ide.depanalyzer;

/*Generated by MPS */

import org.jetbrains.annotations.Nullable;
import jetbrains.mps.ide.findusages.model.SearchResults;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import java.util.List;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.Set;
import java.util.HashSet;
import java.util.ArrayList;
import jetbrains.mps.ide.findusages.model.SearchResult;
import org.jetbrains.mps.openapi.model.SReference;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import java.util.Collections;
import org.jetbrains.mps.openapi.language.SConcept;

public final class ReferencesFinder {
  private final DependencyViewerScope myInitialScope;

  public ReferencesFinder(@Nullable DependencyViewerScope initialScope) {
    // null value is tolerated unless #findRefsFromScopeToOuter or #getRefsBetweenScopes (i.e. non-meta methods) are invoked
    myInitialScope = initialScope;
  }

  /**
   * reports reference targets of input nodes; targets reported are external to supplied scope (all external if scope is null); unique values
   * 
   * 
   * @param selection subscope of the initial one
   */
  public SearchResults<SNode> findRefsFromScopeToOuter(DependencyViewerScope selection, @Nullable DependencyViewerScope external, ProgressMonitor monitor) {
    if (myInitialScope == null) {
      throw new IllegalStateException();
    }
    List<SNode> nodes = Sequence.fromIterable(selection.getNodes()).toList();
    monitor.start("computing references' targets", ListSequence.fromList(nodes).count());
    Set<SNode> targets = new HashSet<SNode>();
    ArrayList<SearchResult<SNode>> results = new ArrayList<SearchResult<SNode>>();
    for (SNode node : ListSequence.fromList(nodes)) {
      for (SReference ref : ListSequence.fromList(SNodeOperations.getReferences(node))) {
        SNode target = jetbrains.mps.util.SNodeOperations.getTargetNodeSilently(ref);
        if (target == null) {
          continue;
        }
        if (myInitialScope.contains(target)) {
          continue;
        }
        if (external != null && !(external.contains(target))) {
          continue;
        }

        if (targets.add(target)) {
          results.add(new SearchResult<SNode>(target, "target"));
        }
      }
      if (monitor.isCanceled()) {
        break;
      }
      monitor.advance(1);
    }
    return new SearchResults<SNode>(Collections.emptyList(), results);
  }

  /**
   * Tells 'meta' dependencies of input nodes, i.e. their concepts, unique.
   * FIXME at the moment, UsagesView is not capable to show anything but SNode, hence SNode, not SConcept result
   *       Need to fix DataTree.createPath along with PathProvider to handle SConcept, just too much work for now
   */
  public SearchResults<SNode> getUsedConcepts(List<SNode> nodes, ProgressMonitor monitor) {
    monitor.start("computing used languages", ListSequence.fromList(nodes).count());
    ArrayList<SearchResult<SNode>> results = new ArrayList<SearchResult<SNode>>();
    Set<SConcept> concepts = new HashSet<SConcept>();
    for (SNode node : nodes) {
      SConcept concept = node.getConcept();
      if (concepts.add(concept)) {
        results.add(new SearchResult<SNode>(concept.getDeclarationNode(), "concept"));
      }
      if (monitor.isCanceled()) {
        break;
      }
      monitor.advance(1);
    }
    return new SearchResults<SNode>(Collections.emptyList(), results);
  }

  /**
   * Subset of input nodes that have their reference targets in supplied target scope 
   * XXX similar to findRefsFromScopeToOuter, which reports reference targets, while this one reports sources
   */
  public SearchResults<SNode> getRefsBetweenScopes(DependencyViewerScope selection, DependencyViewerScope to, ProgressMonitor monitor) {
    if (myInitialScope == null) {
      throw new IllegalStateException();
    }
    List<SNode> nodes = Sequence.fromIterable(selection.getNodes()).toList();
    monitor.start("filtering references", ListSequence.fromList(nodes).count());
    ArrayList<SearchResult<SNode>> results = new ArrayList<SearchResult<SNode>>();
    for (SNode node : ListSequence.fromList(nodes)) {
      for (SReference ref : ListSequence.fromList(SNodeOperations.getReferences(node))) {
        SNode targetNode = jetbrains.mps.util.SNodeOperations.getTargetNodeSilently(ref);
        if (targetNode == null) {
          continue;
        }
        if (myInitialScope.contains(targetNode)) {
          continue;
        }
        if (!(to.contains(targetNode))) {
          continue;
        }
        // XXX same node might be reported several times, is it what we need?
        results.add(new SearchResult<SNode>(node, "references"));
        break;
      }
      if (monitor.isCanceled()) {
        break;
      }
      monitor.advance(1);
    }
    return new SearchResults<SNode>(Collections.emptyList(), results);
  }

  /**
   * Gives subset of input nodes that match concepts from target scope
   */
  public SearchResults<SNode> getLanguageUsagesSearchResults(List<SNode> nodes, DependencyViewerScope targetScope, ProgressMonitor monitor) {
    monitor.start("filtering nodes", ListSequence.fromList(nodes).count());
    ArrayList<SearchResult<SNode>> results = new ArrayList<SearchResult<SNode>>();
    for (SNode node : ListSequence.fromList(nodes)) {
      SConcept concept = node.getConcept();
      // todo replace targetScope.contains with SConcept check
      if (concept.isValid() && targetScope.contains(concept.getDeclarationNode())) {
        results.add(new SearchResult(node, "language"));
      }
      if (monitor.isCanceled()) {
        break;
      }
      monitor.advance(1);
    }
    return new SearchResults<SNode>(Collections.emptyList(), results);
  }
}
