
/*
 * @(#)SplashWindow.java  2.2  2005-04-03
 *
 * Copyright (c) 2003-2005 Werner Randelshofer
 * Staldenmattweg 2, Immensee, CH-6405, Switzerland.
 * All rights reserved.
 *
 * This class has been modified by Paul Gordon to work with applets,
 * and to display a title and status message on top of the splash (startup) screen image
 *
 * This software is in the public domain.
 */

package ca.ucalgary.seahawk.gui.splash;

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;

/**
 * A Splash window.
 *  <p>
 * Usage: MyApplication is your application class. Create a Splasher class which
 * opens the splash window, invokes the main method of your Application class,
 * and disposes the splash window afterwards.
 * Please note that we want to keep the Splasher class and the SplashWindow class
 * as small as possible. The less code and the less classes must be loaded into
 * the JVM to open the splash screen, the faster it will appear.
 * <pre>
 * class Splasher {
 *    public static void main(String[] args) {
 *         SplashWindow.splash(Splasher.class.getResource("splash.gif"));
 *         MyApplication.main(args);
 *         SplashWindow.disposeSplash();
 *    }
 * }
 * </pre>
 *
 * @author  Werner Randelshofer
 * @version 2.1 2005-04-03 Revised.
 *
 * This code has been heavily modified by Paul Gordon to allow message updates,
 * and other neat tricks for applet code.
 */
public class SplashWindow extends Window {
    /**
     * The current instance of the splash window.
     * (Singleton design pattern).
     */
    private static SplashWindow instance;

    // Location of text in the display
    private static int titleX = 20;
    private static int titleY = 50;
    private static int statusX = 20;
    private static int statusY = 160;
    private static int quoteX = 20;
    private static int quoteY = 280;
    
    /**
     * The splash image which is displayed on the splash window.
     */
    private Image image;
    
    /**
     * This attribute indicates whether the method
     * paint(Graphics) has been called at least once since the
     * construction of this window.<br>
     * This attribute is used to notify method splash(Image)
     * that the window has been drawn at least once
     * by the AWT event dispatcher thread.<br>
     * This attribute acts like a latch. Once set to true,
     * it will never be changed back to false again.
     *
     * @see #paint
     * @see #splash
     */
    private boolean paintCalled = false;
    
    private static String titleText;
    private static String statusText;
    private static String quoteOfTheDay = "Please wait...";
    //private static JProgressBar progressBar;

    /**
     * Creates a new instance.
     * @param parent the parent of the window.
     * @param image the splash image.
     */
    private SplashWindow(Frame parent, Image image) {
        super(parent);
        this.image = image;

        // Load the image
        MediaTracker mt = new MediaTracker(this);
        mt.addImage(image,0);

	// Get a random quote
	URL quotesURL = null; //getClass().getClassLoader().getResource("docs/quotes.txt");	
	if(quotesURL != null){
	    try{
		InputStream is = quotesURL.openStream();
		int quoteCount = 0;
		char countChar;
		for(countChar = (char) is.read(); countChar !=-1; countChar = (char) is.read()){
		    if(countChar == '\n'){
			break;
		    }
		    quoteCount = quoteCount*10+Integer.parseInt(""+countChar);
		}
		int randomQuoteNumPicked = (int) (Math.random()*quoteCount);
		while(randomQuoteNumPicked > 0){
		    // Read a line
		    for(countChar = (char) is.read(); 
			countChar != '\n' && countChar != -1; 
			countChar = (char) is.read()){
		    }
		    randomQuoteNumPicked--;
		}
		// Now we are at the line we want.
		quoteOfTheDay = "";
		for(countChar = (char) is.read(); 
		    countChar != '\n' && countChar != -1; 
		    countChar = (char) is.read()){
		    quoteOfTheDay += countChar;
		}
	    }
	    catch(Exception e){
		System.err.println("Could not load quote from quotesURL: " + e);
	    }
	}

	// Wait until the image is loaded
        try {
            mt.waitForID(0);
        } catch(InterruptedException ie){}
        
        // Center the window on the screen
        int imgWidth = image.getWidth(this);
        int imgHeight = image.getHeight(this);
        setSize(imgWidth, imgHeight);
        Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
        setLocation(
        (screenDim.width - imgWidth) / 2,
        (screenDim.height - imgHeight) / 2
        );
        
        // Users shall be able to close the splash window by
        // clicking on its display area. This mouse listener
        // listens for mouse clicks and disposes the splash window.
        MouseAdapter disposeOnClick = new MouseAdapter() {
            public void mouseClicked(MouseEvent evt) {
                // Note: To avoid that method splash hangs, we
                // must set paintCalled to true and call notifyAll.
                // This is necessary because the mouse click may
                // occur before the contents of the window
                // has been painted.
                synchronized(SplashWindow.this) {
                    SplashWindow.this.paintCalled = true;
                    SplashWindow.this.notifyAll();
                }
                dispose();
            }
        };

        addMouseListener(disposeOnClick);

    }

    public static void setTitlePosition(int x, int y){
	titleX = x;
	titleY = y;
    }

    public static void setStatusPosition(int x, int y){
	statusX = x;
	statusY = y;
    }
    
    /**
     * Updates the display area of the window.
     */
    public void update(Graphics g) {
        // Note: Since the paint method is going to draw an
        // image that covers the complete area of the component we
        // do not fill the component with its background color
        // here. This avoids flickering.
        paint(g);
    }
    /**
     * Paints the image on the window.
     */
    public void paint(Graphics g) {
        g.drawImage(image, 0, 0, this);
	if(titleText != null){
	    g.drawString(titleText, titleX, titleY);
	}
        if(statusText != null){
	    g.drawString(statusText, statusX, statusY);
	}
        if(quoteOfTheDay != null){
	    if(quoteOfTheDay.length() > 80){
		int breakPoint = quoteOfTheDay.indexOf(" ", 70);
		g.drawString(quoteOfTheDay.substring(0, breakPoint), quoteX, quoteY-20);
		g.drawString(quoteOfTheDay.substring(breakPoint), quoteX, quoteY);
	    }
	    else{
		g.drawString(quoteOfTheDay, quoteX, quoteY);
	    }
	}

        // Notify method splash that the window
        // has been painted.
        // Note: To improve performance we do not enter
        // the synchronized block unless we have to.
        if (! paintCalled) {
            paintCalled = true;
            synchronized (this) { notifyAll(); }
        }
    }
    
    public static void setStatus(String status){
        statusText = status;
        if(instance != null){
            instance.toFront();  // In case the main window has popped up in the interim
	    instance.repaint();
	}
    }

    public static void setStatus(String status, boolean wait){
        statusText = status;
        if(instance != null && !wait){
	    instance.update(instance.getGraphics());
	}
    }
    
    /**
     * Open's a splash window using the specified image and title
     * @param image The splash image
     * @param title The title of the splash window
     */
    public static void splash(Image image, String title) {
        if (instance == null && image != null) {
            Frame f = new Frame();
            titleText = title;
            
            // Create the splash image
            instance = new SplashWindow(f, image);
            
            // Show the window.
            instance.setVisible(true);
            instance.toFront();
            
            // Note: To make sure the user gets a chance to see the
            // splash window we wait until its paint method has been
            // called at least once by the AWT event dispatcher thread.
            // If more than one processor is available, we don't wait,
            // and maximize CPU throughput instead.
            if (! EventQueue.isDispatchThread() 
            && Runtime.getRuntime().availableProcessors() == 1) {
                synchronized (instance) {
                    while (! instance.paintCalled) {
                        try { instance.wait(); } catch (InterruptedException e) {}
                    }
                }
            }
        }
    }
    
   /**
     * Open's a splash window using the specified image
     * @param image The splash image
     */
    public static void splash(Image image) {
        if (instance == null && image != null) {
            Frame f = new Frame();
            // Jung: There is no title to set here
            
            // Create the splash image
            instance = new SplashWindow(f, image);
            
            // Show the window.
            instance.setVisible(true);
            
            // Note: To make sure the user gets a chance to see the
            // splash window we wait until its paint method has been
            // called at least once by the AWT event dispatcher thread.
            // If more than one processor is available, we don't wait,
            // and maximize CPU throughput instead.
            if (! EventQueue.isDispatchThread() 
            && Runtime.getRuntime().availableProcessors() == 1) {
                synchronized (instance) {
                    while (! instance.paintCalled) {
                        try { instance.wait(); } catch (InterruptedException e) {}
                    }
                }
            }
        }
    }
    
    /**
     * Open's a splash window using the specified image URL and title
     * @param imageURL The url of the splash image
     * @param title The title of the splash window
     */
    public static void splash(URL imageURL, String title) {
        if (imageURL != null) {
           splash(Toolkit.getDefaultToolkit().createImage(imageURL), title);
        }
    }
    
    /**
     * Open's a splash window using the specified image URL
     * @param imageURL The url of the splash image
     */
    public static void splash(URL imageURL) {
        if (imageURL != null) {
            // Jung: calls splash(Image) as we don't have a title
            splash(Toolkit.getDefaultToolkit().createImage(imageURL));
        }
    }
    
    /**
     * Closes the splash window.
     */
    public static void disposeSplash() {
        if (instance != null) {
            instance.getOwner().dispose();
            instance = null;
        }
    }
    
    /**
     * Invokes a method of the provided class name with an integer argument.
     * @param arg the int to be passed into the method.
     */
    public static void invokeStaticMethod(String className, String methodName, int arg) {
        try {
            Class.forName(className)
            .getMethod(methodName, new Class[] {Integer.TYPE})
            .invoke(null, new Object[] {arg});
        } catch (Exception e) {
            InternalError error = new InternalError("Failed to invoke static method " + methodName + 
						    " with int argument");
            error.initCause(e);
            throw error;
        }
    }

    /**
     * Invokes a method of the provided class name with a boolean argument.
     * @param arg the boolean to be passed into the method.
     */
    public static void invokeStaticMethod(String className, String methodName, boolean arg) {
        try {
            Class.forName(className)
            .getMethod(methodName, new Class[] {Boolean.TYPE})
            .invoke(null, new Object[] {arg});
        } catch (Exception e) {
            InternalError error = new InternalError("Failed to invoke static method " + methodName + 
						    " with boolean argument");
            error.initCause(e);
            throw error;
        }
    }

    /**
     * Invokes a method of the provided class name with a double (real number)  argument.
     * @param arg the double precision floating point number to be passed into the method.
     */
    public static void invokeStaticMethod(String className, String methodName, double arg) {
        try {
            Class.forName(className)
            .getMethod(methodName, new Class[] {Double.TYPE})
            .invoke(null, new Object[] {arg});
        } catch (Exception e) {
            InternalError error = new InternalError("Failed to invoke static method " + methodName + 
						    " with double precision floating point number argument");
            error.initCause(e);
            throw error;
        }
    }

    /**
     * Invokes a method of the provided class name with no arguments.
     */
    public static void invokeStaticMethod(String className, String methodName) {
        try {
            Class.forName(className)
            .getMethod(methodName, new Class[] {})
            .invoke(null, new Object[] {});
        } catch (Exception e) {
            InternalError error = new InternalError("Failed to invoke static method " + methodName + 
						    " with no arguments");
            error.initCause(e);
            throw error;
        }
    }

    /**
     * Invokes the main method of the provided class name.
     * @param args the command line arguments
     */
    public static void invokeMain(String className, String[] args) {
        try {
            Class.forName(className)
            .getMethod("main", new Class[] {String[].class})
            .invoke(null, new Object[] {args});
        } catch (Exception e) {
            InternalError error = new InternalError("Failed to invoke main method");
            error.initCause(e);
            throw error;
        }
    }

    /**
     * Invokes the init method of the JApplet class provided by name.
     * @param applet the applet that was actually launched
     */
    public static void invokeInit(String className, javax.swing.JApplet applet) {

        try {
            Class.forName(className)
            .getMethod("init", new Class[] {javax.swing.JApplet.class})
		.invoke(null, new Object[] {applet});// static init takes JApplet arg to copy context, etc.
	    
        } catch (Exception e) {
            InternalError error = new InternalError("Failed to invoke init method");
            error.initCause(e);
            throw error;
        }
    }
}
