package som.interpreter.actors;

import java.util.concurrent.ForkJoinPool;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;

import som.VM;
import som.interpreter.actors.EventualMessage.PromiseCallbackMessage;
import som.interpreter.actors.EventualMessage.PromiseMessage;
import som.interpreter.actors.EventualMessage.PromiseSendMessage;


/**
 * WARNING: This node needs to be used in a context that makes sure
 * that access to the promise is properly synchronized!
 */
public abstract class SchedulePromiseHandlerNode extends Node {

  protected static final WrapReferenceNode createWrapper() {
    return WrapReferenceNodeGen.create();
  }

  private final ForkJoinPool actorPool;

  protected SchedulePromiseHandlerNode(final ForkJoinPool actorPool) {
    this.actorPool = actorPool;
  }

  public abstract void execute(SPromise promise, PromiseMessage msg, Actor current);

  @Specialization
  public final void schedule(final SPromise promise,
      final PromiseCallbackMessage msg, final Actor current,
      @Cached("createWrapper()") final WrapReferenceNode wrapper) {
    assert promise.getOwner() != null;

    msg.args[PromiseMessage.PROMISE_VALUE_IDX] = wrapper.execute(
        promise.getValueUnsync(), msg.originalSender, current);
    msg.originalSender.send(msg, actorPool);
  }

  @Specialization
  public final void schedule(final SPromise promise,
      final PromiseSendMessage msg, final Actor current,
      @Cached("createWrapper()") final WrapReferenceNode rcvrWrapper) {
    VM.thisMethodNeedsToBeOptimized("Still needs to get out the extra cases and the wrapping");
    assert promise.getOwner() != null;

    Actor finalTarget = promise.getOwner();

    Object receiver = rcvrWrapper.execute(promise.getValueUnsync(),
        finalTarget, current);
    assert !(receiver instanceof SPromise) : "TODO: handle this case as well?? Is it possible? didn't think about it";

    // TODO: might want to handle that in a specialization
    if (receiver instanceof SFarReference) {
      // now we are about to send a message to a far reference, so, it
      // is better to just redirect the message back to the current actor
      finalTarget = ((SFarReference) receiver).getActor();
      receiver = ((SFarReference) receiver).getValue();
    }

    msg.args[PromiseMessage.PROMISE_RCVR_IDX] = receiver;

    assert !(receiver instanceof SFarReference) : "this should not happen, because we need to redirect messages to the other actor, and normally we just unwrapped this";
    assert !(receiver instanceof SPromise);

    // TODO: break that out into nodes
    for (int i = 1; i < msg.args.length; i++) {
      msg.args[i] = finalTarget.wrapForUse(msg.args[i], msg.originalSender, null);
    }

    msg.target = finalTarget; // for sends to far references, we need to adjust the target
    msg.finalSender = current;

    finalTarget.send(msg, actorPool);
  }
}

@@ -197,19 +197,124 @@
 i.nodes.
-Nod
+ExplodeLoop;%0Aimport com.oracle.truffle.api.nodes.Node;%0Aimport com.oracle.truffle.api.profiles.IntValueProfil
 e;%0A%0Aimpo
@@ -1676,24 +1676,93 @@
  rcvrWrapper
+,%0A      @Cached(%22createWrapper()%22) final WrapReferenceNode argWrapper
 ) %7B%0A    VM.t
@@ -2806,341 +2806,642 @@
     
-// TODO: break that out into nodes%0A    for (int i = 1; i %3C msg.args.length; i++) %7B%0A      msg.args%5Bi%5D = finalTarget.wrapForUse(msg.args%5Bi%5D, msg.originalSender, null);%0A    %7D%0A%0A    msg.target = finalTarget; // for sends to far references, we need to adjust the target%0A    msg.finalSender = current;%0A%0A    finalTarget.send(msg, actorPool);
+wrapArguments(msg, finalTarget, argWrapper);%0A%0A    msg.target = finalTarget; // for sends to far references, we need to adjust the target%0A    msg.finalSender = current;%0A%0A    finalTarget.send(msg, actorPool);%0A  %7D%0A%0A  private final IntValueProfile numArgs = IntValueProfile.createIdentityProfile();%0A%0A  @ExplodeLoop%0A  private void wrapArguments(final PromiseSendMessage msg, final Actor finalTarget,%0A      final WrapReferenceNode argWrapper) %7B%0A    // TODO: break that out into nodes%0A    for (int i = 1; i %3C numArgs.profile(msg.args.length); i++) %7B%0A      msg.args%5Bi%5D = argWrapper.execute(msg.args%5Bi%5D, finalTarget, msg.originalSender);%0A    %7D
 %0A  %7D

Optimize scheduling promises