// Code displayed in this color is for tracing only
// This code was found to work perfectly with appletviewer but not in Netscape. Why?
// This is identical to the first solution except for the definition of class Stream

import java.awt.*;

abstract class Stream implements Runnable
{
   DisplayIt trace;        // only for tracing - not normally used
   Object value;
   Thread runner = null, nextThread = null;
   
   public Stream ()  {  }
   
   public void putIt (Object t)
   {
      value = t;
      if (nextThread != null) nextThread.resume();
      if (runner != null) runner.suspend();
   }
   
   public Object next ()
   {
      if (runner != null) runner.resume();
      else  {  runner = new Thread(this);  runner.start();  }

      (nextThread = Thread.currentThread()).suspend();
      
      return value;
   }
}

class IntObject
{
   int number;
   
   public IntObject (int n) { number = n; }
   public int valueOf () { return number; }
}

class Successor extends Stream
{
   int number;
   
   public Successor(int n, DisplayIt t) { number = n; trace = t;  }   
   
   public void run ()
   {
      trace.appendText(this+":","Connected","Number: "+number);
      
      while (true)
      {
         trace.appendText(this+":","Run (1)","Returned "+number);
         putIt(new IntObject(number++));	 
      }
   }
}

class Times extends Stream
{
   int number;
   Stream stream;
   
   public Times (int n, Stream s, DisplayIt t) { stream = s;  number = n;  trace = t; }   

   public void run ()
   {
      trace.appendText(this+":","Connected", number+" * "+stream);
      
      while (true)
      {
         int n = ((IntObject)(stream.next())).valueOf();
         trace.appendText(this+":","Run (1)","Returned "+(number*n));
         putIt(new IntObject(number*n));
      }
   }
}   

class Merge extends Stream
{
   Object val1, val2;
   Stream stream1;
   Stream stream2;
   
   public Merge (Stream s1, Stream s2, DisplayIt t)  { stream1 = s1; stream2 = s2; trace = t; }

   public void traceText (int i, Object v)
   {
      if (v == null) trace.appendText(this+":","Run ("+i+")","Returned null");
      else
      {
         int n = ((IntObject)(v)).valueOf();
         trace.appendText(this+":","Run ("+i+")","Returned "+n);
      }
   }

   public void run ()
   {
      trace.appendText(this+":","Connected: ","to: "+stream1+" and "+stream2);
      
      val1 = stream1.next();
      val2 = stream2.next();
      
      while (true)
      {
	 if (val1 == null && val2 == null)
	 {
	    traceText(1, null);
	    putIt(null);
	 }
	 else
	 if (val1 == null)
	 {
	    traceText(2, val2);
	    putIt(val2);
	    val2 = stream2.next();
	 }
	 else
	 if (val2 == null)
	 {
	    traceText(3,val1);
	    putIt(val1);
	    val1 = stream1.next();
	 }
	 else
         if (((IntObject)(val1)).valueOf() < ((IntObject)(val2)).valueOf())
	 {
	    traceText(4,val1);
	    putIt(val1);
	    val1 = stream1.next();
	 }
	 else
	 {
	    traceText(5,val2);
	    putIt(val2);
	    val2 = stream2.next();
	 }
      }
   }
}

class Hamming extends Stream
{
   int primes[], index;
   
   public Hamming (int p[], int i, DisplayIt t) {  primes = p;  index = i; trace = t; }

   public String makeParam ()
   {
      String param = new String();
      for (int i=index ; i > 0 ; i--) param += primes[i]+", ";
      param += primes[0];
      return param;
   }

   public void run ()
   {
      trace.appendText(this+":","Connected",makeParam());
      
      Stream s = new Times(primes[index], new Hamming(primes, index, trace), trace);
      if (index != 0) s = new Merge(s, new Hamming(primes, index-1, trace), trace);
      
      trace.appendText(this+":","Run (1)","Returned "+primes[index]);
      putIt(new IntObject(primes[index]));
      
      while (true)
      {
         Object token = s.next();
         trace.appendText(this+":","Run (2)","Returned "+((IntObject)(token)).valueOf());
         putIt(token);
      }
   }
}

class DisplayIt
{
   TextArea text;
   FontMetrics fm;
   int c1p = 150;
   int c2p = 100;
   String blanks = new String("                                        ");

   public DisplayIt(TextArea t, FontMetrics f) { text = t; fm = f; }

   synchronized public void appendText (String c1, String c2, String c3)
   {
      int l1 = fm.stringWidth(c1);
      int l2 = fm.stringWidth(c2);
      int b1 = (c1p-l1)/4;
      int b2 = (c2p-l2)/4;
      String str = new String(c1+blanks.substring(0, b1-1)+"\t"+
                              c2+blanks.substring(0, b2-1)+"\t"+
                              c3);
      text.appendText(str+"\n");
   }
}

public class S extends java.applet.Applet
{
   Stream str[];
   Button but[];
   TextField out[];
   Object object;
   TextArea trace;
   DisplayIt stream;
   
   public Panel textBox(String label, int t)
   {
      Panel p = new Panel();
      p.setLayout(new BorderLayout());
      p.add("North", new Label(label));
      p.add("Center", out[t] = new TextField());
      return p;
   }
   
   public Panel buttonBox(String label, int b)
   {
      Panel p = new Panel();
      p.setLayout(new BorderLayout());
      p.add("North", new Label(""));
      p.add("Center", but[b] = new Button(label));
      return p;
   }

   public void init ()
   {
      but = new Button[6];
      str = new Stream[6];
      out = new TextField[6];
      
      setLayout(new BorderLayout(10,10));
      
      Panel q = new Panel();
      q.setLayout(new GridLayout(2,5,10,0));
      q.add(textBox("Successor",1));
      q.add(textBox("Times 3", 2));
      q.add(textBox("Times 5", 3));
      q.add(textBox("Merge (*3, *5)", 4));
      q.add(textBox("Hamming (3,5,11)", 5));
      q.add(buttonBox("Start", 1));
      q.add(buttonBox("Start", 2));
      q.add(buttonBox("Start", 3));
      q.add(buttonBox("Start", 4));
      q.add(buttonBox("Start", 5));
      add("North", q);
      
      Panel y = new Panel();
      y.setLayout(new BorderLayout());
      y.add("Center", textBox("Current Stream:", 0));
      y.add("East", but[0] = new Button("Clear"));
      
      Panel p0 = new Panel();
      p0.setLayout(new BorderLayout());
      p0.add("North", new Label("Trace"));
      p0.add("Center", trace = new TextArea(4,60));

      Panel x = new Panel();
      x.setLayout(new BorderLayout());
      x.add("North", y);
      x.add("Center", p0);
      add("Center", x);
      
      trace.setEditable(false);
      out[0].setEditable(false);

      setBackground(new Color(240, 240, 220));
      trace.setBackground(new Color(255, 255, 240));
      out[0].setBackground(new Color(255, 255, 240));
      but[0].setBackground(new Color(0, 170, 255));
      but[0].setForeground(Color.black);

      for (int i=1 ; i < 6 ; i++)
      {
         out[i].setEditable(false);
         out[i].setBackground(new Color(255, 255, 240));
         but[i].setBackground(Color.green);
      }

      stream = new DisplayIt(trace, getGraphics().getFontMetrics());
      
      str[1] = new Successor(1, stream);
      str[2] = new Times(3, new Successor(1, stream), stream);
      str[3] = new Times(5, new Successor(1, stream), stream);
      str[4]  = new Merge(new Times(3, new Successor(1, stream), stream), new Times(5, new Successor(1, stream), stream), stream);
      int a[] = new int[3]; a[0] = 11; a[1] = 5; a[2] = 3;
      str[5] = new Hamming(a, 2, stream);
   }

   public void shave()
   {
      int l = trace.getText().length();
      if (l > 5000)
      {
         String str = new String(trace.getText().substring(l-4000, l));
	 trace.setText("");
	 trace.appendText(str);
      }
   }

   public void setButtonColor (int b)
   {
      for (int i=1 ; i < 6 ; i++)
         if (i == b && but[i].getBackground().equals(Color.green))
	    but[i].setBackground(Color.red);
	 else
	 if (i != b && but[i].getBackground().equals(Color.red))
	    but[i].setBackground(Color.green);
   }

   public void showTitle (int i)
   {
      switch (i) {
      case 1:
         out[0].setText("new Successor(1)");
	 break;
      case 2:
         out[0].setText("new Times(3, new Successor(1))");
         break;
      case 3:
         out[0].setText("new Times(5, new Successor(1))");
         break;
      case 4:
         out[0].setText("new Merge(new Times(3, new Successor(1)), new Times(5, new Successor(1)))");
         break;
      case 5:
         out[0].setText("new Hamming(a, 2)   int a[] = new int[3]; a[0]=11; a[1]=5; a[2]=3;");
         break;
      }
   }

   public boolean action (Event evt, Object obj)
   {
      if (evt.target.equals(but[0])) trace.setText("");
      for (int i=1 ; i < 6 ; i++)
      {
         if (evt.target.equals(but[i]))
         {
            shave();
            but[i].setLabel("Next");
            setButtonColor(i);
	    showTitle(i);
            object = str[i].next();
            int n = ((IntObject)(object)).valueOf();
	    stream.appendText("","["+n+"]","");
            out[i].setText(out[i].getText()+" "+String.valueOf(n));
         }
      }
      return super.action(evt, obj);
   }
}