package annotator.scanner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;

public class MethodCallScanner extends CommonScanner {

  /**
   * Computes the index of the given method invocation amongst all
   * method invocation trees inside its method, using 0-based indexing.
   *
   * @param origpath the path ending in the given method invocation tree
   * @param tree the method invocation tree to search for
   * @return the index of the given method invocation tree
   */
  public static int indexOfMethodCallTree(TreePath origpath, Tree tree) {
    TreePath path = findCountingContext(origpath);
    if (path == null) {
      return -1;
    }

    MethodCallScanner mcs = new MethodCallScanner(tree);
    mcs.scan(path, null);
    return mcs.index;
  }

  private int index;
  private boolean done;
  private final Tree tree;

  /**
   * Creates an InstanceOfScanner that will scan the source tree for the
   *  given node representing the method invocation to find.
   * @param tree the given method invocation to search for
   */
  private MethodCallScanner(Tree tree) {
    this.index = -1;
    this.done = false;
    this.tree = tree;
  }

  @Override
  public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
    if (!done) {
      index++;
    }
    if (tree == node) {
      done = true;
    }
    return super.visitMethodInvocation(node, p);
  }

  // Map from name of a method to a list of bytecode offsets of all
  // method invocations in that method.
  private static Map<String, List<Integer>> methodNameToMethodCallOffsets =
      new HashMap<String, List<Integer>>();

  /**
   * Adds a lambda expression bytecode offset to the current list of
   * offsets for methodName.  This method must be called with
   * monotonically increasing offsets for any one method.
   *
   * @param methodName the name of the method
   * @param offset the offset to add
   */
  public static void addMethodCallToMethod(String methodName, Integer offset) {
    List<Integer> offsetList =
        methodNameToMethodCallOffsets.get(methodName);
    if (offsetList == null) {
      offsetList = new ArrayList<Integer>();
      methodNameToMethodCallOffsets.put(methodName, offsetList);
    }
    offsetList.add(offset);
  }

  /**
   * Returns the index of the given offset within the list of offsets
   * for the given method, using 0-based indexing, or returns a negative
   * number if the offset is not one of the offsets in the context.
   *
   * @param methodName the name of the method
   * @param offset the offset of the lambda expression
   * @return the index of the given offset, or a negative number
   *  if the offset does not exist inside the context
   */
  public static Integer
  getMethodCallIndex(String methodName, Integer offset) {
    List<Integer> offsetList =
        methodNameToMethodCallOffsets.get(methodName);
    if (offsetList == null) {
      return -1;
    }

    return offsetList.indexOf(offset);
  }
}
