import java.applet.Applet;

import java.awt.*;
import java.awt.event.*;

import java.io.*;
import java.awt.image.*;

/** Since this extends Applet it inherits Canvas,
 * and we can use it for paint () experiments.
 * Our goal is fast animation.
 * Calling repaint () to request a paint (g) is not fast enough,
 * but we can do better... */

public class OSBGDemo extends Applet
implements Runnable
{

  private int width, height;

/* A read only image is used as a source for a clean background. */

  private Image    backgroundImage = null;

/* Fill color used if there is no backgroundImage. */

  private Color    backgroundColor = Color.yellow;

/* The offScreenBuffer and a Graphics to draw into it. */

  private Image    offScreenBuffer = null;
  private Graphics osbg            = null;

/* Graphics we get (and keep) to draw to screen anytime.
 * We use this instead of the g provided by paint (g),
 * so we can draw anytime.
 * Yes that is legal. */

  private Graphics screeng         = null;

/** To run as an application instead of applet,
 * construct it and show it in a frame.
 * This must be a static (class not instance) method,
 * because the instance won't exist until we construct it here. */

  public static void main (String []argv) {

      Frame ff = new Frame ("OSBGDemo");
      ff.setBounds (100, 100, 500, 400);

      OSBGDemo cd = new OSBGDemo ();
      ff.add (cd);

      cd.init ();

      ff.show ();
  }

/** Constructor leaves everything to later */

    public OSBGDemo () { }

    public void init () {
        System.out.println ("OSBGDemo init");
	getOSBG ();
	start ();
    }

/** If we haven't done it yet,
 * and the dimensions are established,
 * construct the offScreenBuffer,
 * establish a Graphics context for it,
 * and prefill it with the backgroundImage,
 * or backgroundColor if no backgroundImage.
 * If still no dimensions,
 * leave making the osb (and osbg) for later when we can. */

  protected Graphics getOSBG () {

    if (null == osbg) {
       if (null == offScreenBuffer) {
	  Dimension osize = size ();
	  //Dimension osize = getSize ();
          width = osize.width;
	  height = osize.height;
          if ((0 < width) && (0 < height)) {
            offScreenBuffer = createImage (width, height);
            osbg = offScreenBuffer.getGraphics ();
	    if (null != backgroundImage)
                osbg.drawImage (backgroundImage, 0, 0, this);
	    else {
		osbg.setColor (backgroundColor);
                osbg.fillRect (0, 0, width, height);
	    }

	    setGF (osbg);

            System.out.println ("created OSB "+offScreenBuffer);
        }
      }
      else
	 osbg = offScreenBuffer.getGraphics ();
    }
    return osbg;
  }

/* Cannot assume the osbg has a default font... */

  protected void setGF (Graphics g) { g.setFont (new Font ("Dialog", Font.PLAIN, 18)); }


/* clock thread calls here to drive animation.
 * synch made no difference -- see comments below */

  public
  // synchronized
  void tick (long currentTickTimeMillis, long deltaTickTimeMillis, int seq) {
     getOSBG ();
     if (null == osbg) return;

// fill space with red
     osbg.setColor (Color.red);
     osbg.fillRect (10, 20, 300, 40);

// moving white rect according to tick counter
     osbg.setColor (Color.white);
     osbg.fillRect (seq % 200, 20, 100, 40);

// string shows tick info
     osbg.setColor (Color.black);
     osbg.drawString ("now "+currentTickTimeMillis+" del "+deltaTickTimeMillis+" seq "+seq, 20, 50);

// print to Console to prove we got here for each tick
     System.out.println ("now "+currentTickTimeMillis+" del "+deltaTickTimeMillis+" seq "+seq);

// problem if we request repaint () --
// awt is very slow to honor request
// many ticks do not paint no matter either synch or not
// we see seq changing by big steps in the drawn string
// but println shows --every-- seq so we know we did them
// directPaint works fine

     directPaint ();
    // repaint ();
  }

/** Override default update (it does erase) to avoid screen clears.
 * Little-known fact about update () --
 * Only called when cause was repaint request not exposure.
 * (We do a complete paint after exposure,
 * subject to whatever clipping region we are provided.) */

 private boolean updating = false;

  public void update (Graphics g) { updating = true; paint (g); updating = false; }

/* might be several rectangles we know we have worked in,
 * here we "just know" (10,10,400,100) */

 private Rectangle changed = new Rectangle (10,10,400,100);

  public synchronized void paint (Graphics g) {

 // proof that exposure calls paint directly not via update ()
    if (!updating) System.out.println ("Called paint but not from update");

    if (!updating) {
      g.setColor (Color.white);
      g.fillRect (0, 0, width, height);
    }

    getOSBG ();
    if (null == osbg) return;

/* Only paint the "changed parts" of offScreenBuffer to screen,
 * unless exposure repair which needs everything.
 * Note! g.setClip () is not available in Java 1.0.2,
 * trying throws an Error not an Exception.
 * Here we could use g.clipRect () instead,
 * which always --ands-- with existing clip,
 * because we have a fresh Graphics g every time. */

    if (!updating)
        try { screeng.setClip (changed); } catch (Throwable e) { }

    g.drawImage (offScreenBuffer, 0, 0, this);
  }

/* Paint to screen anytime using our saved Graphics g */

  public synchronized void directPaint () {
      if (null == screeng)
	  screeng = getGraphics ();

/* Only paint the "changed parts" of offScreenBuffer to screen,
 * unless exposure repair which needs everything.
 * Note! g.setClip () is not available in Java 1.0.2,
 * trying throws an Error not an Exception.
 * Here we could --NOT-- use g.clipRect () instead,
 * which always --ands-- with existing clip,
 * because we have a saved Graphics.
 * So for 1.0.2,
 * we must either omit the clip and paint it all,
 * or get a fresh Graphics if the clip changes.
 * Have not experimented... */

      try { screeng.setClip (changed); } catch (Throwable e) { }
      screeng.drawImage (offScreenBuffer, 0, 0, this);
  }

/* Clock stuff */

  private Thread runner = null;
  
  private long currentTickTimeMillis;
  private long lastTickTimeMillis;
  private long scheduledTickTimeMillis;

// this works out to 20 updates per second

  private int  tick_millis = 50;

/** Start the clocking thread if not already running */

  public void start () {
    if (null == runner) {
      runner = new Thread (this); 
      currentTickTimeMillis = System.currentTimeMillis ();
      runner.start ();
    }
  }

/** Set TickMillis  */

  public void setTickMillis  (int tick_millis) { this.tick_millis = tick_millis; }

/** Get TickMillis */

  public long getTickMillis () { return tick_millis; }

/** Execution loop to call tick () at desired frame rate.
 * This method is called by "runner.start ();" not directly. */

  public void run () {

/* Count each tick -- will pass this to the tickee */

     int seq = 0;

/* Could start 'last' at zero or at current -- does anyone care? */

     lastTickTimeMillis= System.currentTimeMillis ();

/* Want first tick right away so schedule it for now */

     scheduledTickTimeMillis = System.currentTimeMillis ();

/* loop forever -- until something kills the thread */

     while (true) {

/* if we are earlier than scheduled try to sleep the difference, */

        while ((currentTickTimeMillis = System.currentTimeMillis ())
	       < scheduledTickTimeMillis) {
          try { Thread.sleep (scheduledTickTimeMillis - currentTickTimeMillis); }
          catch (InterruptedException e) {e.printStackTrace();}
        }

/* now we are at or beyond scheduled,
 * update the next scheduled time,
 * and distribute the tick,
 * with the current tick time,
 * and time difference between the current tick and the last one,
 * and the tick count. */

	scheduledTickTimeMillis = tick_millis + currentTickTimeMillis;
	tick (currentTickTimeMillis,
              currentTickTimeMillis - lastTickTimeMillis,
	      seq++);
        lastTickTimeMillis = currentTickTimeMillis;
     }
  }

}
/* <IMG SRC="/cgi-bin/counter">*/
