import java.applet.*;     // inherit from Applet
import java.awt.*;        // Window I/O classes
import java.awt.event.*;  // Event classes

// Dilip Barman  November 22, 1997
// file DilipsAnimationApplet.java

// 3rd java homework assignment to play with multimedia and double buffering.
// We can implement any applet or application to illustrate these concepts.
//

//   * * *   B A C K G R O U N D   O N   D O U B L E   B U F F E R I N G   * * *
//
// What is double buffering?  It is a way to smoothly display a series of
// images.  If we keep updating the viewing area directly, we get flicker as
// we update the individual graphic elements.  Instead, we prepare a new
// image in an off-screen buffer and only when it's done and we're ready to
// refresh the screen, then we copy or "blit" (bit block transfer) the buffer
// onto a screen area.  
//
// To do this, we declare one or more off screen images of type Image, as well 
// as offscreen graphic contexts (of type Graphics) into which we draw - e.g.:
//     Image offscreenImage;  Graphics offscreenImageG;
// To copy the image, we can use a method in java.awt.Graphics:
//     public abstract boolean drawImage (Image img, int x, int y,
//                                       Color bgcolor, ImageObserver observer)
// which fills a rectangle of color bgcolor into the observer area, and then
// copies, starting from the upper left (x,y) of img, the img image.
//
// When we're ready to update the screen, in the case of an applet, we call
// the applet's repaint method.  repaint() implicitly calls update(), which
// clears the background and then finally calls paint().  Typically, then,
// paint() is overriden with a method like drawImage() to do the copying
// from the buffer to the screen:
//     public void paint ( Graphics g )  
//       { g.drawImage ( offscreenImage, 0, 0, Color.white, this ); }
// One last note on rendering the image; update()'s clearing of the background
// causes flicker, so we override it to just call paint().
//

//   * * *   B A C K G R O U N D   O N   M U L T I - T H R E A D I N G   * * *
//
// This is our first application where we will be using multi-threading.
// To accomplish animation, we will have objects on the drawing surface which
// move, so they can be modeled as having separate threads of control.  There
// are two approaches to implementing threads in java.  The first approach is
// for the class to implement the Runnable interface.  The class must define
// a run() method, which the java virtual machine calls when the thread is
// given control.  An easier approach, that we'll use here, is for the class
// to extend the java Thread class (which itself implements Runnable).  So,
// we can have a class DilipsThread which extends Thread and defines the
// run() method.  We define threads of type AnimationObject and run() is used
// in the AnimationObject definition.

// 11/29/97: I've been frustrated working on this for a week and not being
//   able to resolve a bug having to do with using the AnimationObject class,
//   so finally started from scratch and have bouncing balls pretty much working.
// 11/30/97: Finished it up by cleaning up code and by adding squares.  Now new
//   objects can be added as circles or squares, each with 50% probability.


public class DilipsAnimationApplet 
  extends Applet
  implements ActionListener
// applet, listen for events like mouse clicks and window close

{

  // ***********  Debug Variables   ***********
  static DebugWindow debugWindow = new DebugWindow();
  private boolean debugMode = true;   // set to true to enable debug window msgs

  // can use statements like this in code:
  // if (debugMode) { debugWindow.print("Just created frame; x is " + x); }



  // ***********  Constants   ***********

  // ***********  Class Constants   ***********  
 
  static final String frameTitle = "Dilip's Bouncing Circles and Squares";
  static final int frameTopLx  =  10;      // Bounds for frame
  static final int frameTopLy  = 190; // 200
  static final int frameWidth  = 475; // 450
  static final int frameHeight = 400; // 380


  // ***********  Class Variables   ***********  

  // ***********  Instance Variables   ***********

  int maxNumObjects = 10;               // set to however many objects you want

  MyFrame frame     = new MyFrame ();  // closeable frame def. in MyFrame.java
  Panel actionPanel = new Panel();     // panel to do the animation
  Panel buttonPanel = new Panel();     // panel to display buttons
  Panel leftBorder  = new Panel();     // dummy - just to create L and R borders
  Panel rightBorder = new Panel();     // "

  TextField message = new TextField(" ", 40); // 40-char message about # objects on pane

  Button addButton    = new Button ( "Add" );  // Button to add new object to animation
  Button deleteButton = new Button ( "Delete" );
  Button resetButton  = new Button ( "Reset" );
  Button startButton  = new Button ( "Start" );
  Button stopButton   = new Button ( "Stop" );
  Button quitButton   = new Button ( "Quit" );

  AnimationObject animationObjects[];    // array to hold each AnimationObject
  int currentObject = - 1;

  Image offscreenImage;
  Graphics offscreenImageG;
  Graphics actionPanelG;
  Dimension d;


  // ***********  Standard Applet Methods   ***********

  // Now we define methods.  Applets look for 4 methods - init, start, stop, and destroy
  // init() is called when the applet is loaded, start() when it starts execution (i.e.,
  // right after init() and whenever we revisit this web page), stop() when we leave this
  // web page, and destroy() when we leave the browser.  Since this is running in its own
  // frame, all we need is init() - which creates the frame.

  public void init()
  {
    createFrame ();  // Build the user interface

    d = actionPanel.getSize ();
    actionPanelG = actionPanel.getGraphics ();

    offscreenImage = this.createImage ( d.width, d.height );
    offscreenImageG = offscreenImage.getGraphics ();
    offscreenImageG.setColor ( Color.white );
    offscreenImageG.fillRect ( 0, 0, d.width, d.height );
    //    this.addMouseListener ( this );
    if (debugMode) { debugWindow.print("About to define " + maxNumObjects + " AnimationObjects"); }
    animationObjects = new AnimationObject [maxNumObjects];
    if (debugMode) { debugWindow.print("Done defining the " + maxNumObjects + " AnimationObjects; about to just loopAndRepaint()"); }
    loopAndRepaint();

  }  // end init

  
  // ***********  Methods to Update the Screen   ***********

  private void loopAndRepaint ( )  
  {  while ( true )  
    {  try 
      {  Thread.sleep ( 20 ); } 
    catch ( InterruptedException e) {;}
            repaint ();
    }  // end while
  }  

  public void update ( Graphics g ) { paint ( g ); }
   public void paint ( Graphics g )
  { actionPanelG.drawImage ( offscreenImage, 0, 0, this ); }

  // ***********  Other Methods   ***********

  // Method to create and display the frame
  private void createFrame ( ) 
  {
    setDefaultBoundsColors ();
    addComponentsToFrame ();
    listenForEvents ();
    frame.show ();    // Finally, show the frame!
  }  // end createFrame() private class method


  // Method to setup initial sizes, colors, etc. of components
  private void setDefaultBoundsColors ( ) 
  { 
    frame.setBounds(frameTopLx, frameTopLy, frameWidth, frameHeight);
    frame.setTitle (frameTitle);
    frame.setBackground (Color.gray);

    buttonPanel.setBackground (Color.darkGray);
    leftBorder.setBackground  (Color.darkGray);
    rightBorder.setBackground (Color.darkGray);
    actionPanel.setBackground (Color.white);

    message.setBackground (Color.darkGray);
    message.setForeground (Color.lightGray);
    message.setText ("Click 'Add' to begin adding objects");
    message.setEditable (false);

  }  // end setDefaultBoundsColors()


  // Method to add the components using simple BorderLayout layout manager
  private void addComponentsToFrame ()
  {

    frame.setLayout (new BorderLayout());

    buttonPanel.add (addButton); 
    buttonPanel.add (deleteButton); 
    buttonPanel.add (resetButton);
    buttonPanel.add (startButton); 
    buttonPanel.add (stopButton);
    buttonPanel.add (quitButton);

    buttonPanel.add (message);  

    frame.add ("North", message);
    frame.add ("Center", actionPanel); 
    frame.add ("South", buttonPanel); 

    frame.add ("West", leftBorder);
    frame.add ("East", rightBorder);
   
  }  // end addComponentsToFrame()


  // Method to register to listen for events we need to react to
  private void listenForEvents ()
  {
    // Listen on events like close (handle in MyFrame class)
    frame.addWindowListener (frame);

    // Listen for button pushes
    addButton.addActionListener ( this );
    deleteButton.addActionListener ( this );
    resetButton.addActionListener ( this );
    startButton.addActionListener ( this );
    stopButton.addActionListener ( this );
    quitButton.addActionListener ( this );

  }  // end listenForEvents()

  //***********  Interface Methods   ***********


  // Now define IMPLEMENTORS - handle button presses
  public void actionPerformed ( ActionEvent e )  
  {
    Object s = e.getSource();

    // ---------------  ADD BUTTON  ---------------
    if ( s == addButton )  
      {  if ( currentObject < (maxNumObjects - 1) )  
	{  ++currentObject;
	if (debugMode) { debugWindow.print("Added new object #" + currentObject); }
              animationObjects [currentObject] = 
                new AnimationObject ( offscreenImageG, d, this );
              animationObjects[currentObject].start();
              if (currentObject == 0)
                {  message.setText ( "First object added; room for " +
				     (maxNumObjects-currentObject-1) + " more" );}
              else
                if (currentObject == (maxNumObjects-1) )  
                  {  message.setText ( maxNumObjects + " (maximum) objects; room for no more" ); }
                else        
                  { message.setText ( (currentObject+1)+" objects; room for " +
                                      (maxNumObjects-currentObject-1) + " more" ); }
	}  // end if currentObject < (maxNumObjects-1)

      else { message.setText ( "Maximum number of animationObjects exceeded." ); }

      }  //  end addButton


    // ---------------  DELETE BUTTON  ---------------
    if ( s == deleteButton )  
      {  synchronized ( offscreenImageG ) 
	{  animationObjects[currentObject].delete();
              animationObjects[currentObject].stop();
	}  
         --currentObject;
         message.setText ( "AnimationObject deleted; room for " +
                           (maxNumObjects-currentObject-1)+" more" );
         if (debugMode) { debugWindow.print("Removed object #" + (currentObject+1) ); }

      }  //  end deleteButton

    // ---------------  RESET BUTTON  ---------------
    if ( s == resetButton )
      {  for ( int i = currentObject; i >= 0; i-- )  
	{  synchronized ( offscreenImageG ) 
	  {  animationObjects[i].delete();
                   animationObjects[i].stop();
	  } 
              if (debugMode) 
                { debugWindow.print("Resetting - removed object #" + i ); }
	} // end for loop
   
         currentObject = -1;
         message.setText ( "Reset - Click 'Add' to begin adding objects again." );
         if (debugMode) { debugWindow.print("Reset complete"); }

      }  //  end resetButton

    // ---------------  START BUTTON  ---------------
    if ( s == startButton )
      {  for ( int i = 0; i <= currentObject; i++ )  
	{  animationObjects[i].resume(); }
         message.setText ( "Action resumed." );
         if (debugMode) { debugWindow.print("Action resumed"); }
      } 

    // ---------------  STOP BUTTON  ---------------
    if ( s == stopButton )
      {  for ( int i = 0; i <= currentObject; i++ )  
	{ animationObjects[i].suspend(); } 
         message.setText ( "Action suspended." );
         if (debugMode) { debugWindow.print("Action suspended"); }
      } 

    // ---------------  QUIT BUTTON  ---------------
    if ( s == quitButton )
      {  for ( int i = 0; i <= currentObject; i++ )  
	{  animationObjects[i].stop(); }  
         message.setText ( "Applet quitting." );
         if (debugMode) 
           {  debugWindow.print("About to quit"); 
              debugWindow.setVisible (false);
           }
         frame.setVisible (false);  // frame.hide(); frame.dispose ();
         animationObjects[0].quit();   // call AnimationObject's quit()
         System.exit (0);
      }  //  end quitButton

  }  // end actionPerformed

} // end DilipsAnimationApplet



  

