//******************************************************************************
// Lake.java:	Applet
//
// (c) David Griffiths, 1997
// This source code may not be reproduced without the express permission of the 
// author.
//******************************************************************************
import java.applet.*;
import java.net.URL;
import java.net.MalformedURLException;
import java.awt.*;

//==============================================================================
// Main Class for applet Lake
//
//==============================================================================
public class Lake extends Applet implements Runnable
{
	// THREAD SUPPORT:
	//		m_Lake	is the Thread object for the applet
	//--------------------------------------------------------------------------
	Thread	 m_Lake = null;

	// ANIMATION SUPPORT:
	//		m_Graphics		used for storing the applet's Graphics context
	//		m_WaveGraphics	used for storing the animation's Graphics context
	//		m_Image   		the original image
	//		m_WaveImage   	the image containing the wave animations
	//		m_nCurrImage	the index of the next image to be displayed
	//		m_ImgWidth		width of each image
	//		m_ImgHeight		height of each image
	//		m_OvlWidth		width of each overlay
	//		m_OvlHeight		height of each overlay
	//		m_fAllLoaded	indicates whether all images have been loaded
	//		m_tAnimate		indicates that OK to do animation (changed by mouse 
	//						click)
	//		NUM_FRAMES      number of NUM_FRAMES used in the animation
	//--------------------------------------------------------------------------
	private Graphics m_Graphics, m_WaveGraphics;
	private Image	 m_Image, m_Overlay, m_WaveImage;
	private int 	 m_nCurrImage;
	private int 	 m_nImgWidth  = 0;
	private int 	 m_nImgHeight = 0;
	private int 	 m_nOvlWidth  = 0;
	private int 	 m_nOvlHeight = 0;
	private boolean  m_fAllLoaded = false, m_tAnimate = true;
	private final int NUM_FRAMES = 12;

	// PARAMETER SUPPORT:
	//		Parameters allow an HTML author to pass information to the applet;
	// the HTML author specifies them using the <PARAM> tag within the <APPLET>
	// tag.  The following variables are used to store the values of the
	// parameters.
    //--------------------------------------------------------------------------

    // Members for applet parameters
    // <type>       <MemberVar>    = <Default Value>
    //--------------------------------------------------------------------------
	private String m_ImageName = "";
	private String m_OverlayName = "";
	private URL m_HRef;
	private String m_Frame = "_self";

    // Parameter names.  To change a name of a parameter, you need only make
	// a single change.  Simply modify the value of the parameter string below.
    //--------------------------------------------------------------------------
	private final String PARAM_image = "image";
	private final String PARAM_overlay = "overlay";
	private final String PARAM_href = "href";
	private final String PARAM_target = "target";

	// Lake Class Constructor
	//--------------------------------------------------------------------------
	public Lake()
	{
		// TODO: Add constructor code here
	}

	// APPLET INFO SUPPORT:
	//		The getAppletInfo() method returns a string describing the applet's
	// author, copyright date, or miscellaneous information.
    //--------------------------------------------------------------------------
	public String getAppletInfo()
	{
		return "Name: Lake v3.0\r\n" +
		       "Author: David Griffiths\r\n" +
		       "Created with Microsoft Visual J++ Version 1.0";
	}

	// PARAMETER SUPPORT
	//		The getParameterInfo() method returns an array of strings describing
	// the parameters understood by this applet.
	//
    // Lake Parameter Information:
    //  { "Name", "Type", "Description" },
    //--------------------------------------------------------------------------
	public String[][] getParameterInfo()
	{
		String[][] info =
		{
			{ PARAM_image, "String", "JPG or GIF file to reflect" },
			{ PARAM_overlay, "String", "JPG or GIF file to use as overlay" },
			{ PARAM_href, "URL", "URL to link to" },
			{ PARAM_target, "String", "Target frame" },
		};
		return info;		
	}

	// The init() method is called by the AWT when an applet is first loaded or
	// reloaded.  Override this method to perform whatever initialization your
	// applet needs, such as initializing data structures, loading images or
	// fonts, creating frame windows, setting the layout manager, or adding UI
	// components.
    //--------------------------------------------------------------------------
	public void init()
	{
		// PARAMETER SUPPORT
		//		The following code retrieves the value of each parameter
		// specified with the <PARAM> tag and stores it in a member
		// variable.
		//----------------------------------------------------------------------
		String param;

		// image: JPG of GIF file to reflect
		//----------------------------------------------------------------------
		param = getParameter(PARAM_image);
		if (param != null)
			m_ImageName = param;

		// overlay: JPG of GIF file to use as overlay
		//----------------------------------------------------------------------
		param = getParameter(PARAM_overlay);
		if (param != null) 
			m_OverlayName = param;

		// href: URL to link to
		//----------------------------------------------------------------------
		param = getParameter(PARAM_href);
		if (param != null) 
                try
                {
                    m_HRef = new URL(getDocumentBase(), param);
                }
                catch (MalformedURLException e)
                {
                    getAppletContext().showStatus("Bad URL: " + param);
                    return;
                }

		// target: Target frame
		//----------------------------------------------------------------------
		param = getParameter(PARAM_target);
		if (param != null) 
			m_Frame = param;
	}

	// Place additional applet clean up code here.  destroy() is called when
	// when you applet is terminating and being unloaded.
	//-------------------------------------------------------------------------
	public void destroy()
	{
		// TODO: Place applet cleanup code here
	}

    // ANIMATION SUPPORT:
    //		Draws the next image, if all images are currently loaded
    //--------------------------------------------------------------------------
	private void displayImage(Graphics g)
	{
		if (!m_fAllLoaded)
			return;

		// Draw frame of rippled lower half
		//----------------------------------------------------------------------
		if (m_WaveImage != null) {
			g.drawImage (m_WaveImage, (-m_nCurrImage * m_nImgWidth), m_nImgHeight, this);
			g.drawImage (m_WaveImage, ((NUM_FRAMES-m_nCurrImage) * m_nImgWidth), 
				m_nImgHeight, this);
		}
		// Draw the original in the tophalf.
		//----------------------------------------------------------------------
		g.drawImage (m_Image, 0, -1, this);
	}

	// Lake Paint Handler
	//--------------------------------------------------------------------------
	public void paint(Graphics g)
	{
		// ANIMATION SUPPORT:
		//		The following code displays a status message until all the
		// images are loaded. Then it calls displayImage to display the current
		// image.
		//----------------------------------------------------------------------
		if (m_fAllLoaded)
			displayImage(g);
		else
			g.drawString("Loading images...", 10, 20);

		// TODO: Place additional applet Paint code here
	}

	//		The start() method is called when the page containing the applet
	// first appears on the screen. The AppletWizard's initial implementation
	// of this method starts execution of the applet's thread.
	//--------------------------------------------------------------------------
	public void start()
	{
		if (m_Lake == null)
		{
			m_Lake = new Thread(this);
			m_Lake.start();
		}
	}
	
	// The stop() method is called when the page containing the applet is
	// no longer on the screen. The AppletWizard's initial implementation of
	// this method stops execution of the applet's thread.
	//--------------------------------------------------------------------------
	public void stop()
	{
		if (m_Lake != null)
		{
			m_Lake.stop();
			m_Lake = null;
		}

	}

	// THREAD SUPPORT
	//		The run() method is called when the applet's thread is started. If
	// your applet performs any ongoing activities without waiting for user
	// input, the code for implementing that behavior typically goes here. For
	// example, for an applet that performs animation, the run() method controls
	// the display of images.
	//--------------------------------------------------------------------------
	public void run()
	{
		m_nCurrImage = 0;
		
		// If re-entering the page, then the images have already been loaded.
		// m_fAllLoaded == TRUE.
		//----------------------------------------------------------------------
        if (!m_fAllLoaded)
		{
    		repaint();
    		m_Graphics = getGraphics();

    		// Now load up the image to be used in the animation. Rather than do
			// this asynchronously with imageUpdate (which is a pain in the bum to
			// use) we'll do it synchronously with a MediaTracker. This hangs
			// around until the image is loaded. Using the waitForAll method, just
			// in case we ever add other images to the applet.
    		//------------------------------------------------------------------
    		MediaTracker tracker = new MediaTracker(this);
    		String strImage;

		    m_Image = getImage(getDocumentBase(), m_ImageName);
            if (!"".equals(m_OverlayName))
			    m_Overlay = getImage(getDocumentBase(), m_OverlayName);

            tracker.addImage(m_Image, 0);
            if (!"".equals(m_OverlayName))
				tracker.addImage(m_Overlay, 1);

    		// Wait until all images are fully loaded
    		//------------------------------------------------------------------
			try
			{
				tracker.waitForAll();
				m_fAllLoaded = !tracker.isErrorAny();
			}
			catch (InterruptedException e) {}
			
			if (!m_fAllLoaded)
			{
			    stop();
			    m_Graphics.drawString("Error loading images!", 10, 40);
			    return;
			}
			

			// Can now set width and height straight away because the 
			// MediaTracker object has ensured this information is now available.
			//--------------------------------------------------------------
		    m_nImgWidth  = m_Image.getWidth(this);
		    m_nImgHeight = m_Image.getHeight(this);
            if (!"".equals(m_OverlayName)) {
			    m_nOvlWidth  = m_Overlay.getWidth(this);
			    m_nOvlHeight = m_Overlay.getHeight(this);
			}

			// Now create the animation of the rippling waves.
			//--------------------------------------------------------------
			createAnimation();
        }		
		repaint();

		while (true)
		{
			try
				// Draw next image in animation
				//--------------------------------------------------------------
				if (m_tAnimate) 
				{
					displayImage(m_Graphics);
					if (++m_nCurrImage == NUM_FRAMES)
						m_nCurrImage = 0;

					Thread.sleep(50);
				}
				else
					Thread.sleep(500);
			catch (InterruptedException e)
				stop();
		}
	}

	// MOUSE SUPPORT:
	// Clicking on the applet starts/stops it.
	// Doesn't call 'stop()' directly because otherwise an InterruptedException 
	// is thrown. ..
	//--------------------------------------------------------------------------
    public boolean mouseUp(Event event, int i, int j)
    {
        boolean flag = super.mouseUp(event, i, j);
		if (m_HRef == null)
			m_tAnimate = !m_tAnimate; // Toggle m_tAnimate to start/stop animation.
		else
		{
			showStatus("" + m_HRef);
			getAppletContext().showDocument(m_HRef, m_Frame);
		}
        return true;
    }

	// ANIMATION
	// Create the animation within a single background image. We use a single
	// image rather than the default multiple images because it's quicker.
	//---------------------------------------------------------------------------
    public void createAnimation () 
	{
		// Create inverted image of original loaded image.
		// We create a background image (backImg) 1 pixel higher
		// than the original because we'll need an extra line of
		// pixels to play with when we flip the image upside down.
		//--------------------------------------------------------        
		Image backImg = createImage (m_nImgWidth, m_nImgHeight + 1);
        Graphics backG = backImg.getGraphics();
		// Copy the original image (m_Image) onto the background
		// version.
		//--------------------------------------------------------
        backG.drawImage (m_Image, 0, 1, this);
		// Now flip the image upside down.
		//--------------------------------------------------------
        for (int i = 0; i < (m_nImgHeight >> 1); i++) 
		{
            backG.copyArea (0, i, m_nImgWidth, 1, 0, m_nImgHeight - i);
            backG.copyArea (0, m_nImgHeight - 1 - i, m_nImgWidth, 1,
		        0, -m_nImgHeight + 1 + (i << 1));
            backG.copyArea (0, m_nImgHeight, m_nImgWidth, 1, 0, -1 - i);
        }
		// Now create the large (NUM_FRAMES + 1 times the width) image
		// that will store dithered copies of the inverted original.
		//--------------------------------------------------------
        m_WaveImage = createImage ((NUM_FRAMES + 1) * m_nImgWidth, m_nImgHeight);
        m_WaveGraphics = m_WaveImage.getGraphics();
        m_WaveGraphics.drawImage (backImg, NUM_FRAMES * m_nImgWidth, 0, this);
		// Now create dithered copies (sine displacement up or down) of the
		// inverted original.
		//--------------------------------------------------------
        for (int phase = 0; phase < NUM_FRAMES; phase++) 
            makeWaves (m_WaveGraphics, phase);
		// Now, if there is an overlay image, draw the top half of it
		// over the frame. (The bottom half of the overlay will be drawn over
		// the rippled image)
		//------------------------------------------------------------
		backG.drawImage (m_Image, 0, 1, this);
		if (!"".equals(m_OverlayName)) 
			backG.drawImage (m_Overlay, 
				(m_nImgWidth - m_nOvlWidth) >> 1,
				m_nImgHeight - (m_nOvlHeight >> 1), this);
		m_Image = backImg;
	}

	// ANIMATION
	// Take the initial (unwaved) image from the left-hand-side of the graphics
	// and make NUM_FRAMES copies of it - the pixels rows of each one dithered
	// up or down depending upon the dispy sine function.
	//---------------------------------------------------------------------------
	public void makeWaves (Graphics g, int phase) 
	{
		double p1;
		int     dispx, dispy;
		// Convert the phase into radians (by splitting 2*PI into 
		// NUM_FRAMES segments).
		//--------------------------------------------------------
		p1 = 2 * Math.PI * (double)phase / (double)NUM_FRAMES;
		// dispx defines how far across the image has to be copied 
		// from the original LHS frame.
		//--------------------------------------------------------
		dispx = (NUM_FRAMES - phase) * m_nImgWidth;
		// Now process each horizontal line of pixels. Copy across
		// from original frame on the left-had-side and displacing 
		// up or down WRT the dispy sine function.
		//--------------------------------------------------------
		for (int i = 0; i < m_nImgHeight; i++) 
		{
			// dispy defines the vertical sine displacement. It 
			// attenuates higher up the image, for perspective.
			//--------------------------------------------------------
			dispy = (int)((m_nImgHeight/14) * ((double) i + 28.0)
				* Math.sin ((double)((m_nImgHeight/14)*(m_nImgHeight - i))
				/(double)(i + 1)
				+ p1)
				/ (double) m_nImgHeight);
			// If no line dithers here then copy original.
			//--------------------------------------------------------
			if (i < -dispy)
				g.copyArea (NUM_FRAMES * m_nImgWidth, i, m_nImgWidth, 1,
					-dispx, 0);
			else
			// Else copy dithered line.
			//--------------------------------------------------------
				g.copyArea (NUM_FRAMES * m_nImgWidth, i + dispy,
					m_nImgWidth, 1, -dispx, -dispy);
		}

		// Now, if there is an overlay image, draw the bottom half of it
		// over the frame. (The top half of the overlay will be drawn over
		// the original image)
		//------------------------------------------------------------
		if (!"".equals(m_OverlayName))
			g.drawImage (m_Overlay, 
				(phase * m_nImgWidth) + ((m_nImgWidth - m_nOvlWidth) >> 1), 
				-m_nOvlHeight >> 1, this);
	}
}
