package ngp;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import ngp.Animatable;


/**
 * Animates a collection of shapes by calling on them to 
 * make a small change in their state, then painting those
 * changes on the screen.
 *
 * @author Robert C. Duvall
 */
public class Canvas extends JPanel implements ActionListener
{
    // make it easy to refer to one second in milliseconds
    private static final int ONE_SECOND = 1000;
    // make it easy to change animation rate
    private static final int NUM_FRAMES_PER_SECOND = 30;

    // things that will be animated
    private java.util.List myShapesToDisplay;
    private java.util.List myShapesToAdd;
    private java.util.List myShapesToRemove;

    // make it easy to control timing of animation
    private Timer myClock;


    /**
     * Construct panel that animates shapes
     */
    public Canvas (Dimension size)
    {
        // let Java runtime know dimension panel should be
        setPreferredSize(size);
        // make the background a little more reasonable color
        setBackground(Color.WHITE);

        // set up holder for animatables
        myShapesToDisplay = new java.util.ArrayList();
        myShapesToAdd = new java.util.ArrayList();
        myShapesToRemove = new java.util.ArrayList();

        // set animation state
        myClock = new Timer(ONE_SECOND / NUM_FRAMES_PER_SECOND, this);
    }


    /**
     * Return center of the canvas, so others do not have to calculate it
     */
    public Point getCenter ()
    {
        return new Point(getSize().width / 2, getSize().height / 2);
    }


    /**
     * Start the animation
     */
    public void start ()
    {
        myClock.start();
    }

    /**
     * Stop the animation
     */
    public void stop ()
    {
        myClock.stop();
    }


    /**
     * Return how many shapes there are
     */
    public int numberOfShapes ()
    {
        return myShapesToDisplay.size();
    }

    /**
     * Allow others to iterate over the shapes
     */
    public java.util.Iterator getShapes ()
    {
        return myShapesToDisplay.iterator();
    }

    /**
     * Add given shape to panel to be animated and painted.
     * Shapes are painted in the order they are added.
     */
    public void add (Animatable shape)
    {
        // remember shape for later
        myShapesToAdd.add(shape);
        // let Java runtime that a refresh is needed
        repaint();
    }

    /**
     * Forget given shape, i.e., cause it to disappear.
     */
    public void remove (Animatable shape)
    {
        // forget shape
        myShapesToRemove.add(shape);
        // let Java runtime that a refresh is needed
        repaint();
    }

    /**
     * Clear canvas of all shapes.
     */
    public void clear ()
    {
        // forget all shapes
        myShapesToRemove.addAll(myShapesToDisplay);
        // let Java runtime that a refresh is needed
        repaint();
    }


    /**
     * This method determines how the shape animates by making a
     * small change to some attribute (position, color, size, etc.)
     * over time
     */
    public void animate ()
    {
        // animate shapes
        java.util.Iterator iter = myShapesToDisplay.iterator();
        while (iter.hasNext())
        {
            ((Animatable)iter.next()).update(this);
        }
    }


    /**
     * Never called directly, instead called by Java runtime
     * when area of screen covered by this container needs to be 
     * displayed (i.e., creation, uncovering, change in status)
     */
    public void paintComponent (Graphics pen)
    {
        // clear the panel
        super.paintComponent(pen);

        // paint shapes
        processUpdates();
        java.util.Iterator iter = myShapesToDisplay.iterator();
        while (iter.hasNext())
        {
            ((Animatable)iter.next()).render((Graphics2D)pen);
        }
    }


    protected void processUpdates ()
    {
        // process any add requests
        java.util.Iterator iter = myShapesToAdd.iterator();
        while (iter.hasNext())
        {
            myShapesToDisplay.add(iter.next());
        }
        // process any remove requests
        iter = myShapesToRemove.iterator();
        while (iter.hasNext())
        {
            myShapesToDisplay.remove(iter.next());
        } 
        // get ready for more changes
        myShapesToAdd.clear();
        myShapesToRemove.clear();
    }


    /**
     * Never called directly, instead called by Java runtime
     * each time the timer's delay expires
     */
    public void actionPerformed (ActionEvent e)
    {
        // update and let Java runtime know panel needs to be repainted
        animate();
        repaint();
    }
}
