import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.io.*;
import java.net.*;

// Search applet
//
// Author: Richard Everitt (G4ZFE, g4zfe@g4zfe.com)
// Last updated:   07th February 2003
// Version 1.17
// Purpose - this applet searches contest logs for a specified callsign.
// The results are then displayed.
//

/*
 * Copyright (c) 1997-2003 Richard Everitt G4ZFE
 *      g4zfe@g4zfe.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 */

 /*
  * Applet parameters
  * debug,    "true", trace information is written to the Java console
  *           "false" or not present, no trace info written
  *
  * log1Name .. log5Name, String to display next to radio button
  *
  * URL1Name .. URL5Name, URL (including directory) to read log from
  *
  * fileExtn, file extension for log files (defaults to TXT). 
  *           (not used any more)
  *
  * wildcards, determines if wildcards may be used (e.g G4* will
  *              list all G4 callsigns from the log)
  *              values = true, false (default = true)
  *
  * multiLog, determines if the logname should be displayed before
  *           each match. This is useful if the applet is used
  *           with several log files.
  *           values = true, false (default = false)
  *
  * appletFontName, the Font to use for the applet buttons and checkbox text
  *             (default = Helvetica)
  *             (e.g TimesRoman, Helvetica, Courier, Dialog, DialogInput)
  * resultFontName, the Font to use when returning search results in the search window
  *             (default = Courier)
  * resultFontSize, the size of the Font to use when returning search results in the search window
  *             (default = 12)
  * Example:
  * <applet code="search.class" width="700" height="400">
  * <param name="log1Name" value="1996 IARU">
  * <param name="log2Name" value="1996 CQ WW SSB">
  * <param name="log3Name" value="1996 CQ WW CW">
  * <param name="URL1Name" value="http://www.g4zfe.com/m6a/iaru/">
  * <param name="URL2Name" value="http://www.g4zfe.com/m6a/wwssb/">
  * <param name="URL3Name" value="http://www.g4zfe.comk/m6a/wwcw/">
  * <param name="fileExtn" value="HTM">
  * </applet>
  */


// v1.0a - working prototype. 
// v1.1. Note this applet is *still* under development!
//       Latest version at g4zfe.com/logsearch.html
//         -  tidied up code slightly
//         -  used IP address throughout to allow for use through a
//            firewall (previously had a firewall special version of
//            the applet)
// v1.2. Had to remove IP addresses as Demon have changed the address
//       and URLs containing IP addresses are no longer working
// v1.3  Added debug mode applet parameter
//       Made search a separate thread
// v1.4  Added log1Name .. log5Name and URL1Name .. URL5name parameters
//       Made applet search in a separate directory (pointed to by
//       parameter URLxName) for each contest/log-file.
//       Above changes added to generalise the applet for use by others
// v1.5  Added fileExtn parameter to allow Win 3.1 users to choose file
//       extension for log files (defaults to .txt)
//       Added wildcard facility (e.g search for K1* would show all K1s)
// v1.6  Log files on server are now expected to be a.txt to z.txt.
//       This simplifies use for win 3.1 users and makes sense as the
//       files are text files and not really html files.
//       The fileExtn parameter can be used to keep using .HTML files
//       if required - note filename must be lower case but extension
//	 can be upper case.
// v1.7  Log files can now be stored as either 0.txt-z.txt or       
//       0.TXT-Z.TXT.       
//       Increased number of contest radio buttons to 6
// v1.8  Check for 404 error from server if log file cannot be found
//       Allow fileExtn parameter to be used to override the upper case
//       then lower case filename search rule (this will increase 
//	 performance)
//       Added "stop" button.
// v1.9  If upper case filename not found then lower case used.
// v1.10 Added parameter to disable wildcard searches
//       Ability to search log files with no date and no time
//       information (for the paranoid)
//       Added parameter to display callsign of log. This is
//       useful if the applet is being used to search more
//       than one log.
// v1.11 Callsigns such as F/G4ZFE were not being accepted
//       as valid callsigns in free format log files.
//       If only one log is being searched then do not
//       display checkboxes to select log.
// v1.12 In free format log mode "J3E" and "A1A" were being
//       detected as callsigns!! Using CW and SSB were OK.
//       Allow callsign search to work with ADIF comments
//	 Ignore all HTTP headers
// v1.13 In free format log mode dates "21/10/1998" were being
//       parsed as callsigns.
// v1.14 Updated to Java 2
//       Enter key now works!
//       Allow fonts to be changed
//       Change display font to make search results readable
// v1.15 In free format log mode "PSK31" was being detected as 
//	 a callsign.
// v1.16 Allow '/' and '\' callsigns in callsigns i.e. 9m2\g4zfe and 9m2/g4zfe
//	 Added autoWildcards parameter for PA5ET
// v1.17 Allow for "AO21" and "UO14" as bands. Previously these bands were
//       interpreted as callsigns.


public class search extends Applet implements Runnable, ActionListener, ItemListener
{
	String callsign = "";           // Callsign to search for

	int count = 0;                  // Number of QSOs found

	boolean logSelected = false;    // Flag to stop search if no
					// log has been selected

	TextArea outputArea;            // Search results are displayed here
	TextField callsignField;        // Search callsign entered here

	Button search;            	// The search button
	Button clear;             	// The clear button
	Button abort;			// The stop button

	Checkbox checkbox1 = null;	// Checkboxes to select log/contest
	Checkbox checkbox2 = null;
	Checkbox checkbox3 = null;
	Checkbox checkbox4 = null;
	Checkbox checkbox5 = null;
	Checkbox checkbox6 = null;

	String log1Name = null;		// String to display beside above
	String log2Name = null;
	String log3Name = null;
	String log4Name = null;
	String log5Name = null;
	String log6Name = null;

	String URL1Name = null;		// URL to read log file from
	String URL2Name = null;
	String URL3Name = null;
	String URL4Name = null;
	String URL5Name = null;
	String URL6Name = null;

	private volatile Thread searchThread = null;	// Search thread

	// Parameters
	boolean debugMode;			// Allow debug messages to be written to the Java Console

	String fileExtn;			// File extension parameter

	boolean wildcards;			// Wildcard '*' allowed/disallowed in callsign searches

	boolean autoWildcards;			// Automatically add '*' wildcard. To be used if the callsign
						// is not at the end of the line (default)

	boolean multiLog;			// Applet works with > 1 logs

	boolean singleLog;			// Applet used for only 1 log

	String appletFontName = null;		// Font for applet button text and checkboxes text

	String resultFontName = null;		// Font for the result window

	String resultFontSize = null;		// Size of the font for the result window

	public void destroy()
	{
		searchThread = null;		// Stop the search thread
	}

	// This is where we start. Display the GUI
	public void init ()
	{
		// Read applet parameters
		getAppletParameters ();

		Panel p;
		// The GUI consists of three panels arranged using the
		// Border layout manager
		// Panel 1 - describing text + contest checkboxes
		// Panel 2 - callsign entry field + buttons
		// Panel 3 - output text area

		setLayout (new FlowLayout ());
		p = new Panel();

		if ((log1Name != null && log2Name == null) ||
	            (URL1Name != null && URL2Name == null))
		{
			// Only one log so don't bother with contest
			// checkboxes
			singleLog = true;
		}
		else
		{
			// More than one log so display contest 
			// checkboxes
			singleLog = false;
		}

		if (singleLog == false)
		{
			if (log1Name != null)
			{	
				checkbox1 = new Checkbox (" " + log1Name + " ");
				checkbox1.setFont (new Font(appletFontName, Font.BOLD, 12));
				checkbox1.addItemListener(this);
				p.add (checkbox1);
			}

			if (log2Name != null)
			{
				checkbox2 = new Checkbox (" " + log2Name + " ");
				checkbox2.setFont (new Font(appletFontName, Font.BOLD, 12));
				checkbox2.addItemListener(this);
				p.add (checkbox2);
			}

			if (log3Name != null)
			{
				checkbox3 = new Checkbox (" " + log3Name + " ");
				checkbox3.setFont (new Font(appletFontName, Font.BOLD, 12));
				checkbox3.addItemListener(this);
				p.add (checkbox3);
			}

			if (log4Name != null)
			{
				checkbox4 = new Checkbox (" " + log4Name + " ");
				checkbox4.setFont (new Font(appletFontName, Font.BOLD, 12));
				checkbox4.addItemListener(this);
				p.add (checkbox4);
			}

			if (log5Name != null)
			{
				checkbox5 = new Checkbox (" " + log5Name + " ");
				checkbox5.setFont (new Font(appletFontName, Font.BOLD, 12));
				checkbox5.addItemListener(this);
				p.add (checkbox5);
			}

			if (log6Name != null)
			{
				checkbox6 = new Checkbox (" " + log6Name + " ");
				checkbox6.setFont (new Font(appletFontName, Font.BOLD, 12));
				checkbox6.addItemListener(this);
				p.add (checkbox6);
			}

			add ("North",p);
		}

		p = new Panel();
		p.add (new Label ("Please enter callsign to search for:"));
		callsignField = new TextField ("",12);
		p.add (callsignField);

		search = new Button (" search ");
		search.setFont (new Font(appletFontName, Font.BOLD, 14));
		p.add (search);

		clear = new Button (" clear ");
		clear.setFont (new Font(appletFontName, Font.BOLD, 14));
		p.add (clear);

		abort = new Button (" stop ");
		abort.setFont (new Font(appletFontName, Font.BOLD, 14));
		p.add (abort);

		add ("Center",p);

		search.addActionListener(this);
		clear.addActionListener(this);
		abort.addActionListener(this);
		callsignField.addActionListener(this);

		p = new Panel();
		outputArea = new TextArea ("",12,60);
		outputArea.setFont (new Font(resultFontName, Font.PLAIN, Integer.valueOf(resultFontSize).intValue()));
		p.add (outputArea);

		add ("South",p);

		// Display version number
		outputArea.setText ("G4ZFE Log search applet v1.17 - 07/Feb/03");

		// Always set text input field to white for readability
		callsignField.setBackground (Color.white);
		outputArea.setBackground (Color.white);
	}

	// read in Applet parameters
	public void getAppletParameters ()
	{
		// Debug parameter
		String arg1 = getParameter ("debug");
		String arg2 = getParameter ("wildcards");
		String arg3 = getParameter ("multiLog");
		String arg4 = getParameter ("autoWildcards");

		// Strings to display beside check box
		log1Name = getParameter ("log1Name");
		log2Name = getParameter ("log2Name");
		log3Name = getParameter ("log3Name");
		log4Name = getParameter ("log4Name");
		log5Name = getParameter ("log5Name");
		log6Name = getParameter ("log6Name");

		// URL to read log from
		URL1Name = getParameter ("URL1Name");
		URL2Name = getParameter ("URL2Name");
		URL3Name = getParameter ("URL3Name");
		URL4Name = getParameter ("URL4Name");
		URL5Name = getParameter ("URL5Name");
		URL6Name = getParameter ("URL6Name");

		// File extension parameter
		fileExtn = getParameter ("fileExtn");

		// Autowildcard parameter

		debugMode = (arg1 == null) ? false : true;

		if (arg2 == null || arg2.equalsIgnoreCase ("true"))
			wildcards = true;
		else
			wildcards = false;

		if (arg3 == null || arg3.equalsIgnoreCase ("false"))
			multiLog = false;
		else
			multiLog = true;

		if (arg4 == null || arg4.equalsIgnoreCase ("false"))
			autoWildcards = false;
		else
			autoWildcards = true;

		// Font name for buttons and checkboxes
		appletFontName = getParameter ("appletFontName");
		if (appletFontName == null)
			appletFontName = "Helevetica";

		// Font name for result window
		resultFontName = getParameter ("resultFontName");
		if (resultFontName == null)
			resultFontName = "Courier";

		// Size of font for result window
		resultFontSize = getParameter ("resultFontSize");
		if (resultFontSize == null)
			resultFontSize = "12";
	}

	// Display parameter information
	public String[][] getParameterInfo()
	{
		String[][] info = 
		{
			{"debug","boolean","Write debug messages to Java Console"},
			{"log1Name","String","String to describe log 1"},
			{"log2Name","String","String to describe log 2"},
			{"log3Name","String","String to describe log 3"},
			{"log4Name","String","String to describe log 4"},
			{"log5Name","String","String to describe log 5"},
			{"log6Name","String","String to describe log 6"},
			{"URL1Name","String","Server directory for log 1"},
			{"URL2Name","String","Server directory for log 2"},
			{"URL3Name","String","Server directory for log 3"},
			{"URL4Name","String","Server directory for log 4"},
			{"URL5Name","String","Server directory for log 5"},
			{"URL6Name","String","Server directory for log 6"},
			{"fileExtn","String","file extension for server files"},
			{"wildcards","boolean","True=allow wildcards, False=disallow"},
			{"appletFontName","String","Font for buttons and checkbox text"},
			{"resultFontName","String","Font for applet result window"},
			{"appletFontSize","String","Size of font for applet result window"},
		};

		return info;
	}

	// Display applet information
	public String getAppletInfo()
	{
		return "G4ZFE search applet v1.17 - g4zfe@g4zfe.com";
	}

	// Function disableButtons
	// Purpose - grey out buttons in GUI whilst search is active
	protected void disableButtons ()
	{
		search.setEnabled(true);
		clear.setEnabled(false);
	}

	// Function enableButtons
	// Purpose - enable buttons once search has completed
	protected void enableButtons ()
	{
		search.setEnabled(true);
		clear.setEnabled(true);
	}

	public void start()
	{
	}

	public void stop()
	{
		if (searchThread != null)
		{
			// Stop the thread
			searchThread = null;
		}
	}

	public void itemStateChanged(ItemEvent check)
  	{
		if (checkbox1 != null && checkbox1.getState() == true)
		{
			logSelected = true;
			return;
		}

		if (checkbox2 != null && checkbox2.getState() == true)
		{
			logSelected = true;
			return;
		}

		if (checkbox3 != null && checkbox3.getState() == true)
		{
			logSelected = true;
			return;
		}

		if (checkbox4 != null && checkbox4.getState() == true)
		{
			logSelected = true;
			return;
		}

		if (checkbox5 != null && checkbox5.getState() == true)
		{
			logSelected = true;
			return;
		}

		if (checkbox6 != null && checkbox6.getState() == true)
		{
			logSelected = true;
			return;
		}

	}

	public void actionPerformed (ActionEvent e) 
	{
		Object arg = e.getSource();

		// Check if the stop button has been pressed
		if (arg == abort)
		{
			// Stop the thread
			searchThread = null;

                        // Enable buttons for another search
                        enableButtons();
                        abort.setEnabled(false);

                        // Reset counts etc
                        count = 0;
		}

		// Check if the clear button has been pressed
		if (arg == clear)
		{
			// Clear output area
			outputArea.setText ("");

			// Clear input area
			callsignField.setText (""); 

			// Reset all checboxes
			if (checkbox1 != null)
				checkbox1.setState (false);

			if (checkbox2 != null)
				checkbox2.setState(false);

			if (checkbox3 != null)
				checkbox3.setState(false);

			if (checkbox4 != null)
				checkbox4.setState(false);

			if (checkbox5 != null)
				checkbox5.setState(false);

			if (checkbox6 != null)
				checkbox6.setState(false);

			logSelected = false;

		}

		// Check if the search button has been pressed
		if (arg == search || arg == callsignField)
		{
			// Check that a callsign has been entered
			callsign = callsignField.getText();
			if (callsign.length() == 0)
			{
				// No - display error

				// Clear output area
				outputArea.setText ("");

				outputArea.setText ("Please enter a callsign before starting the search\n");

				// Do not start the search
				searchThread = null;
				return;
			}

			// Make sure that a log has been selected before attempting to start the search
			if (singleLog == false)
			{
				// Check that a contest has been selected
				if (logSelected == false)
				{	
					// Clear output area
					outputArea.setText ("");

					outputArea.setText ("Please select a contest/log to search\n");
					return;
				}
			}

			// We`re ready to start the search now!

			// Disable buttons until search completes
			disableButtons();
                        abort.setEnabled(true);

			// Clear the output area
			outputArea.setText ("");

			count = 0;
			outputArea.setText ("Starting search\n");
			searchThread = new Thread (this);
			searchThread.start();
		}

    	}

	public void run()
	{
		startSearch (callsign);
	}

	// This routine initiates the search in each of the contests
	void startSearch (String callsign)
	{
		Thread thisThread = Thread.currentThread();

		while (searchThread == thisThread)
		{
			if (singleLog == true)
				openURL (callsign, URL1Name, log1Name);
			else
			{
				if (checkbox1 != null && checkbox1.getState() == true)
					openURL (callsign, URL1Name, log1Name);

				if (checkbox2 != null && checkbox2.getState() == true)
					openURL (callsign, URL2Name, log2Name);

				if (checkbox3 != null && checkbox3.getState() == true)
					openURL (callsign, URL3Name, log3Name);

				if (checkbox4 != null && checkbox4.getState() == true)
					openURL (callsign, URL4Name, log4Name);

				if (checkbox5 != null && checkbox5.getState() == true)
					openURL (callsign, URL5Name, log5Name);

				if (checkbox6 != null && checkbox6.getState() == true)
					openURL (callsign, URL6Name, log6Name);
			}

			outputArea.setText(outputArea.getText() + "Search completed ... " + count + " QSOs found\n");

			// Search completed, so enable buttons
			searchThread = null;
			enableButtons();
			abort.setEnabled(false);
		}
	}

	// This routine connects to the server and obtains the contest log
	// according to the callsign. The contest log is then searched
	// in to determine if a QSO has been made
	void openURL (String call, String urlName, String logName)
	{
		String url[] = {"",""};
		int index = 0;
		boolean fileOpened = false;
		boolean posCallsignFound = false;
		BufferedReader dis = null;
		URL doc = null;
		String lowerCaseChars = "abcdefghijklmnopqrstuvwxyz";
		int callsignPosition = 0;
		int commentPosition = 0;

		// Convert callsign to upper case. The first character of the
		// callsign is used to determine which log file to open
		// i.e. look for G4ZFE in the IARU logs in file
		// G.TXT (note upper case filename)
		// This is done to reduce the size of the file to search
		// (and download time!)
		String callsign = call.toUpperCase ();
		String fname = call.toLowerCase();

		if (autoWildcards)
		{
			callsign += "*";
		}

		if (fileExtn == null)
		{
			url[0] = urlName + callsign.charAt(0) + ".TXT";
			url[1] = urlName + fname.charAt(0) + ".txt";
		}
		else
		{
			// Allow for other file extensions.
			if (lowerCaseChars.indexOf(fileExtn.charAt(0)) != -1)
			{
				// File extension begins with a lower case
				// character, so search for lower case filenames first
				url[0] = urlName + fname.charAt(0) + "." + fileExtn;
				url[1] = urlName + callsign.charAt(0) + "." + fileExtn.toUpperCase();
			}
			else
			{
				// File extension begins with an upper case
				// character, so search for upper case filenames first
				url[0] = urlName + callsign.charAt(0) + "." + fileExtn;
				url[1] = urlName + fname.charAt(0) + "." + fileExtn.toUpperCase();
			}
		}

		while (fileOpened == false && index <= 1)
		{
			if (debugMode)
				System.out.println ("Opening url " + url[index] + "\n");

			// Open connection to log file
			try
			{
				doc = new URL (url[index]);
				dis = new BufferedReader (new InputStreamReader(doc.openStream()));
			}
			catch (Exception e)
			{
				if (index == 0)
				{
					// Try lower case file names
					index++;
					continue;
				}
				else
				{
					// Connection to server could not be made
					outputArea.setText (outputArea.getText() + "Sorry .. " + logName + " log not found!\n");
					System.out.println ("openStream(): Exception: " + e + "\n");
					return;
				}
			}

			fileOpened = true;

			try
			{
				String line;
				String subString = "";

				// Read a line at a time from the log file
				while ((line = dis.readLine()) != null)
				{
					// Check for 404 File not found
					// error from server
					if (line.indexOf("HTTP") != -1)
					{
						if (line.indexOf("404") != -1)
						{
							fileOpened = false;
							index++;  
							doc = null;
							dis.close();
							dis = null;

							if (debugMode)
								System.out.println ("HTTP error: " + line + "\n");

							break;
						
						}

						// Ignore all HTTP lines
						continue;
					}

					//Ignore Apache WWW server HTTP headers
					if (line.indexOf("Date:") != -1  || line.indexOf("Server:") != -1 ||
					    line.indexOf("Last-Modified:") != -1 || line.indexOf("ETag:") != -1 ||
					    line.indexOf("Content-Length:") != -1 || line.indexOf("Accept-Ranges:") != -1 ||
					    line.indexOf("Connection:") != -1 || line.indexOf("Content-Type:") != -1)
						continue;

					// Ignore blank lines
					if (line.length() == 0)
						continue;

					try
					{
						// If this is the first time that this log
						// has been opened then determine the 
						// column position of the callsign
						if (posCallsignFound == false)
						{
							callsignPosition = callsignColumn (line);	
							posCallsignFound = true;
						}

						// Read the callsign from the log
						// If ADIF comments are being used then only read up
						// to the start of the comment
						commentPosition = line.indexOf ("**");

						if (commentPosition != -1)
							subString = line.substring(callsignPosition, commentPosition-1);
						else
							subString = line.substring(callsignPosition, line.length());

						// Trim any white space
						subString = subString.trim();

						// Check if the callsign is in this line
						searchLine (subString,
                                                            callsign,
                                                            logName,
							    line);
					}
					catch (StringIndexOutOfBoundsException eString)
					{
						if (index != 0)
						{
							// Probably an incorrectly formatted log file
							outputArea.setText (outputArea.getText() + "Incorrectly formatted log file: " + url + "\n");
							System.out.println ("subString(): Exception: " + eString + "\n");
						}
					}
				}
			}
			catch (IOException e)
			{
				// This exception may have been caused by the file not being
				// found. Try opening the lower case version of this filename
				if (index == 0)
				{
					// Try lower case file names
					fileOpened = false;
					index++;
					continue;
				}

				// Both the lower and upper case filenames could not be found.
				// This is a real problem
				outputArea.setText (outputArea.getText() + "Error whilst reading contest log file..\n");
				System.out.println ("readLine(): Exception: " + e + "\n");
				enableButtons();
				abort.setEnabled(false);
				return;
			}
		}

		if (index > 1)
			outputArea.setText (outputArea.getText() + "Sorry .. " + logName + " log not found!\n");
		else if (count == 0)
		{
			// If no QSOs have been found then say so
			if (logName != null)
				outputArea.setText (outputArea.getText() + "Sorry .. not in " + logName + " log!\n");
			else
				outputArea.setText (outputArea.getText() + "Sorry .. not in log!\n");
		}
	}

	// This routine takes a line from the log and returns
	// the column position that the callsign is in
	int callsignColumn (String line)
	{
		int i=0;
		int numberCharactersSkip=0;

		while (i <= line.length())
		{
			numberCharactersSkip = checkValidCallsign (line.substring (i, line.length()));

			if (numberCharactersSkip == 0)
			{
				if (debugMode)
				{
					System.out.println ("callsign is " + line.substring(i,line.length()) + "\n");
				}

				return (i);
			}

			i = i + numberCharactersSkip;
		}

		return 0;
	}

	// This routine takes a string and attempts to determine
	// if the string is a callsign.
	// Returns: number of chracters to skip if mode/date found. 
	// Return 0 if callsign found
	int checkValidCallsign (String text)
	{
		String upperText = text.toUpperCase();

		// Ignore dates "10/11/98"
		if (isNumeric (upperText.charAt (0)) &&
		    isNumeric (upperText.charAt (1)) &&
		    isNumeric (upperText.charAt (2)))
			return (1);

		// Ignore mode "F3E"
		if (upperText.charAt(0) == 'F' &&
	            upperText.charAt(1) == '3' &&	
	            upperText.charAt(2) == 'E' &&
	            upperText.charAt(3) == ' ')
			return (3);

		// Ignore mode "J3E"
		if (upperText.charAt(0) == 'J' &&
	            upperText.charAt(1) == '3' &&	
	            upperText.charAt(2) == 'E' &&
	            upperText.charAt(3) == ' ')
			return (3);

		// Ignore mode "A1A"
		if (upperText.charAt(0) == 'A' &&
	            upperText.charAt(1) == '1' &&	
	            upperText.charAt(2) == 'A' &&
	            upperText.charAt(3) == ' ')
			return (3);

		// Ignore mode "PSK31"
		if (upperText.charAt(0) == 'P' &&
	            upperText.charAt(1) == 'S' &&	
	            upperText.charAt(2) == 'K' &&
	            upperText.charAt(3) == '3' &&
	            upperText.charAt(4) == '1')
			return (5);

		// Ignore band "AO21"
		if (upperText.charAt(0) == 'A' &&
	            upperText.charAt(1) == 'O' &&	
	            upperText.charAt(2) == '2' &&
	            upperText.charAt(3) == '1' &&
	            upperText.charAt(4) == ' ')
			return (5);

		// Ignore band "UO14"
		if (upperText.charAt(0) == 'U' &&
	            upperText.charAt(1) == 'O' &&	
	            upperText.charAt(2) == '1' &&
	            upperText.charAt(3) == '4' &&
	            upperText.charAt(4) == ' ')
			return (5);

		// Check callsign type: 2E0ABC
		if (isNumeric (upperText.charAt (0)) &&
		    isAlpha   (upperText.charAt (1)) &&
		    isNumeric (upperText.charAt (2)))
			return (0); 

		// Check callsign type: G2AAA
		if (isAlpha   (upperText.charAt (0)) &&
		    isNumeric (upperText.charAt (1)) &&
		    isAlpha   (upperText.charAt (2)))
			return (0); 

		// Check callsign type: GM2AAA
		if (isAlpha   (upperText.charAt (0)) &&
		    isAlpha   (upperText.charAt (1)) &&
		    isNumeric (upperText.charAt (2)))
			return (0); 

		// Check callsign type: HAM4AA
		if (isAlpha (upperText.charAt (0)) &&
		    isAlpha (upperText.charAt (1)) &&
		    isAlpha (upperText.charAt (2)) &&
		    isNumeric (upperText.charAt (3)))
			return (0); 

		// Check callsign type: 3DA5AA
		if (isNumeric (upperText.charAt (0)) &&
		    isAlpha   (upperText.charAt (1)) &&
		    isAlpha   (upperText.charAt (2)) &&
		    isNumeric (upperText.charAt (3)))
			return (0); 

		// Check callsign type: H22A
		if (isAlpha   (upperText.charAt (0)) &&
		    isNumeric (upperText.charAt (1)) &&
		    isNumeric (upperText.charAt (2)) &&
		    isAlpha   (upperText.charAt (3)))
			return (0); 

		return (1);
	}

	boolean isAlpha (char ch)
	{
		if (ch >= 'A' && ch <='Z' || ch == '/' || ch == '\\')
			return true;
		else
			return false;
	}

	boolean isNumeric (char ch)
	{
		if (ch >= '0' && ch <='9' || ch == '/' || ch == '\\')
			return true;
		else
			return false;
	}

	void searchLine (String subString, String callsign, String logName, String line)
	{
		// Check for exact match of callsigns    
		if (subString.equalsIgnoreCase (callsign) == true)
		{
			if (multiLog)
				outputArea.setText (outputArea.getText() + "(" + logName + ") " + line + "\n");
			else
				outputArea.setText (outputArea.getText() + line + "\n");

			// One more QSO found
			count++;
		}

		// Check if wildcard
		int posWildcard = callsign.indexOf ("*");
		if (posWildcard != -1 && wildcards)
		{
			// Yes - do wildcard search
			String wildcard = callsign.substring(0,posWildcard);
			if (subString.startsWith(wildcard))
			{
				if (multiLog)
					outputArea.setText (outputArea.getText() + "(" + logName + ") " + line + "\n");
				else
					outputArea.setText (outputArea.getText() + line + "\n");

				// One more QSO found
				count++;
			}
		}
	}

}


