/* ImageStack.java is based on the animator applet that
 * comes with Java and was written by Herb Jellinek.
 * You can get the animator applet in the Java Development Kit from
 * Sun Microsystems.  (http://www.javasoft.com)
 *
 * ImageStack.java was heavily edited by Paul Houle (ph18@cornell.edu)
 * to transform the animator into a widget that can be controlled by
 * JavaScript through Netscape's LiveConnect.
 *
 * How it works:  see http://www.msc.cornell.edu/~houle/javascript/ImageStack.html
 *
 * The routine that is really interesting (that interfaces with Javascript
 * is called "changeImage".
 *
 * Feel free to use this source code as a starting point for your own
 * programs,  just maintain the chain of attribution.
 */

package honeylocust;

import java.io.InputStream;
import java.awt.*;
import java.awt.image.ImageProducer;
import java.applet.Applet;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;

/**
 * An applet that plays a sequence of images, as a loop or a one-shot.
 * Can have a soundtrack and/or sound effects tied to individual frames.
 *
 * @author Herb Jellinek
 * @version 1.1, 3/4/96
 */

public class ImageStack extends Applet implements Runnable {
    
    /**
     * The images, in display order (Images).
     */
    Vector images = null;

    /**
     * Start-up image URL, if any.
     */
    URL startUpImageURL = null;

    /**
     * Start-up image, if any.
     */
    Image startUpImage = null;

    int startImage = 1;

    /**
     * Largest width.
     */
    int maxWidth = 0;

    /**
     * Largest height.
     */
    int maxHeight = 0;

    /**
     * Was there a problem loading the current image?
     */
    boolean imageLoadError = false;

    /**
     * The directory or URL from which the images are loaded
     */
    URL imageSource = null;

    /**
     * The thread animating the images.
     */
    Thread engine = null;

    /**
     * The current loop slot - index into 'images.'
     */
    int frameNum=0;

    /**
     * frameNum as an Object - suitable for use as a Hashtable key.
     */
    Integer frameNumKey;
    
    /**
     * The current X position (for painting).
     */
    int xPos = 0;
    
    /**
     * The current Y position (for painting).
     */
    int yPos = 0;
    
    /**
     * Load all images before starting display, or do it asynchronously?
     */
    boolean loadFirst;
    
    /**
     * The offscreen image, used in double buffering
     */
    Image offScrImage;

    /**
     * The offscreen graphics context, used in double buffering
     */
    Graphics offScrGC;

    /**
     * Can we paint yet?
     */
    boolean loaded = false;

    /**
     * Was there an initialization error?
     */
    boolean error = false;

    /**
     * What we call an image file in messages.
     */
    final static String imageLabel = "image";
    
    /**
     * Print silly debugging info?
     */
    boolean debug_flag = false;

    /**
     * Awful hack to make sure first image loads correctly
     */

    boolean firstpaint=false;

    /**
     * Info.
     */
    public String getAppletInfo() {
	return "MultiWindow by Paul Houle (ph18@cornell.edu)";
    }

    /**
     * Parameter Info
     */
    public String[][] getParameterInfo() {
	String[][] info = {
	    {"imagesource", 	"url", 		"a directory"},
	    {"startup", 	"url", 		"displayed at startup"},
	    {"startimage", 	"int", 		"start index"},
	};
	return info;
    }

   /**
   * I'm the method who JavaScript talks to.  I tell MultiWindow to change it's
   * image to the n'th image.
   *
   * @param n image number to change to
   *
   */
    public void changeImage(int n) {
        setFrameNum(n);
        repaint();
    };

   /**
    *
    * I am also designed to be talked to by JavaScript.  I return the
    * number of pictures that I hold.
    *
    * @return number of pictures held
    */

    public int numberImages() { return images.size(); };

    /**
     * Print silly debugging info.
     */
    void dbg(String s) {
	if (debug_flag) {
	    System.out.println(s);
	}
    }

    final int setFrameNum(int newFrameNum) {
	frameNumKey = new Integer(frameNum = newFrameNum);
	return frameNum;
    }
    
    void updateMaxDims(Dimension dim) {
	maxWidth = Math.max(dim.width, maxWidth);
	maxHeight = Math.max(dim.height, maxHeight);
    }

    /**
     * Parse the IMAGES parameter.  It looks like
     * 1|2|3|4|5, etc., where each number (item) names a source image.
     *
     * Returns a Vector of image file names.
     */
    Vector parseImages(String attr) {
	Vector result = new Vector(10);
	for (int i = 0; i < attr.length(); ) {
	    int next = attr.indexOf('|', i);
	    if (next == -1) next = attr.length();

	    try {
	        URL file = new URL(imageSource,attr.substring(i, next));
         	result.addElement(file);
	    } catch (MalformedURLException mue)
 	    {
		result.addElement("Could not form URL");
	    };

	    i = next + 1;
	}
	return result;
    }

    /**
     * Fetch the images named in the argument, updating 
     * maxWidth and maxHeight as we go.
     * Is restartable.
     *
     * @return URL of the first bogus file we hit, null if OK.
     */

    URL fetchImages(Vector images) {
	dbg("fetchImages:  loading "+images.size()+" images.");
	for (int i = 0; i < images.size(); i++) {
	    Object o = images.elementAt(i);
	    System.out.println("image #"+i+" is "+o);

	    if (o instanceof URL) {
		URL url = (URL)o;
		tellLoadingMsg(url, imageLabel);
		dbg("Attempting to load image from URL: "+url);

		Image im = getImage(url);

		images.setElementAt(im,i);
	    }

	}

	return null;
    }

    /**
     * Initialize the applet.  Get parameters.
     */
    public void init() {

    String param;

	try {

	    if (getParameter("DEBUG") != null)
	   {
		debug_flag=true;
	   } else debug_flag=false;

	    param = getParameter("IMAGESOURCE");	
	    imageSource = (param == null) ? getDocumentBase() : new URL(getDocumentBase(), param + "/");
	    dbg("IMAGESOURCE = "+param);

		param = getParameter("STARTIMAGE");
		dbg("STARTIMAGE = "+param);
		if (param != null) {
		    startImage = Integer.parseInt(param);
		    frameNum=startImage;
		}

	      
		param = getParameter("IMAGES");
	        dbg("IMAGES = " +param);
	        if (param == null) {
			showStatus("No legal IMAGES, STARTIMAGE, or ENDIMAGE "+
				   "specified.");
			return;
		} else {
		    dbg("Parsing images.");
		    images = parseImages(param);
		    dbg("I found "+images.size()+ " images");
		};
		
	       param = getParameter("STARTUP");
	       dbg("STARTUP = "+param);
	       if (param != null) {
	        	startUpImageURL = new URL(imageSource, param);
	       }

	    } catch(Exception e)
		{};
    }

    void tellLoadingMsg(String file, String fileType) {
	showStatus("MultiWindow: loading "+fileType+" "+abridge(file, 20));
    }

    void tellLoadingMsg(URL url, String fileType) {
	tellLoadingMsg(url.toExternalForm(), fileType);
    }

    void clearLoadingMessage() {
	showStatus("");
    }

        
    /**
     * Cut the string down to length=len, while still keeping it readable.
     */
    static String abridge(String s, int len) {
	String ellipsis = "...";

	if (len >= s.length()) {
	    return s;
	}

	int trim = len - ellipsis.length();
	return s.substring(0, trim / 2)+ellipsis+
	    s.substring(s.length() - trim / 2);
    }
    
    void loadError(URL badURL, String fileType) {
	String errorMsg = "Animator: Couldn't load "+fileType+" "+
	    badURL.toExternalForm();
	showStatus(errorMsg);
	System.err.println(errorMsg);
	error = true;
	repaint();
    }

    void showParseError(Exception e) {
	String errorMsg = "Animator: Parse error: "+e;
	showStatus(errorMsg);
	System.err.println(errorMsg);
	error = true;
	repaint();
    }

    /**
     * Run the animation. This method is called by class Thread.
     * @see java.lang.Thread
     */

    synchronized public void run() {
	
	Thread me = Thread.currentThread();
	
	me.setPriority(Thread.MIN_PRIORITY);

	updateMaxDims(size());

	if (! loaded) {
	    try {
		// ... to do a bunch of loading.
		if (startUpImageURL != null) {
		    tellLoadingMsg(startUpImageURL, imageLabel);
		    startUpImage = getImage(startUpImageURL);

//		    resize(maxWidth, maxHeight);
//		    repaint();
		    
		}

		URL badURL = fetchImages(images);
		if (badURL != null) {
		    loadError(badURL, imageLabel);
		    return;
		}

		clearLoadingMessage();

		dbg("attempted to create image size: ("+maxWidth+","+maxHeight+")");
		offScrImage = createImage(maxWidth, maxHeight);
		offScrGC = offScrImage.getGraphics();
		offScrGC.setColor(Color.lightGray);

		resize(maxWidth, maxHeight);
		loaded = true;
		error = false;
	    } catch (Exception e) {
		error = true;
		e.printStackTrace();
	    }
	
	    /* get the start image loaded */

	    prepareImage((Image) images.elementAt(startImage),this); 

	    while(!firstpaint) 
		try {wait();} catch(Exception e) {};

	    /* and then load the rest */

	    for(int i=0;i<images.size();i++)
		if(i != startImage )
		{
			dbg("preparing image # " + i);
			prepareImage((Image) images.elementAt(i),this);
		};



            /* ugly awful hack that ensured loading when multiple */
	    /* instances of the applet are on screen */
	    /* it seems to work OK now w/o the hack */

/*
	    while(!firstpaint)
		{
			try {
				me.sleep(250);
			} catch (InterruptedException e) {};

			repaint();
		};
*/

	}
 }

    /**
     * Paint the current frame.
     */
    public void paint(Graphics g) {
	dbg("Painting!");
	if (error || !loaded) {
	    dbg("Not loaded yet.");
	    if (startUpImage != null) {
		g.drawImage(startUpImage, 0, 0, this);
	    } else {
		    g.clearRect(0, 0, maxWidth, maxHeight);
	    };

	} else {
	    if ((images != null) && (images.size() > 0)) {
		dbg("Images loaded");
		if (frameNum < images.size()) {
		     offScrGC.fillRect(0, 0, maxWidth, maxHeight);

		    dbg("Attempting to draw image # "+frameNum);
		    Image image = (Image)images.elementAt(frameNum);

		    offScrGC.drawImage(image, xPos, yPos, this);


		    g.drawImage(offScrImage, 0, 0, this);


		} else {
		    // no more animation, but need to draw something
		    dbg("No more animation; drawing last image.");
		    g.drawImage((Image)images.lastElement(), 0, 0, this);
		}
	    }
	}
    }

  synchronized public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {
    dbg("imageUpdate called!");
    notifyAll();

    if ((infoflags & ALLBITS) != 0) {

      dbg("Image ready!");
      if(img == images.elementAt(frameNum))
	{
	      repaint();
	      firstpaint=true;
	};

      return false;
    }
    else 
    {
      dbg("Image not ready yet!");
      return true;
    };
  }
  

    /**
     * Start the applet by forking the loader thread
     */
    public void start() {
	if (engine == null) {
	    engine = new Thread(this);
	    engine.start();
	}
    }

    /**
     * Stop the insanity, um, applet.
     */
    public void stop() {
	if (engine != null && engine.isAlive()) {
	    engine.stop();
	}
	engine = null;
    }

};



















