/**************************************************************************
* Copyright (c) 1996 Jim Crossley
*
* Although it's unlikely that anyone would actually want it,
* permission is hereby granted, without written agreement and
* without license or royalty fees, to use, copy, modify, and
* distribute this software and its documentation for any purpose,
* provided that the above copyright notice and the following three
* paragraphs appear in all copies of this software, its documentation
* and any derivative work.
*
* In no event shall Jim Crossley or Automated Design Systems, Inc. be
* liable to any party for direct, indirect, special, incidental, or
* consequential damages arising out of the use of this software and
* its documentation.
*
* The software provided hereunder is on an "as is" basis, and neither
* Jim Crossley nor Automated Design Systems, Inc. has any obligation
* to provide maintenance, support, updates, enhancements, or modifications.
*
* Derived or altered versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
***************************************************************************/
package animate;

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import utilities.VCRControls;
import utilities.VCRFrame;

/**
 * Given one image file made up of a sequence of frames, we repeatedly
 * cycle through each frame to provide animation.
 * <p>
 * Necessary parameters in the HTML file:
 * <ul>
 * <li><em>filename</em> - the name of the image file (either jpeg or gif). It better exist!!
 * </ul>
 *
 * Optional parameters [default]
 * <ul>
 * <li><em>repeat</em> - how many milliseconds to wait before replaying animation [5000]
 * <li><em>framedelay</em> - how many milliseconds to pause between frames [100]
 * <li><em>framewidth</em> - the width of an individual frame in pixels [applet's width]
 * <li><em>frameheight</em> - the height of the image in pixels [applet's height]
 * <li><em>backandforth</em> - if "true" then animation plays forwards AND backwards [false]
 * <li><em>vcrbuttons</em> - if "true" then VCR buttons are provided [false]
 * <li><em>startup</em> - initial image to display while frames are loading
 * </ul>
 *
 * Because we ensure that each frame of the animation is displayed, there is a
 * lower limit for "framedelay":  the time it takes to paint the frame.
 * <p>
 * If the applet's size is different than that of the image frames, the image
 * will be scaled... to the possible detriment of the picture.
 *
 * @author  Jim Crossley
 * @version 1.1, 1/29/1997
 *
 */
public class Animate extends Applet
	implements Runnable, VCRControls {

// private members //

	private Image offscreen;
	private Image frames;
	private Image initImage;
	private int repeatWait;
	private int frameDelay;
	private Thread animator;
	private Dimension app;
	private int frameWidthAll;
	private int frameWidthOne;
	private int frame = 0;
	private boolean backAndForth = false;
	private boolean paused = false;
	private MediaTracker tracker;
	private VCRFrame vcrControls = null;

// public methods //

	/**
	 * Initialize the applet's member variables and commence image retrieval.
	 */
	public void init()
	{
        System.out.println("Initializing...");

		initialize();

		tracker = new MediaTracker(this);
		if (initImage != null) tracker.addImage(initImage,0);
		if (frames != null) tracker.addImage(frames,1);
	}

	public void start()
	{
		if (animator == null)
		{
			animator = new Thread(this);
			animator.start();
		}
        if (vcrControls != null && !vcrControls.isShowing()) vcrControls.show();
	}

	public void stop()
	{
        if (animator != null) animator.stop();
		animator = null;

		if (vcrControls != null)
		{
	        vcrControls.hide();
	        vcrControls.dispose();
		}
	}

	/**
	 * The animator thread.  The maximum number of images we retrieve is two:  one
	 * optional, initial image to display as the second image is loading, and the
	 * image that contains the animation frames.
	 */
	public void run()
	{
        System.out.println("Running...");
        Graphics g = offscreen.getGraphics();

        if ((frameWidthAll = getFrames(g)) == 0)
        {
            System.err.println("Error getting the Frames width, aborting...");
            g.dispose();
            return;
        }

		try {
            // The main loop
		    while (!paused)
      		{
                synchronized (this)
                {
    			    drawFrame(g);
    		    }
    		    // Pause for a while to allow the paint thread to run
    		    try	{ Thread.sleep(frameDelay); }	catch (InterruptedException e) { ; }

    		    if (!paused && advanceFrame(FORWARD))
    		    {
        		    try	{ Thread.sleep(repeatWait); }	catch (InterruptedException e) { ; }
    		    }
            }
        }
        finally {
    	    System.out.println("Not running...");
    		g.dispose();
    		animator = null; // help out the gc!
    	}
    }

	/**
	 * VCRControls interface implementation:  calls the applet's start method to
	 * start the animation thread.
	 */
	public void play()
	{
		paused = false;
		this.start();
	}

	/**
	 * VCRControls interface implementation:  kills the animation thread.
	 */
	public void pause()
	{
		paused = true;
		while (animator != null)
		{
			try { Thread.sleep(1); }	catch (InterruptedException e) { ; }
		}
	}

	/**
	 * VCRControls interface implementation:  displays the next animation frame.
	 *
	 * @param direction - Determines whether next or previous frame is displayed.
	 */
	public void frameAdvance(boolean direction)
	{
		advanceFrame(direction);
		drawFrame();
	}

	/**
	 * Override to reduce flicker
	 */
	public void update(Graphics g)
	{
		paint(g);
	}

	/**
	 * Transfer the offscreen image -- its resolution will be scaled if necessary.
	 */
	synchronized public void paint(Graphics g)
	{
		g.drawImage(offscreen,0,0,app.width,app.height,this);
	}

// private methods //

	/*
	 * Read parameters from HTML file to establish values for member variables.
	 */
	private void initialize() {
		int frameHeight;

		app = this.size();

		try {
			repeatWait = Integer.parseInt(this.getParameter("repeat"));
		}
		catch (NumberFormatException e) {
			repeatWait = 5000;
		}
		try	{
			frameDelay = Integer.parseInt(this.getParameter("framedelay"));
		}
		catch (NumberFormatException e)	{
			frameDelay = 100;
		}
		try	{
			frameWidthOne = Integer.parseInt(this.getParameter("framewidth"));
		}
		catch (NumberFormatException e)	{
			frameWidthOne = app.width;
		}
		try	{
			frameHeight = Integer.parseInt(this.getParameter("frameheight"));
		}
		catch (NumberFormatException e) {
		    frameHeight = app.height;
		}
		offscreen = this.createImage(frameWidthOne, frameHeight);

		if (Boolean.valueOf(this.getParameter("vcrbuttons")).booleanValue())
		{
			vcrControls = new VCRFrame(this, this.getParameter("name"));
		}

		backAndForth = Boolean.valueOf(this.getParameter("backandforth")).booleanValue();

		String framesFile = this.getParameter("filename");
		frames = (framesFile == null) ? null :
			this.getImage(this.getDocumentBase(), framesFile);

		String initFile = this.getParameter("startup");
		initImage = (initFile == null) ? null :
			this.getImage(this.getDocumentBase(), initFile);
	}

	/*
	 * Draw the image into the offscreen buffer.  The call to repaint() causes
	 * this.paint(Graphics) to display the offscreen image.
	 * If this.paint(g) is synchronized, the caller of this function needs to
	 * release the object for the screen update to actually occur.
	 */
	private void drawFrame()
	{
	    Graphics g = offscreen.getGraphics();
	    drawFrame(g);
	    g.dispose();
	}
	/*
	 * See above
	 */
	private void drawFrame(Graphics offscreen)
	{
		offscreen.drawImage(frames, frame, 0, this);
		repaint();
	}

	/*
	 * Update the position at which we draw the image containing the animation frames.
	 *
	 * @param forward - if false, we advance to the previous frame.
	 * @return True if we went past the visible range in either direction.
	 */
	private boolean advanceFrame(boolean forward)
	{
		int direction = forward ? 1 : -1;

		frame -= direction * frameWidthOne;
		if (frame > 0 || frame <= -frameWidthAll) // out of visible range
		{
			if (backAndForth)
			{
				frame += direction * frameWidthOne;
				frameWidthOne = -frameWidthOne;
			}
			else
			{
				frame = forward ? 0 : (frameWidthAll/frameWidthOne - 1) * -frameWidthOne;
			}
		    return true;
		}
		else
		{
			return false;
		}
	}

	/*
	 * This function waits for the image containing the animation frames to completely
	 * load.  In addition, it will monitor the loading and then display the initial
	 * "user pacification splash" image.
	 *
	 * @param g For drawing status images.
	 * @return The width of the image containing the animation frames.
	 */
	private int getFrames(Graphics g)
	{
		try
		{
    		if (!tracker.checkAll(true))
        	{
        		System.out.println("Getting animation frame images...");
    			if (initImage != null) {
            		// retrieve and display the initial image
    				System.out.println("waiting for initImage");
    				tracker.waitForID(0);	 // wait for initial image to load
    				System.out.println("got initImage");
    				if (tracker.isErrorID(0)) {
    					System.err.println("Error loading initial image -- Aborting!");
    				}
    				g.drawImage(initImage,0,0,this);
    			}
    			else {
    				int h = g.getFontMetrics().getHeight();
    				g.drawString("Loading images...",2,h);
    				g.drawString("Please wait.",2,2*h);
    			}
    			repaint();
                Thread.yield();    // paint() is synchronized

    			if (frames != null) {
    				System.out.println("waiting for frames");
    				tracker.waitForID(1); // wait for frames image to load
    				System.out.println("got frames");
    				if (tracker.isErrorID(1)) {
    					System.err.println("Error loading frames image -- Aborting!");
    				}
    			}
    		}
		}
		catch (Exception e) {
			System.out.println("Caught Exception -- Aborting!");
		}

		return frames.getWidth(this);
	}
}

