import java.awt.*;
import java.util.*;
import java.applet.*;
import java.net.*;


/**
 * Takes the logo for the intro of the kiosk, and creates an
 * effect of the logo reflecting in the water.
 *
 * Takes the introLogo from /webstart/graphics
 * and the introSound from /webstart/media
 *
 * @version 1.0, August 1998
 * @author  Robert B. Weeks   rweeks@eng.sun.com
 */

public class LogoReflection extends Applet implements Runnable {
	// Instance Variables
	Thread LogoReflectionThread = null;  // The Thread the applet will run on
	
	// The progress bar on the applet
	int progress = 0;
	
	// The directories to find the logo and sound
	URL mediaDirectory			= null;
	URL graphicsDirectory		= null;
	
	// Set up the kioskResource file
	ResourceBundle kioskResourceBundle;
	
	// The loading string
	String loadingString		= null;
	
	// Is the Kiosk layer that this is on still hidden?
	boolean layerHidden = true; 	
	
	// The actual Image instances for the original and animatied images.
	Image mainImage;
	Image LogoReflectionImage;

	// The Hraphics contexts for the original and animated images.
	Graphics appGraphics;
	Graphics LogoReflectionGraphics;

	// What image frame are we on?
	int currentImage;
	
	// The width and height of the image for calculations.
	int imageWidth  = 0;
	int imageHeight = 0;
	
	// Is the original image loaded yet?
	boolean imgLoaded = false;
	
	// The AudioClip reference for the sound to be played.
	AudioClip	soundClip = null;
	
	// Are we going to play a sound with this?
	boolean playSound = false;
	boolean soundPlaying = false;
	
	// Initialize the variables for the names of the parameters.
	String mainImageName = "";
	String soundBite = "";
	
	// The number of frames in the final animation.
	final int numFrames = 12;
	
	
	/**
	 * Main applet initialization method. 
	 */
	public void init() {
		// Variables
		String s;
		URL soundURL = null;
		
		// Set the background color to black...
		this.setBackground(Color.black);
		
		// Load in the resources....
		kioskResourceBundle = ResourceBundle.getBundle("KioskResources",getLocale());
		
		loadingString = getMessageString("Loading", "Loading...");
		
		
		// Set the URL's for the media and graphics directory
		mediaDirectory = getDirectory("media");
		graphicsDirectory = getDirectory("graphics");
		
		// Now that we have gone this far, there should be no 
		// "ugly" grey applet box, go ahead and display.
		// displayApplet();
	
		// Print out console information.
		System.out.println("\n\nLogoReflection");
		System.out.println("Copyright 2000 Sun Microsystems, Inc.");
		System.out.println("======================================\n\n\n");


		// Get the image from the applet tag
		s = getParameter("introLogo");
		if (s != null) {
			mainImageName = s;
		} else {
			System.out.println("Could not find the image named " + mainImageName + ".");
			System.out.println("Please check your applet tag parameter 'introLogo'");
		}


		// Grab the sound
		s = getParameter("introSound");
		if (s != null) {
			playSound = true;
			soundClip = getAudioClip(mediaDirectory,"introSound.au");
		} else {
			System.out.println("Could not load the sound");
		}

	} // init()



	/*
	 ****************************************************************
	 * Overridden methods.
	 ****************************************************************
	 */
	 
	public void paint(Graphics g) {
		// First, we need to make sure that all the images
		// are loaded in the applet. 
		if (imgLoaded)
			displayFrame(g);
		else
			g.setColor(Color.white);
			g.drawString(loadingString, 10, 20);		
	} // paint()


	public void start() {
		if (LogoReflectionThread == null) {
			LogoReflectionThread = new Thread(this);
			LogoReflectionThread.start();
		}
	} // start()
	

	public void stop() {
		if (LogoReflectionThread != null) {
			LogoReflectionThread.stop();
			LogoReflectionThread = null;
		}
	} // stop();

	public void run() {
		// Initialize current frame.
		currentImage = 0;

		// Test for the image being loaded
        if (!imgLoaded) {
    		repaint();
    		appGraphics = this.getGraphics();

    		// Create a MediaTracker to load in the image..
    		MediaTracker imageTracker = new MediaTracker(this);
		    mainImage = getImage(graphicsDirectory, mainImageName);
            imageTracker.addImage(mainImage, 0);
 
    		// Wait for them to be loaded.
			try {
				imageTracker.waitForAll();
				
				// This will set imgLoaded, and also alert us for any errors.
				imgLoaded = !imageTracker.isErrorAny();
			} catch (InterruptedException trkExc) {
				System.err.println("There was an error loading your images.");
			}
			
			// If there was a problem, as was made above, report it.
			if (!imgLoaded){
			    stop();
			    System.err.println("There was an error loading your images.");
			    return;
			}
			

			// Set the image width and height
		    imageWidth  = mainImage.getWidth(this);
		    imageHeight = mainImage.getHeight(this);
 
			// Create the frames of the animation
			makeLogoReflection();
			
        }		
        
        // Repaint the applet
		repaint();

		// If there is a sound, play it here
		if (playSound && !soundPlaying) {
			soundClip.play();	
			soundPlaying = true;
		}
		


		// Continuously loop
		while (true) {
			try {
				// Display the next frame of the animation
				displayFrame(appGraphics);
				
				// If we are at the last frame (numFrames), start over
				if (++currentImage == numFrames)
					currentImage = 0;

				// Sleep for 50ms so as to not blur (go to fast)
				Thread.sleep(100);
			} catch (InterruptedException exc) {
				System.err.println("We are stopping the applet - there is a problem:");
				System.err.println(exc);
				stop();
			} // try....
		} // while (true)
	} // run()	 



	/*
	 ****************************************************************
	 * Main methods.
	 ****************************************************************
	 */
	 
	/**
	 * If all the images are loaded, draws next frame. If not, returns.
	 *
	 * @param g		Applet Graphics context.
	 */
	void displayFrame(Graphics g) {
		// If all the images are not loaded yet, return.
		if (!imgLoaded)
			return;

		// Draw the LogoReflectioned image
		if (LogoReflectionImage != null) {
			g.drawImage (LogoReflectionImage, (-currentImage * imageWidth), imageHeight, this);
			g.drawImage (LogoReflectionImage, ((numFrames-currentImage) * imageWidth), imageHeight, this);
		}
		
		// Draw the original image
		g.drawImage (mainImage, 0, -1, this);
		
	}

	
	
	// ANIMATION
	// Create the animation within a single background image. We use a single
	// image rather than the default multiple images because it's quicker.
	
	/**
	 * makeLogoReflection
	 *
	 * 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.
	 *
	 * @param None
	 */
    public void makeLogoReflection () {
		Image bufferedImage = createImage (imageWidth, imageHeight + 1);
        Graphics bufferedGraphics = bufferedImage.getGraphics();
        
		// Copy image onto the bufferedGraphic
        bufferedGraphics.drawImage (mainImage, 0, 1, this);

		// Algorithm to flip it upside down......
        for (int i = 0; i < (imageHeight >> 1); i++) {
            bufferedGraphics.copyArea (0, i, imageWidth, 1, 0, imageHeight - i);
            bufferedGraphics.copyArea (0, imageHeight - 1 - i, imageWidth, 1,
		        0, -imageHeight + 1 + (i << 1));
            bufferedGraphics.copyArea (0, imageHeight, imageWidth, 1, 0, -1 - i);
        }

		// Now create the large (numFrames + 1 times the width) image
		// that will store dithered copies of the inverted original.
        LogoReflectionImage = createImage ((numFrames + 1) * imageWidth, imageHeight);
        LogoReflectionGraphics = LogoReflectionImage.getGraphics();
        LogoReflectionGraphics.drawImage (bufferedImage, numFrames * imageWidth, 0, this);

		// Now create dithered copies (sine displacement up or down) of the
		// inverted original.
        for (int phase = 0; phase < numFrames; phase++) 
            createMotion (LogoReflectionGraphics, phase);

		bufferedGraphics.drawImage (mainImage, 0, 1, this);
		
		mainImage = bufferedImage;
	}


	/**
	 * Creates the actual motion in the image as the LogoReflection.
	 * This is done by taking the image, making copies of it 
	 * (according to what numFrames is set as above), and dithering
	 * each row of pixels up or down depending on the outcome of the
	 * displace Y sine function.
	 *
	 * @param	g		Graphics context to be drawn on
	 * @param	frame	Which frame of the animation we are in.
	 */
	public void createMotion (Graphics g, int frame) {
		double frRad;
		int xDisp, yDisp;
		
		// Lets convert the frame itself to radians for calculation.
		// This is done by splitting 2pi into the same number of
		// segments as the number of frames to use (numFrames)
		frRad = 2 * Math.PI * (double)frame / (double)numFrames;
		
		
		
		// xDisp tells where in the 'BIG' created image the frame to be 
		// manipulated is.
		// Ex: frame 3 of animation of 15 frame anim of 1 100px image:
		//     xDisp will be (15-3) * 100 = 1200, so would start
		//     to get this frame starting at 1200px. This is done
		//     from the left side of the image (frames image).
		xDisp = (numFrames - frame) * imageWidth;
		
		// Now create the sine displacement of each line of pixels.
		// Copy from original frame, and displace up or down, depending
		// on outcome of the sin function.
		for (int i = 0; i < imageHeight; i++) {
			// yDisp is the vertical sine displacement. It 
			// attenuates higher up the image, for perspective.
			//--------------------------------------------------------
			
			// This is the actual algorithm to determing the placement of the waves.
			// imageHeight/x makes the waves tighter/wider (smaller to larger),
			// creating a smoothness. A good number is 15 (how many periods 
			// the image is split into.
			// 
			yDisp = (int)((imageHeight/20) * ((double) i + 14.0) * Math.sin((double)((imageHeight/20)*(imageHeight - i))/(double)(i + 1) + frRad) / (double) imageHeight);
			
			
			// Now either copy the altered line or if there was no 
			// dithering, copy the original.
			// First, if i < the negation of yDisp, no dithering done, copy original
			if (i < -yDisp)
				g.copyArea (numFrames * imageWidth, i, imageWidth, 1, -xDisp, 0);
				// numFrames * imageWidth = where to start the rectangle to copy from
				// i is the pixel row that we are on, so will create a clip that high
				// imageWidth tells the width of the clipping rectangle
				// 1 tells the height of the rectangle to copy (1 pixel here)
				// -xDisp is the dx, or the horizontal pixels to copy the image
				// 0 here tells us to keep the y-element the same, thus just a straight copy.
			else
				// or else, there was alterations, so copy the new.
				g.copyArea (numFrames * imageWidth, i + yDisp, imageWidth, 1, -xDisp, -yDisp);
		} // for() ...
	} // createMotion()
	
	
	
	/*
	 ****************************************************************
	 * Get/Set methods.
	 ****************************************************************
	 */
	
	/**
	 * Get the URL to the directory specified in the arg.
	 * The directory must reside in /webstart/kiosk
	 *
	 * @arg String containing name of the directory to be returned.
	 * @return URL of the directory querying about.
	 */
	public URL getDirectory(String dirName) {
		URL returnURL		= null;
		String kioskDir		= null;
		String targetDir	= null;
		String tempDir		= null;
		int baseIndex;
		
		// Setup the dir originally
		kioskDir = this.getCodeBase().toString();
		baseIndex = kioskDir.lastIndexOf("/");
		tempDir = kioskDir.substring(0,baseIndex);
		
		// Do it once more to get rid of the /classes dir
		// to reveal the /webstart/kiosk dir
		kioskDir = null;
		baseIndex = tempDir.lastIndexOf("/");
		// Want to add +1 to include the last '/'
		targetDir = new String((tempDir.substring(0,baseIndex + 1)) + dirName + "/");
		
		// Now try to create a URL from our targetDir
		try {
			returnURL = new URL(targetDir);
		} catch (MalformedURLException mue) {
			mue.printStackTrace();
		}
		
		// And return our directory
		return returnURL;
	 
	 } // getDirectory()
	 
	 /**
	 * Retrieves a resource from the messageResource Bundle for
	 * internationalized text. 
	 *
	 * @param messageKey	The key to look up in the resource class.
	 * @param defaultMessage Message to return if messageKey not found.
	 *
	 * @return String containing the value from the key.
	 */
	public String getMessageString(String messageKey, String defaultMessage) {
		// Use kioskResourceBundle assigned in init()
		String newMessage;
		
		try {
			newMessage = (String)kioskResourceBundle.getObject(messageKey);
		} catch (MissingResourceException rsrcExc) {
			newMessage = defaultMessage;
		}
		
		// Return it
		return newMessage;
			
	} // getMessageString
	 
	 
	
	/*
	 ****************************************************************
	 * Convenience methods.
	 ****************************************************************
	 */
	 public void console(String msg) {
	 	System.out.println("Console: " + msg);
	 }
	
	
	
} // class LogoReflection

