package org.checkerframework.dataflow.cfg.node;

import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.javacutil.TreeUtils;
import org.plumelib.util.StringsPlume;

/**
 * A node for method invocation.
 *
 * <pre>
 *   <em>target(arg1, arg2, ...)</em>
 * </pre>
 *
 * CFGs may contain {@link MethodInvocationNode}s that correspond to no AST {@link Tree}, in which
 * case, the tree field will be null.
 */
public class MethodInvocationNode extends Node {

  /** The tree for the method invocation. */
  protected final @Nullable MethodInvocationTree tree;

  /**
   * The MethodAccessNode for the method being invoked. Includes the receiver if any. For a static
   * method, the receiver may be a class name.
   */
  protected final MethodAccessNode target;

  /** The arguments of the method invocation. */
  protected final List<Node> arguments;

  /** The tree path to the method invocation. */
  protected final TreePath treePath;

  /**
   * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an
   * enhanced for loop, then the {@code iterExpression} field is the expression in the for loop,
   * e.g., {@code iter} in {@code for(Object o: iter}.
   *
   * <p>Is set by {@link #setIterableExpression}.
   */
  protected @Nullable ExpressionTree iterableExpression;

  /**
   * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an
   * enhanced for loop, then the {@code enhancedForLoop} field is the {@code EnhancedForLoopTree}
   * AST node.
   *
   * <p>Is set by {@link #setEnhancedForLoop}.
   */
  protected @Nullable EnhancedForLoopTree enhancedForLoop;

  /**
   * Create a MethodInvocationNode.
   *
   * @param tree for the method invocation
   * @param target the MethodAccessNode for the method being invoked
   * @param arguments arguments of the method invocation
   * @param treePath path to the method invocation
   */
  public MethodInvocationNode(
      @Nullable MethodInvocationTree tree,
      MethodAccessNode target,
      List<Node> arguments,
      TreePath treePath) {
    super(tree != null ? TreeUtils.typeOf(tree) : target.getMethod().getReturnType());
    this.tree = tree;
    this.target = target;
    this.arguments = arguments;
    this.treePath = treePath;
  }

  public MethodInvocationNode(MethodAccessNode target, List<Node> arguments, TreePath treePath) {
    this(null, target, arguments, treePath);
  }

  public MethodAccessNode getTarget() {
    return target;
  }

  public List<Node> getArguments() {
    return arguments;
  }

  public Node getArgument(int i) {
    return arguments.get(i);
  }

  public TreePath getTreePath() {
    return treePath;
  }

  /**
   * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an
   * enhanced for loop, then return the expression in the for loop, e.g., {@code iter} in {@code
   * for(Object o: iter}. Otherwise, return null.
   *
   * @return the iter expression, or null if this is not a {@link Iterator#next()} from an enhanced
   *     for loop
   */
  public @Nullable ExpressionTree getIterableExpression() {
    return iterableExpression;
  }

  /**
   * If this MethodInvocationNode is a node for an {@link Iterator#next()} desugared from an
   * enhanced for loop, then return the corresponding {@code EnhancedForLoopTree} AST node.
   * Otherwise, return null.
   *
   * @return the {@code EnhancedForLoopTree}, or null if this is not a {@link Iterator#next()} from
   *     an enhanced for loop
   */
  public @Nullable EnhancedForLoopTree getEnhancedForLoop() {
    return enhancedForLoop;
  }

  /**
   * Set the iterable expression from a for loop.
   *
   * @param iterableExpression iterable expression
   * @see #getIterableExpression()
   */
  public void setIterableExpression(@Nullable ExpressionTree iterableExpression) {
    this.iterableExpression = iterableExpression;
  }

  /**
   * Set the enhanced for loop for which {@code this} is the desugared loop update.
   *
   * @param enhancedForLoop the {@code EnhancedForLoopTree}
   * @see #getEnhancedForLoop()
   */
  public void setEnhancedForLoop(@Nullable EnhancedForLoopTree enhancedForLoop) {
    this.enhancedForLoop = enhancedForLoop;
  }

  @Override
  public @Nullable MethodInvocationTree getTree() {
    return tree;
  }

  @Override
  public <R, P> R accept(NodeVisitor<R, P> visitor, P p) {
    return visitor.visitMethodInvocation(this, p);
  }

  @Override
  public String toString() {
    return target + "(" + StringsPlume.join(", ", arguments) + ")";
  }

  @Override
  public boolean equals(@Nullable Object obj) {
    if (!(obj instanceof MethodInvocationNode)) {
      return false;
    }
    MethodInvocationNode other = (MethodInvocationNode) obj;

    return getTarget().equals(other.getTarget()) && getArguments().equals(other.getArguments());
  }

  @Override
  public int hashCode() {
    return Objects.hash(target, arguments);
  }

  @Override
  @SideEffectFree
  public Collection<Node> getOperands() {
    List<Node> list = new ArrayList<>(1 + arguments.size());
    list.add(target);
    list.addAll(arguments);
    return list;
  }
}
