/*
 * Copyright 2005, The University of Texas at Austin. All rights reserved.
 */
package orc.runtime;

import java.rmi.RemoteException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

import orc.env.Env;
import orc.error.TokenException;
import orc.runtime.nodes.Node;
import orc.runtime.regions.Execution;
import orc.runtime.regions.Region;
import orc.runtime.values.Future;
import orc.runtime.values.GroupCell;
import orc.runtime.values.Value;

/**
 * The Orc Engine provides the main loop for executing active tokens.
 * Tokens are always processed in a single thread, but tokens might
 * be activated or resumed from other threads, so some synchronization
 * is necessary. 
 * 
 * @author wcook, dkitchin, quark
 */
public class OrcEngine implements Runnable {

	LinkedList<Token> activeTokens = new LinkedList<Token>();
	LinkedList<Token> queuedReturns = new LinkedList<Token>();
	Set<LogicalClock> clocks = new HashSet<LogicalClock>();
	int round = 1;
	public boolean debugMode = false;
	/**
	 * This flag is set by the Execution region when execution completes
	 * to terminate the engine.
	 */
	protected boolean halt = false;
	/**
	 * Track when the engine is blocked waiting for a site to return.
	 */
	protected boolean isBlocked = false;
	
	public synchronized boolean isBlocked() { return isBlocked; }
	public synchronized boolean isDead() { return halt; }

	/**
	 * Process active nodes, running indefinitely until
	 * signalled to stop by a call to terminate().
	 * Typically you will use one of the other run methods
	 * to queue an active token to process first.
	 */
	public void run() {
		while (true) {
			// FIXME: can we avoid synchronizing this whole block?
			synchronized(this) {
				if (halt) return;
				if (!step()) {
					isBlocked = true;
					try {
						wait();
					} catch (InterruptedException e) {
						// do nothing
					}
					isBlocked = false;
				}
			}
		}
	}
	
	/**
	 * Terminate execution.
	 */
	public synchronized void terminate() {
		halt = true;
		debug("Engine terminated.");
		notify();
	}

	/**
	 * Run Orc given a root node.
	 * Creates an initial environment and then 
	 * executes the main loop.
	 * @param root  node to run
	 */ 
	public void run(Node root) {
		start(root);
		run();
	}

	public void run(Node root, Env env) {
		start(root, env);
		run();
	}
	
	public void start(Node root) {
		start(root, new Env<Future>());
	}
	
	public void start(Node root, Env env) {
		assert(root != null);
		assert(env != null);
		activate(new Token(root, env, null, new GroupCell(), new Execution(this), null, this));
	}
	
	/**
	 * Run one step (process one token, handle one site response, or advance
	 * all logical clocks). Returns true if work was done.
	 */
	protected boolean step() {
		/* If an active token is available, process it. */
		if (!activeTokens.isEmpty()){
			activeTokens.remove().process();
			return true;
		}
		
		/* If a site return is available, make it active.
		 * This marks the beginning of a new round. 
		 */
		if (!queuedReturns.isEmpty()){
			activeTokens.add(queuedReturns.remove());
			round++; reportRound();
			return true;
		}
		
		/* If the engine is quiescent, advance all logical clocks. */
		boolean progress = false;
		
		for (LogicalClock clock : clocks) {
			progress = clock.advance() || progress;
		}
		
		/* If some logical clock actually advanced, return. */
		return progress;
	}
	
	/**
	 * Activate a token by adding it to the queue of active tokens
	 * @param t	the token to be added
	 */
	synchronized public void activate(Token t) {
		activeTokens.addLast(t);
		notify();
	}
	
	/**
	 * Activate a token by adding it to the queue of returning tokens
	 * @param t	the token to be added
	 */
	synchronized public void resume(Token t) {
		queuedReturns.addLast(t);
		notify();
	}
	
	/**
	 * Publish a result. This method is called by the Pub node
	 * when a publication is 'escaping' the bottom of the
	 * execution graph.
	 * 
	 * The default implementation prints the value's string
	 * representation to the console. Change this behavior
	 * by extending OrcEngine and overriding this method.
	 * 
	 * @param v
	 */
	public void pub(Value v) {
		System.out.println(v.toString());
		System.out.flush();
	}
	
	/**
	 * 
	 * A token owned by this engine has encountered an exception.
	 * The token dies, remaining silent and leaving the execution,
	 * and then calls this method so that the engine can report or 
	 * otherwise handle the failure.
	 * 
	 * @param t
	 * @param probme
	 */
	public void tokenError(Token t, TokenException problem) {
		System.out.println();
		System.out.println("Token " + t + " encountered an error. ");
		System.out.println("Problem: " + problem);
		System.out.println("Source location: " + problem.getSourceLocation());
		System.out.println();
	}
	
	
	public void debug(String s) {
		if (debugMode) System.out.println(s);
	}
	
	public void reportRound() {
		if (debugMode){
			debug("---\n" + 
			      "Round:   " + round + "\n" +
			      "Active:  " + activeTokens.size() + "\n" +
			      "Queued:  " + queuedReturns.size() + "\n");
			for(LogicalClock clock : clocks) {
			      debug("L-Clock: " + clock.getTime() + "\n");
			}
			debug("---\n\n");
		}
	}
	
	public synchronized boolean addClock(LogicalClock clock) {
		return clocks.add(clock);
	}
}

@@ -1273,16 +1273,166 @@
  false;%0A
+%09/**%0A%09 * Currently this reference is just needed to keep pending tokens%0A%09 * from being garbage-collected prematurely.%0A%09 */%0A%09private Execution region;%0A
 %09%0A%09publi
@@ -2649,16 +2649,48 @@
  null);%0A
+%09%09region = new Execution(this);%0A
 %09%09activa
@@ -2736,35 +2736,22 @@
 Cell(), 
-new Execution(this)
+region
 , null, 
@@ -4938,24 +4938,169 @@
 ocation());%0A
+%09%09Throwable cause = problem.getCause();%0A%09%09if (debugMode && cause != null) %7B%0A%09%09%09System.out.println(%22Caused by:%22);%0A%09%09%09cause.printStackTrace();%0A%09%09%7D%0A
 %09%09System.out

Fix big oops I created where pending tokens can get garbage collected prematurely because the engine has no reference to the execution root region.