//******************************************************************************
// quotes.java:	Applet
//
// Display a series of quotes read from a file on a colored background
// "blackboard style".  A quote will be copied over and over until it
// cannot fit any more, then the "board" is erased and a different quote
// is drawn.  The quotes are supplied by a text file.
// The user can set the speed, colors, font size, and whether or not to 
// begin each quote on a new line ("Bart Simpson style", instead of 
// immediately following the previous quote "Jason Fox style") and/or 
// to select the quotes randomly from the list (instead of using them in
// order).
//
// Copyright © 1998 by Don Del Grande.  All rights reserved (whatever that
// means).
// Permission is granted to use this code for your applets and to make
// modifications to this applet itself, PROVIDED:
// (a) You do not charge ANY sort of fee, including "costs", for this
//     applet, any modified version of this applet, or any applet you
//     develop which derives significantly from this code ("borrowing"
//     one or two basic concepts from this code, like "how to read 
//     strings of text from a file without getting a SecurityException",
//     is OK - how else do you learn except from working examples?);
// (b) If you modify this applet, you add your name (and, preferably, when 
//     and how you modified it) to the comments and not remove any existing
//     names or otherwise make it appear as if you were the sole author;
// (c) You do not own any bootleg "Calvin and Hobbes" or "The Simpsons" 
//     merchandise.  (Note that, except for the books, copies of the strips 
//     themselves, and one calendar, ALL "Calvin and Hobbes" merchandise is 
//     bootlegged.  C&H author Bill Watterson owns all merchandising rights
//     to the characters, and refuses to merchandise them (see "The Calvin
//     and Hobbes Tentn Anniversary Book" for an explanation why).  If you 
//     have a T-shirt that features Calvin (in his pajamas and wearing 
//     sunglasses) and Hobbes dancing, you are in fact in possession of 
//     stolen property.)  I'll overlook bootlegged "King of the Hill" stuff
//     because of this stupid "let's move the show to California" idea...
//
// Violators will be subject to anything from a not particularly cheerful
// E-mail to branding on all appropriate newsgroups and, in severe cases,
// having to listen to me drone on and on about "in my day, we didn't have
// luxuries like HACK and color monitors and 300 MB PCs with 64MB of RAM;
// it was ROGUE and ADM3A terminals and either waiting for a terminal in
// Cory or connecting with a 300 baud modem (because even the directly
// connected terminals couldn't handle anything faster than 9600)"
//
//******************************************************************************
import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;

//==============================================================================
// Main Class for applet quotes
//
//==============================================================================
public class quotes extends Applet implements Runnable
{
	// THREAD SUPPORT:
	//		m_quotes	is the Thread object for the applet
	//--------------------------------------------------------------------------
	private Thread	 m_quotes = null;

	// Parameters
	private boolean	OneQuotePerLine;	// (true) new line is needed after each quote?
	private boolean RandomQuote;		// (true) quotes shown out of sequence?
	private int		Delay;				// (100) minimum delay between letters in milliseconds
	private int		FontSize;			// (24) font size (limited to 8-48)
	private String	QuoteFileName;		// ("quotes.txt") name of the quote file
										// note the parameter name is "File"
	private int		BoardColor;			// (0x008000) the color of the "chalkboard'
										// note the parameter name is "Color"
	private int		TextColor;			// (0xFFFFFF) the color of the text

	// Local variables
	private StringBuffer	QuoteArray[];
	private int		NumQuotes;
	private Font	F;

	private int		Q;				// current quote number
	private int		c;				// pointer to letter in current quote

	private boolean	DrawBackground;
	private int		TextHeight;
	private int		MaxLines;		// lines that will fit
	private StringBuffer	Chalkboard[];
	private int		CurrentLine;
	private int		AppWidth, AppHeight;

	// quotes Class Constructor
	//--------------------------------------------------------------------------
	public quotes()
	{
	}

	// APPLET INFO SUPPORT:
	//		The getAppletInfo() method returns a string describing the applet's
	// author, copyright date, or miscellaneous information.
    //--------------------------------------------------------------------------
	public String getAppletInfo()
	{
		return "What the hell are YOU staring at?";
	}

	// 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()
	{
		int		i;
		String	S;

        // If you use a ResourceWizard-generated "control creator" class to
        // arrange controls in your applet, you may want to call its
        // CreateControls() method from within this method. Remove the following
        // call to resize() before adding the call to CreateControls();
        // CreateControls() does its own resizing.
        //----------------------------------------------------------------------

		// get parameters
		QuoteFileName = "quotes.txt";
		OneQuotePerLine = true;
		RandomQuote = true;
		Delay = 100;
		FontSize = 24;
		BoardColor = 0x8000;
		TextColor = 0xFFFFFF;
		if ((S = getParameter("File")) != null)
			QuoteFileName = S.trim();
		if ((S = getParameter("OneQuotePerLine")) != null)
			OneQuotePerLine = new Boolean(S).booleanValue();
		if ((S = getParameter("RandomQuote")) != null)
			RandomQuote = new Boolean(S).booleanValue();
		if ((S = getParameter("Delay")) != null)
		{
			try
			{
				Delay = new Integer(S).intValue();
			}
			catch (NumberFormatException E)
			{
				Delay = 100;
			}
		}
		if ((S = getParameter("FontSize")) != null)
		{
			try
			{
				FontSize = new Integer(S).intValue();
			}
			catch (NumberFormatException E)
			{
				FontSize = 24;
			}
		}
		if (FontSize < 8)
			FontSize = 8;
		if (FontSize > 48)
			FontSize = 48;
		if ((S = getParameter("Color")) != null)
		{
			try
			{
				i = 0;
				if (S.charAt(0) == '#')
					i = 1;
				int BoardR = Integer.valueOf(S.substring(i, i+2), 16).intValue();
				int BoardG = Integer.valueOf(S.substring(i+2, i+4), 16).intValue();
				int BoardB = Integer.valueOf(S.substring(i+4, i+6), 16).intValue();				
				BoardColor = BoardR * 65536 + BoardG * 256 + BoardB;
			}
			catch (NumberFormatException E)
			{
				BoardColor = 0x8000;
			}
		}
		if ((S = getParameter("TextColor")) != null)
		{
			try
			{
				i = 0;
				if (S.charAt(0) == '#')
					i = 1;
				int TextR = Integer.valueOf(S.substring(i, i+2), 16).intValue();
				int TextG = Integer.valueOf(S.substring(i+2, i+4), 16).intValue();
				int TextB = Integer.valueOf(S.substring(i+4, i+6), 16).intValue();				
				TextColor = TextR * 65536 + TextG * 256 + TextB;
			}
			catch (NumberFormatException E)
			{
				TextColor = 0xFFFFFF;
			}
		}

		NumQuotes = 0;
		try
		{
			URL U = new URL(getDocumentBase(), QuoteFileName);
			DataInputStream FS = new DataInputStream(U.openStream());
			while (FS.readLine() != null)
				NumQuotes++;
			FS.close();
			QuoteArray = new StringBuffer[NumQuotes];
			FS = new DataInputStream(U.openStream());
			for (i = 0; i < NumQuotes; i++)
			{
				S = FS.readLine();
				QuoteArray[i] = new StringBuffer(S.trim());
			}
			FS.close();
		}
		catch (MalformedURLException E)
		{
			QuoteArray = new StringBuffer[1];
			NumQuotes = 0;
			QuoteArray[0] = new StringBuffer("Malformed URL Exception");
		}
		catch (IOException E)
		{
			QuoteArray = new StringBuffer[1];
			NumQuotes = 0;
			QuoteArray[0] = new StringBuffer("I/O Exception or File Does Not Exist");
		}
		catch (SecurityException E)
		{
			QuoteArray = new StringBuffer[1];
			NumQuotes = 0;
			QuoteArray[0] = new StringBuffer("Security Exception");
		}

		Q = 0;
		if (RandomQuote && NumQuotes > 1)
			Q = new Double(Math.random() * NumQuotes).intValue();
		c = 0;

		CurrentLine = 1;
		MaxLines = 0;		// force refresh
		F = new Font("Helvetica", Font.PLAIN, FontSize);
		//***** component.size() is deprecated in 1.1 - use getSize()
		//***** (as soon as Netscape supports 1.1)
		AppWidth = this.size().width;
		AppHeight = this.size().height;
		DrawBackground = true;
	}

	// Place additional applet clean up code here.  destroy() is called when
	// when you applet is terminating and being unloaded.
	//-------------------------------------------------------------------------
	public void destroy()
	{
	}

	// overrides default paint(), which clears the applet area (and
	// causes flickering) first
	public void update(Graphics g)
	{
		paint(g);
	}

	// quotes Paint Handler
	//--------------------------------------------------------------------------
	public void paint(Graphics g)
	{
		int		i;
		StringBuffer	WriteLine;
		StringBuffer	NextWord;
		char	NextChar;
		int		TextBase;
		FontMetrics FM;
		boolean	EndOfQuote;

		// For the start of a new string, draw a dark green rectangle
		// (erasing any previous string)

		FM = g.getFontMetrics(F);
		TextBase = FM.getMaxAscent() + 10;

		// if there was an error (NumQuotes < 1), display the error
		// message (in QuoteArray[0]) and exit

		if (NumQuotes < 1)
		{
			if (NumQuotes == 0)
			{
				g.setColor(Color.red);
				g.fillRect(5, 5, AppWidth - 10, AppHeight - 10);
				g.setFont(F);
				g.setColor(Color.white);
				TextHeight = FM.getMaxAscent() + FM.getMaxDescent() + FM.getLeading();
				g.drawString(QuoteArray[0].toString(), 10, TextHeight);
				NumQuotes = -1;		// only do this once
			}
			return;
		}

		// If the screen is full (or at the start of the program),
		// clear the screen, build the entire screen one line at a time,
		// and get the next quote ready
		if (CurrentLine >= MaxLines)
		{
			TextHeight = FM.getMaxAscent() + FM.getMaxDescent() + FM.getLeading();
			MaxLines = (180 + FM.getLeading()) / TextHeight;
			DrawBackground = true;			// redraw background, erasing existing quote
			Chalkboard = new StringBuffer[MaxLines];
			for (i = 0; i < MaxLines; i++)
				Chalkboard[i] = new StringBuffer("");
			c = 0;
			NextWord = new StringBuffer("");
			CurrentLine = 0;
			while (CurrentLine < MaxLines)
			{
				// if the end of the quote is reached, put a space
				// before the start of the next copy of the quote
				// (if OneQuotePerLine is true, go to the start of the
				// next line instead of inserting a space)
				EndOfQuote = false;
				if (c >= QuoteArray[Q].length())
				{
					if (OneQuotePerLine)
						EndOfQuote = true;
					NextChar = ' ';
					c = 0;
				}
				else
				{
					NextChar = QuoteArray[Q].charAt(c);
					c++;
				}
				// if a space is read, check to see if the previous
				// word fits on the end of the line; if not, start
				// a new line
				if (NextChar == ' ')
				{
					if (NextWord.length() > 0)
					{
						if (FM.stringWidth(Chalkboard[CurrentLine].toString() + NextWord.toString()) < 620)
							Chalkboard[CurrentLine].append(NextWord).append(' ');
						else
						{
							CurrentLine++;
							if (CurrentLine < MaxLines)
								Chalkboard[CurrentLine].append(NextWord).append(' ');
						}
					}
					if (EndOfQuote)
						CurrentLine++;
					NextWord = new StringBuffer("");
				}
				else
					NextWord.append(NextChar);
			}
			if (RandomQuote)
				Q = (Q + new Double(Math.random() * NumQuotes - 2).intValue() + 1) % NumQuotes;
			else
				Q = (Q + 1) % NumQuotes;
			CurrentLine = 0;
			c = 0;								// start with beginning of string
		}
		if (DrawBackground)
		{
			g.setColor(new Color(BoardColor));
			g.fillRect(5, 5, AppWidth - 10, AppHeight - 10);
			DrawBackground = false;
		}
		g.setFont(F);
		g.setColor(new Color(TextColor));
		for (i = 0; i < CurrentLine; i++)
		{
			g.drawString(Chalkboard[i].toString(), 10, TextBase + i * TextHeight);
		}
		g.drawString(Chalkboard[CurrentLine].toString().substring(0, c+1),
			         10, TextBase + CurrentLine * TextHeight);
		c++;
		if (c >= Chalkboard[CurrentLine].length())
		{
			c = 0;
			CurrentLine++;
		}
	}


	//		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_quotes == null)
		{
			m_quotes = new Thread(this);
			m_quotes.start();
		}
		DrawBackground = true;
	}
	
	//		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_quotes != null)
		{
			m_quotes.stop();
			m_quotes = 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()
	{
		while (true)
		{
			try
			{
				repaint();
				Thread.sleep(Delay);
			}
			catch (InterruptedException e)
			{
				stop();
			}
		}
	}


}
