package org.hummingbirdlang.nodes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.frame.VirtualFrame;

import org.hummingbirdlang.objects.Function;
import org.hummingbirdlang.objects.Method;
import org.hummingbirdlang.types.TypeException;
import org.hummingbirdlang.types.realize.InferenceVisitor;

public class HBCallNode extends HBExpressionNode {
  @Child private HBExpressionNode targetNode;
  @Children private final HBExpressionNode[] parameterNodes;

  public HBCallNode(HBExpressionNode targetNode, HBExpressionNode[] parameterNodes) {
    this.targetNode = targetNode;
    this.parameterNodes = parameterNodes;
  }

  public void accept(InferenceVisitor visitor) throws TypeException {
    this.targetNode.accept(visitor);
    for (HBExpressionNode parameterNode : parameterNodes) {
      parameterNode.accept(visitor);
    }
    visitor.visit(this);
  }

  public HBExpressionNode getTargetNode() {
    return this.targetNode;
  }

  public HBExpressionNode[] getParameterNodes() {
    return this.parameterNodes;
  }

  @Override
  public Object executeGeneric(VirtualFrame frame) {
    Object target = this.targetNode.executeGeneric(frame);

    ArrayList<Object> arguments = new ArrayList<>();
    for (HBNode argumentNode : this.parameterNodes) {
      arguments.add(argumentNode.executeGeneric(frame));
    }

    if (target instanceof Method) {
      Method method = (Method)target;
      CallTarget callTarget = method.getCallTarget();
      arguments.add(0, method.getReceiver());
      return callTarget.call(arguments.toArray(new Object[arguments.size()]));

    } else if (target instanceof Function) {
      Function function = (Function)target;
      CallTarget callTarget = function.getCallTarget();
      return callTarget.call(arguments.toArray(new Object[arguments.size()]));

    } else {
      throw new Error("Cannot call to: " + target.toString());
    }
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    result.append("HBCallNode(");
    result.append(this.targetNode.toString());
    result.append(", (");
    List<String> parameters = Arrays.stream(this.parameterNodes).map(p -> p.toString()).collect(Collectors.toList());
    result.append(String.join(", ", parameters));
    result.append(")");
    result.append(")");
    return result.toString();
  }
}

@@ -1283,16 +1283,43 @@
 (frame);
+%0A    CallTarget callTarget;
 %0A%0A    Ar
@@ -1559,35 +1559,24 @@
 rget;%0A      
-CallTarget 
 callTarget =
@@ -1649,87 +1649,8 @@
 ());
-%0A      return callTarget.call(arguments.toArray(new Object%5Barguments.size()%5D));
 %0A%0A  
@@ -1738,35 +1738,24 @@
 rget;%0A      
-CallTarget 
 callTarget =
@@ -1784,87 +1784,8 @@
 t();
-%0A      return callTarget.call(arguments.toArray(new Object%5Barguments.size()%5D));
 %0A%0A  
@@ -1863,16 +1863,94 @@
 );%0A    %7D
+%0A%0A    return callTarget.call(arguments.toArray(new Object%5Barguments.size()%5D));
 %0A  %7D%0A%0A  

Tweak control flow in executing call node