import java.applet.*;
import java.awt.*;
import java.awt.event.*;

/**
 * This class defines the applet, and sets up the Model and View objects.
 * This program is more complex than it needs to be, because it enforces
 * a strict separation between the model (the part that does the actual
 * computation), the display, and the controls in the user interface (the
 * Start/Stop button). This is called the MVC (Model-View-Controller)
 * pattern, and while it may be overkill for this example, MVC provides an
 * excellent basis for more complex animations.
 */
public class Controller extends Applet implements ActionListener {

  // Declare components here, where they are visible to inner classes
  Panel buttonPanel = new Panel();
  Button runButton = new Button("  Start  ");

  // Create the model (the ball) and the view (the animation)
  Thread animation;
  Model model = new Model();
  View view = new View();
  
  /**
   * Lays out the applet components, starts a new thread for the
   * view to do its animation, and gives the model, view, and
   * controller access to one another.
   */
  public void init() {
  
    // Lay out components
    setLayout(new BorderLayout());
    this.add(BorderLayout.CENTER, view);
    buttonPanel.add (runButton);
    buttonPanel.setBackground(Color.lightGray);
    this.add (BorderLayout.SOUTH, buttonPanel);
        
    // Get applet parameters and put them where they belong
    view.delay = Integer.valueOf(getParameter("delay")).intValue();
    
    // Tell the view about the model and the controller
    view.model = model;
    view.controller = this;
   
    // Get everything set up for the view BEFORE starting its Thread
    view.alive = true;
    view.okToRun = false;
    
    // Give the view its own thread in which to do the animation
    animation = new Thread(view);
    animation.start();

    // Attach actions to components
    runButton.addActionListener(this);
  }
  
  /**
   * Figures out which ActionEvent occurred and takes appropriate
   * action. We said that this class implements ActionListener--
   * this is where we fulfil that promise.
   */
  public void actionPerformed (ActionEvent event) {
    if (event.getSource() == runButton) {
      if (view.okToRun) {
        view.okToRun = false;
        showStatus ("Animation is stopped.");
        runButton.setLabel ("  Start  ");
      }
      else {
        view.okToRun = true;
        showStatus ("Animation is running.");
        runButton.setLabel ("  Stop   ");
      }
    }
  }
      
  /**
   * Finds the size of the canvas (this can't be done in init()
   * because the canvas isn't ready yet) and gives the animation
   * permission to run.
   */
  public void start() {
    model.xLimit = view.getSize().width - model.BALL_SIZE;
    model.yLimit = view.getSize().height - model.BALL_SIZE;
  }
  
  /**
   * Withdraws permission for the animation to run.
   */
  public void stop() {
    view.okToRun = false;
  }
  
  /**
   * Tells the animation thread to stop running.
   */
  public void destroy() {
    view.alive = false;
  }
}

// -------------------------------------------------------------------------------------  

/**
 * This class models the action of a bouncing ball. It is important to note
 * that this class is <i>independent</i> of the GUI. For this animation, the
 * Model does need to know the size of the box that the ball is bouncing
 * around in, and this is set from outside. Similarly, it provides a
 * makeOneStep() method so that the model can be controlled from outside the
 * class; but neither of these requires the model to know anything at all
 * about the rest of the program
 */
class Model {
  final int BALL_SIZE = 20;
  int xPosition = 0;
  int yPosition = 0;
  int xLimit, yLimit;
  int xDelta = 4;
  int yDelta = 3;
  
  /**
   * Moves the ball one step, by adding xDelta and yDelta to its xPosition
   * and yPosition, respectively, and checking for bounces off the "walls".
   */
  void makeOneStep() {
    xPosition += xDelta;
    if(xPosition < 0 || xPosition >= xLimit) {
      xDelta = -xDelta;
      xPosition += xDelta;
    }
    yPosition += yDelta;
    if(yPosition < 0 || yPosition >= yLimit) {
      yDelta = -yDelta;
      yPosition += yDelta;
     }
  }
}

// -------------------------------------------------------------------------------------  

/**
 * This class runs in a separate (animation) thread and controls all the
 * painting.
 */
class View extends Canvas implements Runnable {
   Controller controller;
   Model model;
   Dimension size = getSize();
   int delay = 1000;
   int stepNumber = 0;
   boolean alive, okToRun;
    Image offscreenImage = null;
    Graphics offscreenGraphics = null;
   
   /**
    * Sets up an (almost) infinite loop to do the animation, and every
    * delay milliseconds asks the model to update itself, then draws the
    * result.
    */
   public void run() {
   
     // "alive" will be true until the Applet is destroyed
     while(alive) {
     
       // "okToRun" will be true if the Applet is started and not stopped
       if(okToRun) {
         model.makeOneStep();
         repaint(); // Note: uses Graphics for this Canvas, not for the Applet
       }
       
       // Control the speed of execution
       try { Thread.sleep(delay); }
       catch(InterruptedException e) {}
     }
   }
  
  /**
   * To avoid flicker, this method paints into the Graphics
   * of an offscreen Image.
   */
  public void update(Graphics g) {
  
    // Don't do anything without permission from the controller
    if (!okToRun) return;
    
    // Create the offscreen Graphics
    if (offscreenImage == null) {
      offscreenImage = createImage(getSize().width, getSize().height);
      offscreenGraphics = offscreenImage.getGraphics();
    }
    // Paint into the offscreen Graphics
    background(offscreenGraphics);
    paint(offscreenGraphics);
    
    // Copy the offscreen onto the screen
    g.drawImage(offscreenImage, 0, 0, null);
  }
  
  /**
   * Repaints the canvas based on information that it gets from the model.
   */
  public void paint(Graphics g) {

    // Don't do anything without permission from the controller
    if (!okToRun) return;

    // Draw new ball
    g.setColor(Color.red);
    g.fillOval(model.xPosition, model.yPosition, model.BALL_SIZE, model.BALL_SIZE);

    // Display steps and position in the status line;
    // this is a good place to put debugging information.
    if(++stepNumber % 10 == 0)
      controller.showStatus("Step " + stepNumber + ", x = " + model.xPosition +
                            ", y = " + model.yPosition);
  }
  
  /**
   * Paints a background on the given Graphics.
   */
   void background(Graphics g) {
       int width = getSize().width;
       int height = getSize().height;
       int squareSize = 2 * model.BALL_SIZE;
       
       g.setColor(Color.blue);
       g.fillRect(0, 0, width, height);
       g.setColor(Color.green);
       for (int x = 0; x < width; x += 2 * squareSize) {
           for (int y = 0; y < height; y += 2 * squareSize) {
               g.fillRect(x, y, squareSize, squareSize);
               g.fillRect(x + squareSize, y + squareSize, squareSize, squareSize);
           }
       }
   }
}
