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

// v1 Search applet
// June 2000
// K.Becker

/*
 * This applet provides client-side search facilities for Web sites
 *
 * Adapted from: Home Page Search Program
 * Copyright (c) 1997 Richard Everitt G4ZFE
 *      richard@babbage.demon.co.uk
 *      http://www.babbage.demon.co.uk/
 *
 * I have left much of the original code intact even though some of it 
 * does not apply to this use. KB
 *
 */

/* Applet parameters:
 * This applet takes the following parameters
 * a. siteRoot,  the name of the root directory for the site being searched
 * b. maxSearch, the maximum number of matches to search for. Used to cut 
 *               down on the search time for very large sites
 * c. debug,     this parameter is for my use. Set to true to display 
 *               parameter and debug information.
 *               (this parameter is optional)
 * d. bgColour,  The background colour for the applet in RGB hex format
 *               (rrggbb). The default is light grey.
 * e. fgColour,  The foreground colour for the applet in RGB hex format
 *               (rrggbb). The default is black.
 * * Example of applet use on a Demon Home Page - www.cpsc.ucalgary.ca
 * <APPLET CODE="HomePageSearch.class" WIDTH=650 HEIGHT=400>
 * <PARAM NAME="siteRoot" VALUE="cpsc.ucalgary.ca/~becker/461">
 * <EM>Sorry but the Search applet requires a Java aware
 * Web browser </EM>
 * </APPLET>
 *
 * Example of applet use
 * <APPLET CODE="HomePageSearch.class" WIDTH=650 HEIGHT=400>
 * <PARAM NAME="debug" VALUE="true">
 * <EM>Sorry but the Search applet requires a Java aware
 * Web browser </EM>
 * </APPLET>
 */


/* Modification history
 * for original modification history, see:
 * http://www.babbage.demon.co.uk/java/HomePageSearch.java
 *
 */

public class search2 extends Applet
{
    final int MAX_NUMBER_PAGES = 100;       // default limit on number
                                            // of matches to try for
    final int BACKSPACE_CHARACTER = 8;      // ASCII backspace 
    final int NUMBER_SPECIAL_CHARS = 45;    // Number of special character
				            // entity names supported

    Button search, clear, abort;    // GUI buttons

    TextField inputArea;            // TextField used to enter 
				    // search text in
    TextField statusArea;           // TextField used to display 
				    // search status
    List  resultsArea;              // List to display matches in

    public String siteRoot;         // name of root for this site

    public int maxSearch = MAX_NUMBER_PAGES;
                                    // Maximum number of pages to
                                    // search (optional)
    public boolean debugMode;       // TRUE = localhost
				    // FALSE = on-line

    Vector pageNames;               // Pages that have been visited
    
    public String indexName;        // Name of index page (defaults to
				    // index.html or index.htm)
    
    SearchData cp = null;          // Search thread
    
    Checkbox caseSensitive; 
    Checkbox caseInsensitive;
    Checkbox matchWholeWord;
    
    public boolean matchCase = false;       // Flag to indicate if we
					    // need to match case.


    public boolean matchWord = false;       // Flag to indicate if we need
					    // to match the whole word

    String versionNumber = "v1.0";
	
    boolean packComponents;                 // Set to true if size < 600

    Color   bgColour;                       // Background colour of applet
    Color   fgColour;                       // Foreground colour of applet
    
    Choice numPagesChoice;                  // Option menu to select max
                                            // number of pages to search
    Vector pageMatch;                       // Pages that contain the
					    // search word

    // FOR THE READ =================================================
    private static final String urlName =
       	"http://www.cpsc.ucalgary.ca/~becker/461/Asst/SearchEngine/Resources/text.bin";

    String theData;

    //..............................................................................

    public void init ()
    {
	Panel p;

	getParameters ();       // Read the applet parameters

	setLayout (new BorderLayout ());

	// If applet size is <= 600 pixels then reduce the length
	// of text fields, labels etc so that the applet will
	// display OK
	if (getSize().width <= 600)
	    packComponents = true;
	else
	    packComponents = false;
	
	// This panel consists of a input text field where the
	// user enters the text to search for. The buttons allow
	// the search to be started, aborted and clear the applet's
	// output fields.
	
	p = new Panel();
	p.setLayout (new FlowLayout());
	Label lab = new Label ("Search for: ");
	lab.setFont (new Font ("Helvetica", Font.PLAIN, 12));
	p.add (lab);
	if (packComponents)
	    inputArea = new TextField ("",15);
	else
	    inputArea = new TextField ("",20);
	p.add (inputArea);
	if (packComponents)
	    {
		search = new Button ("search");
		search.setFont (new Font ("Helvetica", Font.BOLD, 12));
	    }
	else
	    {
		search = new Button (" search ");
		search.setFont (new Font ("Helvetica", Font.BOLD, 14));
	    }
	p.add (search);
	if (packComponents)
	    {
		clear = new Button ("clear");
		clear.setFont (new Font ("Helvetica", Font.BOLD, 12));
	    }                       
	else
	    {
		clear = new Button (" clear ");
		clear.setFont (new Font ("Helvetica", Font.BOLD, 14));
	    }                       
	p.add (clear);
	if (packComponents)
	    {
		abort = new Button ("stop");
		abort.setFont (new Font ("Helvetica", Font.BOLD, 12));
	    }
	else
	    {
		abort = new Button (" stop ");
		abort.setFont (new Font ("Helvetica", Font.BOLD, 14));
	    }
	abort.setEnabled(false);
	p.add (abort);
	if (packComponents)
	    lab = new Label ("Pages");
	else
	    lab = new Label (" Max. Pages:");
	lab.setFont (new Font ("Helvetica", Font.PLAIN, 12));
	p.add (lab);
	numPagesChoice = new Choice();
	p.add (numPagesChoice);
	p.setForeground (fgColour);
	p.setBackground (bgColour);
	add ("North",p);
	
	// This panel lists the results. When an item from the list
	// box is double clicked the URL is opened up.
	p = new Panel();
	p.setLayout (new GridLayout(0,1));
	resultsArea = new List (10,false);
	p.add (resultsArea);
	p.setForeground (fgColour);
	p.setBackground (bgColour);
	add ("Center",p);
	
	p = new Panel();
	Label labVersion = new Label (versionNumber);
	labVersion.setFont (new Font ("Helvetica", Font.PLAIN, 12));
	p.add (labVersion);
	CheckboxGroup caseSense = new CheckboxGroup();
	
	// This textfield shows the status of the search to provide
	// some feedback to the user. The page count is displayed.
	if (packComponents)
	    statusArea = new TextField ("",14);
	else
	    statusArea = new TextField ("",20);
	
	statusArea.setEditable (false);
	p.add (statusArea);
	
	if (packComponents)
	    caseInsensitive = new Checkbox ("in-sensitive");
	else
	    caseInsensitive = new Checkbox ("case in-sensitive");
	
	p.add (caseInsensitive);
	caseInsensitive.setCheckboxGroup (caseSense);
	
	if (packComponents)
	    caseSensitive = new Checkbox ("sensitive" );
	else
	    caseSensitive = new Checkbox ("case sensitive" );
	
	p.add (caseSensitive);
	caseSensitive.setCheckboxGroup (caseSense);
	caseSense.setSelectedCheckbox (caseInsensitive);
	
	if (packComponents)
	    matchWholeWord = new Checkbox ("whole word");
	else
	    matchWholeWord = new Checkbox ("match whole word");
	
	p.add (matchWholeWord);
	p.setForeground (fgColour);
	p.setBackground (bgColour);
	add ("South",p);
	
	disableButtons ();      // Disable buttons until text entered
	
	// Create vector to hold pages that have been found
	// and pages that contain the search text
	pageNames = new Vector();
	pageMatch = new Vector();
	
	// Now that we know what the maxSearch parameter is fill
	// in sensible page numbers
	for (int i=maxSearch / 5; i<= maxSearch; i += maxSearch / 5)
	    {
		numPagesChoice.addItem (Integer.toString(i));   
	    }
	
	numPagesChoice.setFont (new Font ("Helvetica", Font.PLAIN, 12));
	
	// Set the default number of pages to be searched
	numPagesChoice.select (0);      
	maxSearch = maxSearch / 5;
	
	// Set the background + foreground applet colours
	//              setBackground(bgColour);
	//              setForeground(fgColour);
	
	// Always set text input field to white for readability
	inputArea.setBackground (Color.white);

	///////////////////////////////////////////////////////////////////////
	resultsArea.addItem("Here we go...");
	///////////////////////////////////////////////////////////////////////
	// OPEN AND READ DATA FILE
	try {
	    doit();
	}
	catch (Exception e) {
	}
	resultsArea.addItem("Read the file, looks like..");
	resultsArea.addItem(theData);
    }
    
    //..............................................................................
    // Function enableButtons
    // Purpose - enable use of buttons in GUI
    public void enableButtons ()
    {
	search.setEnabled(true);
	clear.setEnabled(true);
    }
    
    //..............................................................................
    // Function disableButtons
    // Purpose - disable use of buttons in GUI
    final void disableButtons ()
    {
	search.setEnabled(false);
	clear.setEnabled(false);
    }
    
    //..............................................................................
    // Function getParameters
    // Purpose - read applet parameters
    final void getParameters ()
    {
	siteRoot   = getParameter ("siteRoot");
	String num = getParameter ("maxSearch");
	String arg = getParameter ("debug");
	
	String colour = getParameter("bgColour");
	if (colour == null)
	    {
		// I wish this could be locali[sz]ed so that I could
		// write lightGrey !!
		bgColour = Color.lightGray;
	    }
	else 
	    {
		try 
		    {
			bgColour = new Color(Integer.parseInt(colour, 16));
		    }
		catch (NumberFormatException e) 
		    {
			bgColour=Color.lightGray;
			}
	    }
	
	colour = getParameter("fgColour");
	if (colour == null)
	    {
		fgColour = Color.black;
	    }
	else 
	    {
		try 
		    {
			fgColour = new Color(Integer.parseInt(colour, 16));
		    }
		catch (NumberFormatException e) 
		    {
			bgColour=Color.black;
		    }
	    }
	
	// Check for missing parameters
	if (siteRoot == null)
	    {
		statusArea.setText ("Error - no root name");
		System.out.println (" Error: No root name specified");
		siteRoot = "none";
	    }
		
	maxSearch = (num == null) ? MAX_NUMBER_PAGES : Integer.parseInt(num);
	debugMode = (arg == null) ? false : true;
	
	if (debugMode)
	    {
		System.out.println ("Site Root is " + siteRoot);
		System.out.println ("maxSearch is " + maxSearch);
		System.out.println ("debugMode is " + debugMode);
		System.out.println ("bgColour is " + bgColour);
		System.out.println ("fgColour is " + fgColour);
	    }
    }
    
    //..............................................................................
    // Display parameter information
    public String[][] getParameterInfo()
    {
	String[][] info =
	{
	    {"siteRoot","String","Root Directory of site"},
	    {"maxSearch","String","maximum number of pages to search"},
	    {"debug","String","debug mode"},
	    {"server","String","Home Page URL"},
	    {"indexName","String","Name of index page"},
	    {"bgColour","Color","Background colour of applet"},
	    {"fgColour","Color","Foreground colour of applet"}
	};
	
	return info;
    }
    
    //..............................................................................
    // Display applet information
    public String getAppletInfo()
    {
	return "Client Side Search Applet v1.0";
    }
    
    //..............................................................................
    // Function keyDown
    // Purpose - enable or disable buttons. When search text is entered
    // the search and clear buttons are enabled. When no search text has
    // been entered the buttons are disabled.
    public boolean keyDown (Event e, int nKey)
    {
	boolean boolDone = true;
	String text;
	
	text = inputArea.getText();     // Read the search text
	int n = text.length();          // Count number of chars
	
	if (nKey == BACKSPACE_CHARACTER)// catch backspace character
	    {
		boolDone = false;
		n--;
	    }
	else
	    {
		boolDone = false;
		n++;
	    }
	
	if (n > 0)
	    {
		enableButtons ();
	    }
	else
	    {
		disableButtons ();
	    }
	
	return (boolDone);
    }
    
    //..............................................................................
    // Purpose - this function handles all the GUI events
    public boolean action (Event e, Object o)
    {
	
	String text;            // Search text entered by user
	String searchText;      // Lower case version of above
	URL newURL = null;
	
	// Check to see if the option menu has been selected
	if (e.target instanceof Choice)
	    {
		Choice c = (Choice) e.target;
		try 
		    {
			maxSearch = Integer.parseInt(c.getSelectedItem(), 10);
		    }
		catch (NumberFormatException ex) 
		    {
			maxSearch = MAX_NUMBER_PAGES;
		    }
		
		if (debugMode)
		    System.out.println ("maxSearch is now " + maxSearch);
	    }
	
	// Check to see if a checkbox has been pressed
	if (e.target instanceof Checkbox)
	    {
		if (caseSensitive.getState() == true)
		    matchCase = true;
		else
		    matchCase = false;
		
		if (matchWholeWord.getState() == true)
		    matchWord = true;
		else
		    matchWord = false;
	    }
	
	// A button has been pressed - determine which
	if (e.target instanceof Button)
	    {
		if (e.target == search)
		    {
		       	// Search button pressed - read in 
		       	// search text entered
			text = inputArea.getText();
			
		       	// Make sure ther's somthing to search for
			if (text.length() == 0)
			    return (false);
			
		       	// New search so clear the GUI out
			if (resultsArea.getItemCount() > 0)
			    resultsArea.removeAll();
			
			disableButtons ();
			abort.setEnabled(true);
			statusArea.setText("");
			
		       	// Clear out previous search data
			pageNames.removeAllElements();
			pageMatch.removeAllElements();

			//++++++++++++++++++++++++++++++++++++++++++++++
			// We're off - start the search thread
			cp = new SearchData (this, siteRoot, text, theData,  maxSearch);
			cp.start();
		    }
		else if (e.target == abort)
		    {
			// Abort button pressed - stop the thread
			if (cp != null)
			    cp.requestStop();
			cp = null;
			
			// Enable buttons for another search
			enableButtons();
			abort.setEnabled(false);
		    }
		else 
		    {
			// Clear button pressed - clear all the fields
		       	// and return
			inputArea.setText("");
			statusArea.setText("");
			
		       	// Clear radio buttons
			caseSensitive.setState(false); 
			caseInsensitive.setState(true);
			matchWholeWord.setState(false);
			
			// Clear option menu
			numPagesChoice.select (0);      
			try 
			    {
				maxSearch = Integer.parseInt(numPagesChoice.getSelectedItem(), 10);
			    }
			catch (NumberFormatException ex) 
			    {
				maxSearch = MAX_NUMBER_PAGES;
			    }
			
			if (debugMode)                                  
			    System.out.println ("maxSearch is now " + maxSearch);
			
			if (resultsArea.getItemCount() > 0)
			    resultsArea.removeAll();
			
			cp = null;
		    }
	    }
	
	// Selection made from the list of matches
	if (e.target instanceof List)
	    {
		List list = (List) e.target;
		int index = list.getSelectedIndex();
		
		// Extract the page name from the list
		if (index < pageMatch.size())
		    {
			String URLSelected = (String)pageMatch.elementAt(index);          
			
			try
			    {
				newURL = new URL(siteRoot + URLSelected);
			    }
			catch(MalformedURLException excep) 
			    {
				System.out.println("action(): Bad URL: " + newURL);
			    }
			
			if (debugMode)
			    System.out.println (" Jumping to ... " + newURL.toString());
			
		       	// Display the document
			getAppletContext().showDocument(newURL,"_self");
		    }
	    }
	
	return true;                                    // We're done
    }
    
    
    //..............................................................................
    // Purpose - checks to see if a page has already been
    // visited by the search thread
    boolean checkAlreadyFound (String page)
    {
	if (pageNames.size() == 0)
	    return (false);
	
	// Check this is a new one
	for (int i=1; i < pageNames.size() ;i++)
		{
		    String pageName = (String) pageNames.elementAt(i);
		    if (pageName.equalsIgnoreCase (page))
			return (true);
		}
	
	return (false);
    }
    
    //..............................................................................
    // Purpose - adds a page visited by the search thread to
    // the list of visited pages
    // This prevents the same link from being followed if it
    // is on multiple pages.
    public void incrementPages (String page)
    {
	// Check if page already indexed
	if (checkAlreadyFound (page))
	    return;
	
	pageNames.addElement (page);
	
	// Provide feedback to the user
	statusArea.setText ("Searching page: " + pageNames.size());
    }
    
    //..............................................................................
    // Purpose - returns the number of pages that the search
    // thread has visited
    public int getTotalPages ()
    {
	return pageNames.size() - 1;
    }
    
    //..............................................................................
    // Purpose - convert special characters in the page title
    // to Unicode characters so they are displayed properly
    final protected String translateSpecialChars (String title)
    {
	int start;
	int i;
	// HTML representation of selected extended chars
	String rawString[] = {"&aacute;","&acirc;","&aelig;",
			      "&agrave;","&auml;","&ccedil;",
			      "&eacute;","&ecirc;","&egrave;",
			      "&euml;","&icirc;","&iuml;",
			      "&ocirc;","&ouml;","&szlig;",
			      "&uuml;","&yuml;","&copy;",
			      "&pound;","&reg;","&lt;",
			      "&gt;","&amp;","&quot;",
			      "&atilde;","&aring;","&igrave;",
			      "&iacute;","&eth;","&ntilde;",
			      "&ograve;","&oacute;","&otilde;",
			      "&divide;","&oslash;","&ugrave;",
			      "&uacute;","&ucirc;","&yacute;",
			      "&thorn;","&times;","&nbsp;",
			      "&sect;","&cent;","&deg;"};
	// Unicode representation of above
	char translatedChar[] = {'\u00e1','\u00e2','\u00e6',
				 '\u00e0','\u00e4','\u00e7',
				 '\u00e9','\u00ea','\u00e8',
				 '\u00eb','\u00ee','\u00ef',
				 '\u00f4','\u00f6','\u00df',
				 '\u00fc','\u00ff','\u00a9',
				 '\u00a3','\u00ae','\u003c',
				 '\u003e','\u0026','\u0022',
				 '\u00e3','\u00e5','\u00ec',
				 '\u00ed','\u00f0','\u00f1',
				 '\u00f2','\u00f3','\u00f5',
				 '\u00f7','\u00f8','\u00f9',
				 '\u00fa','\u00fb','\u00fd',
				 '\u00fe','\u00d7','\u00a0',
				 '\u00a7','\u00a2','\u00b0'};
	StringBuffer translated = new StringBuffer ("");
	String titleString = title;
	
	// Check the title for each of the above HTML special chars
	for (int loop=0; loop < NUMBER_SPECIAL_CHARS; loop++)
	    {
		if (translated.length() > 0)
		    {
			titleString = translated.toString();
			translated = new StringBuffer ("");
		    }
		
		start = titleString.indexOf (rawString[loop]);
		
		if (start != -1)
		    {
			// HTML special character found so replace it
		       	// with the Unicode equivalent for display
			for (i=0; i < start; i++)
			    translated.insert (i,titleString.charAt(i));
			
			translated.append (translatedChar[loop]);
			
			for (i=start+rawString[loop].length(); i < titleString.length(); i++)
			    translated.append (titleString.charAt(i));
			
		    }
	    }
	
	return (translated.length() == 0) ? titleString : translated.toString();
    }
    
    //..............................................................................
    // Purpose - adds a page to the list of matches in the results
    // ListBox. The page title and matching text are displayed.
    // The page name is also stored so that the URL can be jumped
    // to.
    public void addToList (String Page, String line, String title)
    {
	String translatedTitle = title;
	String translatedLine  = line;
	
	if (title.indexOf("&") != -1 &&
	    title.indexOf(";") != -1)
	    {
		// check for HTML special characters
		// e.g &quot; &ccedil; etc.
		translatedTitle = translateSpecialChars (title);
	    }       
	
	if (line.indexOf("&") != -1 &&
	    line.indexOf(";") != -1)
	    {
	       	// check for HTML special characters
		// e.g &quot; &ccedil; etc.
		translatedLine = translateSpecialChars (line);
	    }       
	
	resultsArea.addItem ("MATCH:\" " + translatedTitle + "\" TEXT: " + translatedLine);
	
	pageMatch.addElement(Page);
    }

//---------------------------------------------------------------------------
// read the data
    public void doit() throws Exception {

        try {
	    // for opening a network file
	    URL binfile = new URL( urlName );
	    // allows for binary input in blocks or by byte
	    BufferedInputStream in =
		new BufferedInputStream( binfile.openStream() );

	    int b = 0;
	    theData = "START::";
	    // read will return -1 at end-of-file
	    while( ( b = in.read() ) >= 0 ) {
		// reads in a single signed byte at a time
		// this example converts it to character to display

		theData = theData + (char)b;
		// question: what's happenning to the white space?
	    }
	    in.close();
	    theData = theData + "::END";
	    
	}
	catch( IOException e ) { System.err.println( e.toString() ); }
	    
    } // end doit   

}


//=========================================================================
//                        Class SearchData 
//=========================================================================

// This thread performs the search.
// The original was written to grab each page from the server and search it
// once the client has it. Modification uses data file instead. The data
// file will be loaded and searched by "SearchData" (formerly known as
// "SearchPages"
// It's still a thread

class SearchData extends Thread
{
    
    String siteRoot;                 // Root name of site e.g cpsc
    search2 app;                      // Parent applet
    String textToFind;               // String to find
    int maxPages;                    // Maximum number of pages to visit
    int hitsFound = 0;               // No of occurrences of search string
    static final byte URLCOUNT = 2;

    private boolean stopRequested;  // with "interrupt", replaces STOP method
    
    Vector topLevelPages;           // Page names in the index page
    Vector nextLevelPages;          // Lower level pages

    Read stuff; // THE READ-A-FILE OBJECT
    String answer; // WHERE IT PUTS WHAT IT READ
    String theData;


    //..............................................................................
    // Constructor
    SearchData (search2 applet, String hn, String text, String s, int maxSearch)
    {
	app = applet;
	siteRoot = hn;
	textToFind = text;
	theData = s;
	maxPages = maxSearch;

    }
    
    //..............................................................................
    public void run()
    {
	try {
	    app.statusArea.setText ("Searching...");
	    startSearch();
	    app.enableButtons();
	    app.abort.setEnabled(false);	    
	    if (hitsFound == 0)
		app.statusArea.setText ("No matches found");
	    else if (hitsFound == 1)
		app.statusArea.setText (hitsFound + " match found");
	    else
		app.statusArea.setText (hitsFound + " matches found");
	}
	catch (Exception e) {
	}
    }
    
    
    //..............................................................................
    final void startSearch()
    {       
	app.addToList( "Asst/SearchEngine/ClientSideSearch.html",
		       "using all literals", "page name");
	hitsFound++;

	app.addToList( "index.html",
		       "using one string", textToFind);
	hitsFound++;

	String temp = theData;

	if (theData.length() == 0)
	    app.addToList( "index.html","data string empty","====");
	else
	    app.addToList( "index.html",
			   "AND NOWl", temp);
	hitsFound++;


    }

    //..............................................................................
    public void requestStop () {
	stopRequested = true;
	interrupt();
    }
    
}




