import java.math.BigInteger;
import edu.mit.ai.psg.traveler.*;
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import edu.mit.ai.psg.jexa.*;
import edu.mit.ai.psg.ui.outliner.*;
import edu.mit.ai.psg.ui.patches.CloseableFrame;
import javax.swing.JButton;

/** Future Factorial computes factorial of a positive BigInteger <i>n</i>.
    It computes factorial as a product of the range of integers from 1 to
    <i>n</i>.  It illustrates concurrent recursive divide and conquer on
    the range of integers to be multiplied.  A non-minimal range is 
    is divided in half, and futures are used to compute the 
    rangeproducts for each subrange concurrently. 

    This version has calls to Trace added to trace invocations and returns
    from <code>rangeproduct(..)</code> and <code>factorial(..)</code>.

    @author CarlManning, caroma@ai.mit.edu<br>
    Copyright (c) 1999 Massachusetts Institute of Technology
**/
class FutureFactorialTraced { 
  public static BigInteger factorial(final BigInteger n) {
    CallRecord callRecord =
      Trace.invokingStatic(factorialMethod, new Object[]{n});

    if (1 == n.signum()) // positive
      return (BigInteger) Trace.returning(callRecord, rangeProduct(ONE, n));
    else
      throw ((IllegalArgumentException)
	     Trace.throwing(callRecord,
			    new IllegalArgumentException("n < 1")));
  }
  static final BigInteger ONE = BigInteger.ONE;
  static final BigInteger TWO = new BigInteger("2");
  public static BigInteger rangeProduct(final BigInteger lo,
					final BigInteger hi) {
    CallRecord callRecord =
      Trace.invokingStatic(rangeProductMethod, new Object[]{lo, hi});

    if (lo.equals(hi)) return (BigInteger) Trace.returning(callRecord, lo);
    else if ((lo.add(BigInteger.ONE)).equals(hi))
      return (BigInteger) Trace.returning(callRecord, lo.multiply(hi));
    else {
      final BigInteger mid = (lo.add(hi)).divide(TWO);
      Trace.doing("mid = "+mid);
      // future creation cannot be nested in .value() expression, else thread
      // would wait for value before creating second future -- non concurrent.
      FutureTraced loProd = (new FutureTraced() { { start(); }
                         public Object compute() {
			   return rangeProduct(lo, mid); }});
      FutureTraced hiProd = (new FutureTraced() { { start(); }
	                 public Object compute() {
			   return rangeProduct(mid.add(ONE), hi); }});
      return
	((BigInteger)
	 Trace.returning
	 (callRecord,
	  ((BigInteger)loProd.value()).multiply((BigInteger)hiProd.value())));
    }
  }
  static Method factorialMethod = 
    Trace.getMethod(FutureFactorialTraced.class,
		    "factorial", new Class[]{BigInteger.class});
  static Method rangeProductMethod = 
    Trace.getMethod(FutureFactorialTraced.class,
		    "rangeProduct", new Class[]{BigInteger.class,
						BigInteger.class});
}


/** A future can be used to return a placeholder for a value while
    concurrently computing the value.  It is particularly useful if
    the value won't be used immediately.  <p>
    
    Future must be extended with a <code>compute()</code> method.
    This can be done using an anonymous class at the point the the
    future is created, e.g., <pre>
    .  Future hiProd = (new Future() { 
    .	                 public Object compute() {
    .			   return rangeProduct(mid.add(ONE), hi); }
    .			 { start(); }})</pre>
    Note the <code> { start(); } </code> initializer, which starts
    the future running; it must not be called until the derived
    object is fully initialized, including any free final vars in
    the anonymous class. <p>
    
    The future value can later be accessed by calling the
    <code> value() </code> method; any thread which calls this will
    wait if necessary until the thread computing the value has completed.
    If the computation throws an Error or RuntimeException, then
    <code> value() </code> will also throw the exception. <p>

    Future implements Runnable rather than extending Thread so that when the
    computing thread has be completed it can be garbage collected.  **/
abstract class FutureTraced
  implements Runnable, Cloneable, FutureFactorialApplet.CloneablePublic
  // (added SharedAccountBioApplet.CloneablePublic interface
  //  to make clone method public on public interface for applet)
{
  // --- fields ---
  private State myState = UNINITIALIZED;
  private Object myValue = null;
  // --- constants for state ---
  private static class State {
    private String stateName;
    State(String stateName) { this.stateName = stateName; }
    public String toString() { return stateName; }
  }
  private static final
    State UNINITIALIZED          = new State("Uninitialized");
  private static final
    State INITIALIZED_AND_STARTED= new State("Initialized and started");
  private static final
    State COMPLETED_RETURN       = new State("Completed with return");
  private static final
    State COMPLETED_THROW        = new State("Completed with throw");
  // --- constructor ---
  FutureTraced() {
    if (traceLev != 0)
      synchronized(this) {
	ActivityRecord r = Trace.beganSynchronization(this, futureConstructor);
	Trace.exitting(r);
      }
  }
  /** clone method made public so Futures can be cloned in Applet without
      making each derived class a public class (which would either require 
      that it be in its own file [harder to browse], or be a nested class of a
      public class [name becomes long]).  Anonymous classes cannot be
      cloned in applets without this.  Not needed if security not a problem.
  **/
  public Object clone() throws CloneNotSupportedException {
    return super.clone(); }
  /** Start computing value of future in a new thread. <code>start()</code>
      must not be called until future is initialized; this includes
      initializing free final variables in derived classes' compute
      method.  It may be called at end of initialization of a derived
      class IF that class is anonymous or final (so no classes can
      ever be derived from it).
      @throws IllegalStateException if has already been started.
  **/
  public synchronized void start() {
    CallRecord callRecord = 
      (traceLev == 0 ? null : Trace.received(this, startMethod, null));

    if (myState == UNINITIALIZED) {
      new Thread(this).start();
      myState = INITIALIZED_AND_STARTED;

      if (traceLev != 0) Trace.returningVoid(callRecord);
      return;
    } else {
      throw ((IllegalStateException)
	     Trace.throwing
	     (callRecord, new IllegalStateException("Already started.")));
    }
  }
  /** compute value of future; called by run after future is started **/
  protected abstract Object compute(); 
  // --- Runnable ---
  public synchronized void run() { 
    CallRecord callRecord = 
      (traceLev == 0 ? null : Trace.received(this, runMethod, null));

    while (myState == UNINITIALIZED) 
      try { this.wait(); } catch (InterruptedException e) {}
    
    if (myState == INITIALIZED_AND_STARTED) {
      try { 
	myValue = compute();
	myState = COMPLETED_RETURN;
      } catch(Throwable t) {
	myValue = t;
	myState = COMPLETED_THROW;
      }
      this.notifyAll();
    }

    if (traceLev != 0) Trace.returningVoid(callRecord);
  }
  /** get value of future, waiting for computation to complete if necessary.
      @throws Error if {@link #compute()} completes by throwing an Error.
      @throws RuntimeException if {@link #compute()} completes by throwing
      a runtime exception. **/
  public synchronized Object value() {
    CallRecord valueRecord = // not for this call, but for wait() call
      (traceLev == 0 ? null : Trace.received(this,valueMethod,null));

    while (! (myState == COMPLETED_RETURN || myState == COMPLETED_THROW)) {
      CallRecord waitRecord = // not for this call, but for wait() call
	(traceLev == 0 ? null :
	 Trace.invoking(this,waitMethod,null));
      try {
	this.wait();
	if (traceLev != 0) Trace.returningVoid(waitRecord);
      }
      catch (InterruptedException e) {
	Error error = new Error(e.toString());
	throw (traceLev == 0 ? error :
	       (Error) Trace.throwing(waitRecord, error));
      }
    }
    if (myState == COMPLETED_RETURN)
      return (traceLev == 0 ? myValue :
	      Trace.returning(valueRecord, myValue));
    else { // myState == COMPLETED_THROW
      Throwable thrown = (traceLev == 0? (Throwable) myValue : 
			  Trace.throwing(valueRecord,
					 (Throwable) myValue));
      if (dbgLev> 0) thrown.printStackTrace();
      if (thrown instanceof Error) throw (Error) thrown;
      else throw (RuntimeException) thrown;
    }
  }
  public String toString() {
    return "Future(state="+myState+", value="+myValue+")";}
  static final Constructor futureConstructor =
    Trace.getConstructor(FutureTraced.class, new Class[]{});
  static final Method startMethod =
    Trace.getMethod(FutureTraced.class, "start", null);
  static final Method runMethod =
    Trace.getMethod(FutureTraced.class, "run", null);
  static final Method valueMethod =
    Trace.getMethod(FutureTraced.class, "value", null);
  static final Method waitMethod =
    Trace.getMethod(FutureTraced.class, "wait", null);
  public static int traceLev = 1;
  public static int dbgLev = 1;
}

/** This applet can be run as an application as well **/
public class FutureFactorialApplet extends JexaApplet { 
  /** application startup: invokes initFrameApplet **/
  public static void main(String[] args) {
    TravelerOutliner.ensureInitialized();
    Jexa.dbgLev = 1; // DEBUG
    if (args.length > 0) initialParameter = args[0];
    // run applet as frame
    Frame frame = new FutureFactorialApplet().initFrameApplet();
    // provide clean exit -- exit when original frame is closed.
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) { System.exit(0); }});
  }
  // invoke factorial
  static String initialParameter = "10";
  public static ActivityRecord run(String parameter) {
    ActivityRecord record =
      Trace.beginning(FutureTraced.traceLev == 0 ? 
		      "// Not recording Future activity:" :
		      "// Recording Future activity:");

    BigInteger result =
      FutureFactorialTraced.factorial(new BigInteger(parameter));
    System.out.println(result);

    Trace.exitting(record);
    return record;
  }    
  // Applet methods 
  public String getAppletTitle() { return "Traveler: Future Factorial"; }
  public Object getObjectToExamine() {
    return null;
  }
  public void initPanelInPlaceApplet() {
    TravelerOutliner.ensureInitialized();
    super.initPanelInPlaceApplet();
    Panel headPanel = (Panel) getComponent(0);
    Panel jexaPanel = (Panel) getComponent(1);
    headPanel.add(makeParamPanel(jexaPanel), BorderLayout.SOUTH);
  }    
  public Frame initFrameApplet() {
    TravelerOutliner.ensureInitialized();
    Frame frame = new CloseableFrame(getAppletTitle());
    Panel jexaPanel = new Panel();
    OutlineNode node =
      Outliner.outlineMaker.makeOutlineNode(getObjectToExamine());
    Outliner.outlineMaker.initializePane(jexaPanel, node);
    frame.add(makeParamPanel(jexaPanel), BorderLayout.NORTH);
    frame.add(jexaPanel, BorderLayout.CENTER);
    frame.setBounds(60, 28, 580, 300);
    frame.show();
    return frame;
  }    
  public Panel makeParamPanel(final Container jexaPane) {
    Panel paramPanel = new Panel(new FlowLayout(FlowLayout.CENTER, 0, 0));
    paramPanel.setBackground(SystemColor.control); 
    paramPanel.add(new Label("factorial("));
    final TextField paramField = new TextField(initialParameter, 2);
    paramPanel.add(paramField);
    paramPanel.add(new Label(")"));
    final Checkbox recordFutureCheckBox =
      new Checkbox("Record calls on Futures also (more complex)");
    final Button paramButton = new Button("Go");
    paramButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	String param = paramField.getText().trim();
	if ("".equals(param)) paramField.setText(param = initialParameter);
	FutureTraced.traceLev = recordFutureCheckBox.getState() ? 1 : 0;
	ActivityRecord record = run(param); 
	// display record
	OutlineNode node = Outliner.outlineMaker.makeOutlineNode(record);
	jexaPane.invalidate();
	Outliner.outlineMaker.initializePane(jexaPane, node);
	jexaPane.validate();
      }});
    paramPanel.add(paramButton);
    paramPanel.add(new Label("  ")); // spacer
    paramPanel.add(recordFutureCheckBox);
    return paramPanel;
  }
  // public interface to make Futures cloneable in applet
  public interface CloneablePublic {
    public Object clone() throws CloneNotSupportedException;
  }
}

