/*
 * GuessHooApplet.java
 *
 * Created: 22 December 2006, 21:25
 * Author: Mark Boddington
 * Description: Guess Who Game Engine Java Applet
 * License: GNU General Public License Version 2
 *
 */

// Import all required AWT classes
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.WindowListener;
import java.awt.event.WindowEvent;

// Import all required Swing classes
import javax.swing.JApplet;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JWindow;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.BoxLayout;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.ImageIcon;

// Import XML SAX classes for config file parsing
import org.xml.sax.XMLReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.DefaultHandler;

// Import misc other classes
import java.net.URL;
import java.lang.Integer;
import java.util.Random;

// Begin the GuessHooApplet class
public class GuessHooApplet extends JApplet {
    
    // BEGIN CLASS WIDE VARIABLES
    
    // The version of the engine
    private static String version = "1.0" ;
    // When true print debug information to the console
    private boolean debug = false;
    // myScreen is the main playing area.
    private myScreen screen = new myScreen();
    // Two arrays to hold players and computers persons
    private person[] player = new person[24];
    private person[] computer = new person[24];
    // Two more person objects for each players mystery card
    private person userCard;
    private person compCard = new person(99);
    // This will hold the number of different attributes used in the game
    private int datums;
    // Here we map questions and attribute IDs to strings
    private String datumMap[];
    private String question[];
    // Our XML reader for parsing the config file.
    private XMLReader xr;
    // keep track of game and turn status
    private boolean gameRunning = false;
    private boolean userTurn = false;
    // This stores the ID of the most recently asked question
    private int compQuest;
    // This stores the compQuest offset for guessing.
    private static int guessOffset = 100;
    // Switches for the playing options
    private boolean strictGuessing = true;
    private boolean attributeTips = false;
    private boolean autoTurn = false;
    // This is our only required paramater. The directory containing people.xml
    private String urlPath;
    
    // END CLASS WIDE VARIABLES
    
    /** Creates a new instance of GuessHooApplet */
    public void init() {
        
        // Check we were given a URL path, else we can't continue
        urlPath = getParameter("urlPath");
        if ( urlPath.length() == 0 ) {
            // no urlPath. Bugger!
            System.out.println("No urlpath set. GuessHoo can not run!");
            System.exit(1);
        }
        
        // Create the myScreen object, our main User Interface.
        myScreen screen = new myScreen();
        
        // Tell the screen to layout the cards.
        screen.setUpScreen(true);
        
        // Create empty people for the computer and player
        for ( int id=0; id < 24 ; id++ ) {
            player[id] = new person(id);
            screen.addPerson(player[id]);
            computer[id] = new person(id);
        }
        
        // Attempt to create the XML Reader. Catch any exception.
        try {
            xr = XMLReaderFactory.createXMLReader();
        } catch (Exception e) {
            System.out.println("Failed to instantiate XML Reader: " + e.toString() );
            System.exit(2);
        }
        
        /* Create an instance of MySAXApp and set it as the handler for xr.
         * The MySAXApp class implements the abstract SAX handler methods.
         */
        MySAXApp handler = new MySAXApp();
        xr.setContentHandler(handler);
        xr.setErrorHandler(handler);
        
        /* Attempt to parse the config file.
         * The handler (MySAXApp) will now set up all the attributes used in the
         * game, map the IDs to questions and attribute strings, and set the all
         * important datums value.
         */
        try {
            URL xmlFile = new URL( urlPath + "/people.xml");
            xr.parse(new InputSource(xmlFile.openStream()));
        } catch(Exception e) {
            System.out.println("Failed to parse XML file: " + e.toString());
            System.exit(3);
        }
        
        // Add the main playing buttons to the UI.
        screen.addButton("Start New Game");
        screen.addButton("Options");
        screen.addButton("Question");
        screen.addButton("Guess");
        
    }
    
    /* This method sets the computers mystery card values */
    public void compCard(int id){
        // create all the booleans in this persons datums array
        compCard.initDatums();
        // Set the mystery cards name (copy from his playing deck).
        compCard.setName(" " + computer[id].name + " ");
        // Set the mystery cards ID.
        compCard.id = id;
        // for all datums, copy the value from the deck to the mystery card
        for ( int index=0 ; index < datums ; index++) {
            compCard.datum[index] = computer[id].datum[index];
        }
        if (debug)
            System.out.println("compCard = " + compCard.name );
        // update the computers deck, so that it's mystery card is inactive.
        computer[id].activeCard = false;
    }
        
    /* This method implements a small sleep */
    private void sleep(int millisecs) {
        // small sleep function for any delays used in the game.
        try {
            Thread.sleep(millisecs);
        } catch (Exception e) {
            System.err.println("Failed to sleep");
        }
    }
    
    /* Return an image that is included in the java archive */
    private ImageIcon getLocalIcon(String path) {
        java.net.URL imageURL = GuessHooApplet.class.getResource(path);
        
        if (imageURL == null) {
            System.err.println("Resource not found: "
                    + path);
            return null;
        } else {
            return new ImageIcon(imageURL);
        }
    }
        
    /* Return an image from a remote resource URL */
    private ImageIcon getRemoteIcon(String path) {
        URL imageURL;
        try {
            imageURL = new URL( urlPath + path);
        } catch (Exception e ) {
            System.err.println("Resource not found: " + path);
            return null;
        }
        return new ImageIcon(imageURL);
    }
    
    public class myScreen extends JPanel implements ActionListener, WindowListener {
        
        /* This is the main UI. The game is all event driven and we make heavy
         * use of window and Action listeners in this object.
         */
        
        // lots of Swing panels to layout the screen.
        private JPanel outerPane = new JPanel();
        private JPanel playPane = new JPanel();
        private JPanel actionPane = new JPanel();
        private JPanel userPane = new JPanel();
        private JPanel statusPane = new JPanel();
        private JPanel buttonPane = new JPanel();
        private JPanel[] personRow = new JPanel[5];
        private JScrollPane playScroll;
        
        // Labels for the two main arease of the screen
        private JLabel title = new JLabel("Guess Hoo");
        private JLabel userTitle = new JLabel("Your Character");
        
        // This is the status field along the bottom, for notifying the user.
        private JTextField status = new JTextField(50);
        
        // We use this for laying out the cards on the playerPane
        private int personNum = 0;
        
        // These are the pop up windows we use
        private JFrame opts;   // options window
        private JFrame remote; // remote playing window
        private JFrame pop;    // everything else
        
        // The Return to browser button (only shown in remote window)
        private JButton RTB = new JButton("Return to Browser");
        
        // Our Psuedo Random Number Generator
        private Random rand = new Random();
        
        // constructor method for this class. Initialise the screen object
        public myScreen() {
            /* Set up all the layout managers for each panel in the UI.
             * We use BoxLayout, PAGE_AXIS lays out vertically, LINE_AXIS
             * lays out horizontally.
             */
            outerPane.setLayout(new BoxLayout(outerPane, BoxLayout.PAGE_AXIS));
            playPane.setLayout(new BoxLayout(playPane, BoxLayout.LINE_AXIS));
            actionPane.setLayout(new BoxLayout(actionPane, BoxLayout.PAGE_AXIS));
            userPane.setLayout(new BoxLayout(userPane, BoxLayout.PAGE_AXIS));
            statusPane.setLayout(new BoxLayout(statusPane, BoxLayout.LINE_AXIS));
            buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
            
            // outerPane is the root/glass pane. We add the title first.
            outerPane.add(title);
            
            /* playScroll is the main board area and holds all the playing cards
             * We set it to automagically add scroll bars when required.
             */
            playScroll = new JScrollPane(actionPane,
                    JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                    JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            
            // initialise the players mystery card
            userCard = new person(98);
            
            // Add the playpane to the outerPane, which holds the main board and
            // the players mystery card area.
            outerPane.add(playPane);
            
            // Add the scrolling board and the userPane to playPane
            playPane.add(playScroll);
            playPane.add(userPane);
            
            // set the preferred dimensions of the userPane and centre the title.
            userPane.setPreferredSize(new Dimension(130,200));
            userTitle.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            
            // We want the mystery card object centred in the userPane too.
            userCard.setAlignmentX(JButton.CENTER_ALIGNMENT);
            
            // Add the title and card to the UserPane;
            userPane.add(userTitle);
            userPane.add(userCard);
            
            // Finally add the statusPane and the buttonPane to the UI.
            outerPane.add(statusPane);
            outerPane.add(buttonPane);
            
            // Add the status text field to the statusPane
            statusPane.add(status);
            
            // Set the initial text on the statusbar and mark it un-editable.
            status.setText("Welcome to GuessHoo");
            status.setEditable(false);
            
        }
        
        public void actionPerformed(ActionEvent event) {
            // This is the Action Listner for the events
            // Get the text on the pressed button
            String arg = event.getActionCommand();
            
            /* We only use a combo box for the question list, so if it was a
             * combo box, it was a question. Set the compQuest variable to match
             * the questions index. All atts,datums,question index the same.
             * If it wasn't a combo, then it was a button or checkbox, so call
             * the performAction method
             */
            if (event.getSource().toString().contains("JComboBox")) {
                JComboBox combo = (JComboBox) event.getSource();
                compQuest = combo.getSelectedIndex();
            } else {
                performAction(true,arg);
            }
        }
        
        private void performAction(boolean alreadyInEDT, String source) {
            /* We can only update the screen in the Event-Dispatching-Thread, so if
             * we aint in it, call it. This function is overloaded. This first one
             * gets us in EDT by invokeAndWait, the other actually processes the event
             */
            if (alreadyInEDT) {
                performAction(source);
            } else {
                final String Source = source;
                //Execute a job on the event-dispatching thread:
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {
                        public void run() {
                            performAction(Source);
                        }
                    });
                } catch (Exception e) {
                    System.err.println("perfromAction didn't successfully complete");
                }
            }
        }
        
        private void performAction(String source) {
            /* We are now in the EDT, so process the action
            /* Javas switch statement is very limited, so this is basically a
             * big messy if then else block. I'll go through and name all the
             * buttons at some point to make it cleaner. I'll also refactor the
             * code too. But not today.
             */
            if ( source.equals("Question")) {
                // One of my many checks to see if the pop window is running.
                if ( pop != null ) {
                    this.updateStatus("Pop up Window is already running");
                } else {
                    // pop windows is not open, so call the ask question dialog.
                    askQuestion();
                }
            } else if (source.equals("Ask Question")) {
                // The user chose a question and clicked "Ask Question".
                // dispose of the pop window and call the process method.
                pop.dispose();
                pop = null;
                processQuestion();
                // it was the users turn, but now it's not.
                userTurn = false;
            } else if ( source.equals("Yes") || source.equals("No")) {
                // looks like the computer asked a question and we got an answer
                // dispose of the pop window and process the answer.
                pop.dispose();
                pop = null;
                processAnswer(source);
                // it's not the users turn again.
                userTurn = true;
            } else if ( source.equals("Guess")) {
                // OK the user wants to have a guess.
                if (pop != null) {
                    // pop window is open, inform the user.
                    this.updateStatus("Pop up Window is already running");
                } else if ( ! gameRunning ) {
                    // We're not playing a game???
                    this.updateStatus("Please start a game first!");
                }else if (userTurn){
                    // Ok it's the  users turn so let them guess.
                    // call the makeGuess method.
                    makeGuess();
                } else {
                    // It's not the user turn, tell them to wait.
                    this.updateStatus("Sorry, it's not your turn.");
                }
            } else if ( source.equals("Make Guess")) {
                // The user has picked a person and clicked "Make Guess"
                pop.dispose();
                pop = null;
                // Process the Guess
                processGuess();
                // it's no longer the users turn.
                userTurn=false;
            } else if (source.equals("Start New Game")) {
                // start a new game
                if ( pop != null ) {
                    this.updateStatus("Pop up Window is already running");
                } else if ( gameRunning ) {
                    // we're already playing, display the warning.
                    startGameWarning();
                } else {
                    // open the coin toss window and let them call
                    startGameDialog();
                }
            } else if (source.equals("Yes, start a new game")) {
                pop.dispose();
                pop = null;
                // We warned the user a game was running, they chose to restart
                startGameDialog();
            } else if (source.equals("Ooops, No, Cancel this")) {
                // we warned the user a game was running and they cancelled.
                pop.dispose();
                pop = null;
            }else if (source.equals("Heads") || source.equals("Tails")) {
                // The user called the coin. Pass their choice to coinCall()
                coinCall(source.toString());
            } else if (source.equals("Options")) {
                // Options button pressed, check options isn't open already.
                if (opts != null) {
                    this.updateStatus("Options Dialog is already open?");
                } else {
                    // call the optionsDialog
                    optionsDialog();
                }
            } else if (source.equals("Strict Guessing")) {
                // Toggle the Strict Guessing option
                if ( strictGuessing )
                    strictGuessing = false;
                else
                    strictGuessing = true;
            } else if (source.equals("Attribute Tips")) {
                /* Toggle the "attribute tips", tooltip popups.
                 * They are turned on by setting the tooltip on the cards to a
                 * string of the datums which are true. To turn the toolips off
                 * we set them to null.
                 */
                if ( attributeTips ) {
                    attributeTips = false;
                    for ( int x=0; x<24 ; x++) {
                        player[x].setToolTipText(null);
                    }
                    userCard.setToolTipText(null);
                } else {
                    attributeTips = true;
                    for ( int x=0; x<24 ; x++) {
                        String tip = player[x].getName();
                        for ( int y=0; y< datums ; y++ ) {
                            if ( player[x].datum[y]) {
                                tip = tip.concat(", " + datumMap[y]);
                            }
                        }
                        player[x].setToolTipText(tip);
                        player[x].repaint();
                        if ( userCard.id == x )
                            userCard.setToolTipText(tip);
                        if (debug)
                            System.out.println("player:" + x + " tip:" + tip);
                    }
                }
            } else if (source.equals("Auto Turning")) {
                // Toggle automatic card turning of the players cards.
                if ( autoTurn )
                    autoTurn = false;
                else
                    autoTurn = true;
            } else if (source.equals("About GuessHoo")) {
                // The about button was clicked. dispose of the opts window
                // call the aboutDialog method to display the applet info.
                opts.dispose();
                opts = null;
                aboutDialog();
            } else if (source.equals("Close Options")) {
                // The user clicked the close button. dispose of opts window.
                opts.dispose();
                opts = null;
            } else if (source.equals("Run in Window")) {
                // We've been asked to run in a remote window.
                // Check we're not already doing that.
                if ( remote != null ) {
                    this.updateStatus("You are already in a remote window.");
                } else {
                    // Create a new window, set its size, add listeners, etc
                    remote = new JFrame("remote");
                    remote.addWindowListener(this);
                    remote.setName("remote");
                    remote.setTitle("Guess Hoo Applet");
                    remote.setLocation(100,80);
                    remote.setSize(780,780);
                    
                    // Add the "return to browser" button
                    RTB.addActionListener(this);
                    this.buttonPane.add(RTB);
                    
                    // move the game surface into the window and make it visible.
                    remote.add(outerPane);
                    remote.setVisible(true);
                }
                // dispose of the options window.
                opts.dispose();
                opts = null;
            } else if (source.equals("Return to Browser")) {
                // The Return To Browser button was clicked.
                this.buttonPane.remove(RTB);
                // call setUpScreen to move the outerPane back to the browser
                this.setUpScreen(false);
                // dispose of the remote window
                remote.dispose();
                remote = null;
            } else {
                // We must be a person. Find out which card and toggle it
                for ( int index=0 ; index <= 23 ; index++) {
                    if ( player[index].name.equals(source)) {
                        player[index].setActive();
                        break;
                    }
                }
                
            }
        }
        
        public void setUpScreen(boolean init) {
            // when called from init(), the init var should be set to true.
            // If init is true then we create the panes for laying out the cards
            if (init) {
                for ( int row = 0 ; row <= 4 ; row++ ) {
                    personRow[row] = new JPanel();
                    personRow[row].setLayout(new BoxLayout(personRow[row], BoxLayout.LINE_AXIS));
                    actionPane.add(personRow[row]);
                }
            }
            // Add the outerPane to the rootPane and make it the glass pane.
            // getContentPane() will return the embeded frame in the browser so
            // we can use this when returning from a remote window.
            getContentPane().add(outerPane);
            setGlassPane(outerPane);
            outerPane.setVisible(true);
        }
        
        public void addButton(String name){
            // add a button to the button pane, using the string as the text.
            JButton button = new JButton();
            button.setName(name);
            button.setText(name);
            button.setVisible(true);
            button.addActionListener(this);
            buttonPane.add(button);
        }
        
        public void addPerson(person name){
            // Add the people to the 4 rows in the actionPane.
            // Six people per row
            if ( personNum < 6 ) {
                personRow[0].add(name);
            } else if ( personNum < 12 ) {
                personRow[1].add(name);
            } else if ( personNum < 18 ) {
                personRow[2].add(name);
            } else {
                personRow[3].add(name);
            }
            personNum++;
            name.addActionListener(this);
        }
        
        public void addCard(int id){
            /* Set up the players mystery card, by copying the attributes
             * from the playing cards, based on the id. */
            userCard.initDatums();
            userCard.activeIcon = player[id].activeIcon;
            userCard.setName(player[id].name);
            userCard.id = id;
            String tip = userCard.name;
            
            // copy the datums & generate the tooltip string, if attributeTips
            // are enabled.
            for ( int index=0 ; index < datums ; index++) {
                if ( attributeTips && player[id].datum[index] )
                    tip = tip.concat(", " + datumMap[index] );
                userCard.datum[index] = player[id].datum[index];
            }
            
            // if attributeTips are on then set call setToolTipText.
            if ( attributeTips)
                userCard.setToolTipText(tip);
            
            // Ensure the card is active, so we show the correct Icon.
            if ( ! userCard.activeCard )
                userCard.setActive();
            
            // flip the card over to force a repaint of the new face.
            userCard.setActive();
            userCard.setActive();
            if (debug)
                System.out.println("userCard = " + userCard.name );
        }
        
        // Update the status bar to display the given string value
        public void updateStatus(String value) {
            status.setText(value);
        }
                
        private void startGameDialog() {
            // display the startGameDialog()
            JPanel Butts = new JPanel();
            JPanel Panel = new JPanel();
            JLabel Title = new JLabel("Start New Game");
            JLabel Text = new JLabel("");
            
            // we add a window listener so we know if the users closes the
            // window and dispose of the window properly.
            pop = new JFrame("Start Game");
            pop.addWindowListener(this);
            pop.setName("pop");
            
            // buildPanel is used to layout most of our windows.
            JPanel popPanel = buildPanel(Title, Panel, Text, Butts);
            pop.add(popPanel);
            
            // Create the cointoss and the heads/tails buttons.
            JLabel Toss = new JLabel();
            JButton heads = new JButton("Heads");
            JButton tails = new JButton("Tails");
            ImageIcon toss;
            
            // reset all the cards to active (face up).
            status.setText("Restarting Game. All Cards Reset!");
            for (int index=0 ; index <= 23 ; index++ ) {
                player[index].resetActive();
                computer[index].resetActive();
            }
            
            // Get the cointoss image from archive
            toss = getLocalIcon("images/toss.gif");
            
            // add listeners to the heads and tails buttons
            heads.addActionListener(this);
            tails.addActionListener(this);
            
            // Add the image to the Toss Label.
            Toss.setIcon(toss);
            
            // Add the objects to their correct panels in the window.
            Panel.add(Toss);
            Butts.add(heads);
            Butts.add(tails);
            
            Text.setText("You must pick heads or tails to decide who goes first");
            
            // set the location, size and display the window.
            pop.setLocation(100,100);
            pop.setSize(500,300);
            pop.setVisible(true);
        }
        
        private void coinCall(String call) {
            String coin;
            int firstCard;
            int secondCard;
            
            // close the popup window
            pop.dispose();
            pop=null;
            
            // the game is now running
            gameRunning = true;
            // nextint(1) returns crappy results. Always heads!?!?
            if ( ( rand.nextInt() % 2 ) == 0 ) {
                coin = "Heads";
            } else {
                coin = "Tails";
            }
            
            // pick two different cards from the pack
            firstCard = rand.nextInt(24);
            secondCard = rand.nextInt(24);
            while ( firstCard == secondCard ) {
                secondCard = rand.nextInt(24);
            }
            
            if (debug)
                System.out.println(firstCard + ":" + secondCard);
            
            if ( coin.equals(call)) {
                // The player called correctly, they go first.
                userTurn = true;
                // player gets the first card too
                this.addCard( firstCard );
                // comp gets second card
                compCard( secondCard );
                // display the outcome in the status bar
                this.updateStatus("You called " + call + ". The coin was " + coin +
                        ". You picked first. It's your turn." );
            } else {
                // the player called wrong, the computer goes first
                userTurn = false;
                // computer gets the first card
                compCard( firstCard );
                // player gets the second card
                this.addCard( secondCard );
                this.updateStatus("You called " + call + ". The coin was " + coin +
                        ". You picked second. It's the computers turn." );
            }
            
        }
        
        private void optionsDialog() {
            // Create the options dialog
            opts = new JFrame("Options");
            opts.addWindowListener(this);
            opts.setName("opts");
            
            // Create all the buttons and checkboxes
            JPanel optsPanel = new JPanel();
            JButton optsRemote = new JButton("Run in Window");
            JButton optsAbout = new JButton("About GuessHoo");
            JButton optsClose = new JButton("Close Options");
            JCheckBox sg = new JCheckBox("Strict Guessing");
            JCheckBox tt = new JCheckBox("Attribute Tips");
            JCheckBox at = new JCheckBox("Auto Turning");
            Dimension butts = new Dimension(170,25);
            
            // Add the layout manager for this window
            optsPanel.setLayout(new BoxLayout(optsPanel,BoxLayout.PAGE_AXIS));
            optsPanel.setAlignmentX(JLabel.LEFT_ALIGNMENT);
            
            optsRemote.addActionListener(this);
            optsAbout.addActionListener(this);
            optsClose.addActionListener(this);
            opts.add(optsPanel);
            
            // Size the buttons, all the same dimensions
            optsRemote.setMaximumSize(butts);
            optsRemote.setPreferredSize(butts);
            optsAbout.setMaximumSize(butts);
            optsAbout.setPreferredSize(butts);
            optsClose.setMaximumSize(butts);
            optsClose.setPreferredSize(butts);
            
            // turn on options which are set
            if ( strictGuessing )
                sg.setSelected(true);
            if ( attributeTips )
                tt.setSelected(true);
            if ( autoTurn )
                at.setSelected(true);
            
            // add a listener for changes
            sg.addActionListener(this);
            tt.addActionListener(this);
            at.addActionListener(this);
            
            // Add everything to the window
            optsPanel.add(new JLabel("Remote Window"));
            optsPanel.add(optsRemote);
            optsPanel.add(Box.createRigidArea(new Dimension(5,10)));
            optsPanel.add(new JLabel("Options"));
            optsPanel.add(sg);
            optsPanel.add(tt);
            optsPanel.add(at);
            optsPanel.add(Box.createRigidArea(new Dimension(5,10)));
            optsPanel.add(new JLabel("Information"));
            optsPanel.add(optsAbout);
            optsPanel.add(Box.createRigidArea(new Dimension(5,10)));
            optsPanel.add(new JLabel("Close Options"));
            optsPanel.add(optsClose);
            
            // Display the window
            opts.setSize(200,300);
            opts.setLocation(100,100);
            opts.setVisible(true);
        }
        
        private void aboutDialog() {
            // Create all the UI componenents for the about dialog
            JPanel Butts = new JPanel();
            JPanel Panel = new JPanel();
            JLabel Title = new JLabel("About GuessHoo");
            JLabel Text = new JLabel("");
            pop = new JFrame("About");
            pop.addWindowListener(this);
            pop.setName("pop");
            
            // Stitch it together with buildPanel()
            JPanel popPanel = buildPanel(Title, Panel, Text, Butts);
            pop.add(popPanel);
            
            // Size it based on the appletinfo content
            JTextArea about = new JTextArea(getAppletInfo());
            about.setRows( about.getLineCount() + 1 );
            about.setColumns(28);
            about.setEditable(false);
            Panel.add(about);
            
            pop.setLocation(100,100);
            pop.setSize(350,( 100 + ( about.getRows() * 12 )));
            pop.setVisible(true);
            
        }
        private void startGameWarning() {
            // Create UI components for this window
            JPanel Butts = new JPanel();
            JPanel Panel = new JPanel();
            JLabel Title = new JLabel("Warning a game is already in progress");
            JLabel Text = new JLabel("Are you sure you want to restart the game?");
            pop = new JFrame("Start Game");
            pop.addWindowListener(this);
            pop.setName("pop");
            // Stitch it together
            JPanel popPanel = buildPanel(Title, Panel, Text, Butts);
            pop.add(popPanel);
            
            // Add the yes,no buttons
            JButton warnYes = new JButton("Yes, start a new game");
            JButton warnNo = new JButton("Ooops, No, Cancel this");
            // Add listeners to the bnuttons
            warnYes.addActionListener(this);
            warnNo.addActionListener(this);
            // Add buttons to the panel in the window
            Butts.add(warnYes);
            Butts.add(warnNo);
            
            // size and display the window
            pop.setLocation(100,100);
            pop.setSize(500,150);
            pop.setVisible(true);
            
        }
        
        public void pickBestQuestion() {
            // Determine the best question for the computer to ask
            
            // Create an array of integers to hold the number of active cards
            // with each available attribute
            int actDatum[] = new int[datums];
            int most=0;
            int active=0;
            
            // for each active card, increment the integer array for that
            // attribute and the active card count.
            for (int x=0; x<24 ; x++){
                if (computer[x].activeCard) {
                    active++;
                    for ( int y=0; y<datums ; y++) {
                        if (computer[x].datum[y])
                            actDatum[y]++;
                    }
                }
            }
            
            if (debug)
                System.out.println("comp has " + active + " cards active");
            
            // if we only have one card left active, make a guess.
            if (active == 1) {
                for (int x=0; x<24 ; x++){
                    if (computer[x].activeCard) {
                        compQuest = guessOffset + x;
                        if (debug)
                            System.out.println("Computer guesses: " + computer[x].getName());
                    }
                }
            } 
            // else, find the attribute most prevalent and set the compQuest to
            // it's ID.
            else {
                for ( int x=0; x<datums; x++) {
                    if (actDatum[x] > most && actDatum[x] < active) {
                        compQuest = x;
                        most = actDatum[x];
                    }
                }
            }
            
            // compQuest now either holds a question or a guess. If compQuest is
            // greater than/equal to guessOffset, then we're guessing.
            
        }
        
        public void processAnswer(String answer) {
            // Process the yes,no answer from the user
            if ( answer.equals("Yes")) {
                if (compQuest >= guessOffset ) {
                    // we guessed and got it right. We win
                    this.updateStatus("The computer wins. Bad Luck!");
                    gameRunning = false;
                } else {
                    // find all cards that were false for our question and mark
                    // them inactive
                    for ( int i = 0 ; i < 24 ; i++ ) {
                        if ( computer[i].activeCard) {
                            if ( ! computer[i].datum[compQuest])
                                computer[i].activeCard = false;
                        }
                    }
                }
            } else {
                if (compQuest >= guessOffset ) {
                    // we guessed and got it wrong. we lose?
                    this.updateStatus("Hmmm, the computer got it wrong? You win!");
                    gameRunning = false;
                } else {
                    // find all cards that were true for our question and mark
                    // them inactive
                    for ( int i = 0 ; i < 24 ; i++ ) {
                        if ( computer[i].activeCard) {
                            if ( computer[i].datum[compQuest])
                                computer[i].activeCard = false;
                        }
                    }
                }
            }
        }
        
        private void askQuestion() {
            // The user clicked the question button. Display the question dialog
            // if it's the computers turn, ask a question, else let the user ask
            if ( ! gameRunning ) {
                // no game running, tell the user to start one
                this.updateStatus("Please start a game first!");
                return;
            }
            
            JPanel Butts = new JPanel();
            JPanel Panel = new JPanel();
            JLabel Title = new JLabel("Ask a Question");
            JLabel Text = new JLabel("Select a question from the list above");
            
            pop = new JFrame("Question");
            pop.addWindowListener(this);
            pop.setName("pop");
            
            JPanel popPanel = buildPanel(Title, Panel, Text, Butts);
            pop.add(popPanel);
            
            if ( userTurn ) {
                // it's the users turn add the combobox and fill it with the
                // questions. And add the "Ask Question" button.
                JComboBox qList = new JComboBox();
                JButton ask = new JButton("Ask Question");
                for ( int i=0; i< datums ; i++ ) {
                    qList.addItem(makeObj(question[i]));
                }
                qList.setSelectedIndex(0);
                compQuest = 0;
                qList.setEditable(false);
                qList.setSize(400,20);
                qList.addActionListener(this);
                ask.addActionListener(this);
                Panel.add(new JLabel("Question: "));
                Panel.add(qList);
                Butts.add(ask);
            } else {
                // computers turn. Get the best question and ask it. Add Yes/No
                // buttons to the window
                JLabel qText = new JLabel();
                JButton yes = new JButton("Yes");
                JButton no = new JButton("No");
                
                Title.setText("The Computer asks:");
                pickBestQuestion();
                
                yes.addActionListener(this);
                no.addActionListener(this);
                if (compQuest >= guessOffset ) {
                    // computer is going to guess
                    qText.setText("Is your person \"" + computer[ compQuest - guessOffset ].getName() + "\"?");
                } else {
                    // ask the question
                    qText.setText(question[compQuest]);
                }
                Text.setText("");
                Panel.add(qText);
                Butts.add(yes);
                Butts.add(no);
            }
            
            pop.setLocation(100,100);
            pop.setSize(500,200);
            pop.setVisible(true);
        }
        
        private void processQuestion() {
            // The user asked a question, update the status pane with the answer
            if ( compCard.datum[compQuest] ) {
                // compCard attribute was true, so the answer is yes.
                this.updateStatus("You asked \"" + question[compQuest] + "\". The computer said \"Yes\".");
                if (debug)
                    System.out.println("You asked: " + question[compQuest] + ". Computer says: Yes");
                // If autoturning is on, turn the players cards for them
                if (autoTurn) {
                    for (int i=0; i<24 ; i++) {
                        if ( ( ! player[i].datum[compQuest] ) && player[i].activeCard)
                            player[i].setActive();
                    }
                }
            } else {
                // The compCards attribute was false. The answer is no. 
                this.updateStatus("You asked \"" + question[compQuest] + "\". The computer said \"No\".");
                if (debug)
                    System.out.println("You asked: " + question[compQuest] + ". Computer says: No");
                // Turn the players cards if autoTurning is enabled.
                if (autoTurn) {
                    for (int i=0; i<24 ; i++) {
                        if ( player[i].datum[compQuest] && player[i].activeCard)
                            player[i].setActive();
                    }
                }
            }
        }
        
        private void processGuess() {
            // The user has guessed.
            if ( compCard.id == compQuest ) {
                // and won
                this.updateStatus("Congratulations! You have won!");
                gameRunning = false;
            } else {
                // the user guessed wrongly, if strict guessing is on, then they
                // just lost. Otherwise let them have another go.
                if ( strictGuessing ) {
                    this.updateStatus("You have chosen incorrectly. You Lose!");
                    gameRunning = false;
                } else {
                    this.updateStatus("You have chosen incorrectly. Try again!");
                }
                
            }
        }
        
        private void makeGuess() {
            // the user wants to have a guess
            if ( ! gameRunning ) {
                // no game running, tell them to start one first.
                this.updateStatus("Please start a game first!");
                return;
            }
            
            // create window componenets
            JPanel Butts = new JPanel();
            JPanel Panel = new JPanel();
            JLabel Title = new JLabel("Make a Guess");
            JLabel Text = new JLabel("Select a name from the list above");
            
            pop = new JFrame("Guess");
            pop.addWindowListener(this);
            pop.setName("pop");
            
            JPanel popPanel = buildPanel(Title, Panel, Text, Butts);
            pop.add(popPanel);
            
            // add combobox containing the characters names.
            JComboBox qList = new JComboBox();
            JButton ask = new JButton("Make Guess");
            for ( int i=0; i< 24 ; i++ ) {
                qList.addItem(makeObj(computer[i].getName()));
            }
            // Set the selected name and compQuest to the first card.
            qList.setSelectedIndex(0);
            compQuest = 0;
            qList.setEditable(false);
            qList.setSize(400,20);
            
            // Add a listener for the selection.
            qList.addActionListener(this);
            
            Panel.add(new JLabel("Person: "));
            Panel.add(qList);
            
            // Add and listen for the ask button being pushed.
            ask.addActionListener(this);
            Butts.add(ask);
            
            // Display the window
            pop.setLocation(100,100);
            pop.setSize(500,300);
            pop.setVisible(true);
        }
        
        private JPanel buildPanel(JLabel title, JPanel panel, JLabel text, JPanel butts) {
            // Layout and return a window. Most of the popups use this for basic
            // formatting and layout of their componenets
            JPanel myPanel = new JPanel();
            
            myPanel.setAlignmentX(JPanel.CENTER_ALIGNMENT);
            text.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            title.setAlignmentX(JLabel.CENTER_ALIGNMENT);
            
            myPanel.setLayout(new BoxLayout(myPanel,BoxLayout.PAGE_AXIS));
            myPanel.add(title);
            myPanel.add(panel);
            myPanel.add(text);
            myPanel.add(butts);
            return myPanel;
        }
        
        private Object makeObj(final String item)  {
            // Basically create an object from a string. We need this for our
            // combo boxes, as they require objects.
            return new Object() { public String toString() { return item; } };
        }
        
        // implement all the abstract methods required by WindowListener
        public void windowOpened(WindowEvent e) {
        }
        public void windowClosing(WindowEvent e) {
            // Closing means the user closed the window, without using one of
            // our buttons. Check which window it was and dispose of it.
            JFrame source = (JFrame) e.getWindow();
            if ( "pop".equals(source.getName()) ) {
                // pop up window was closed;
                pop.dispose();
                pop = null;
            } else if ( "remote".equals(source.getName())) {
                // remote window was closed. We need to remove the "RTB" button
                // and set the browser applet frame as our root pane.
                remote.dispose();
                remote = null;
                this.buttonPane.remove(RTB);
                this.setUpScreen(false);
            } else if ( "opts".equals(source.getName())) {
                // options window closed
                opts.dispose();
                opts = null;
            }
        }
        public void windowClosed(WindowEvent e) {
        }
        public void windowIconified(WindowEvent e) {
        }
        public void windowDeiconified(WindowEvent e) {
        }
        public void windowActivated(WindowEvent e) {
        }
        public void windowDeactivated(WindowEvent e) {
        }
        
    }
    
    private class person extends JButton {
        // All of our cards are JButtons with a few extra attributes.
        // both the computer and player hold and array of person objects.
        public String name;
        public int id;
        public boolean datum[];
        public ImageIcon activeIcon;
        private ImageIcon inactiveIcon;
        public boolean activeCard;
        
        // constructor for the person object
        public person(int myid) {
            // set our ID, our initila name, and basic alignment options.
            // Also get the inactive icon from the archive.
            id = myid;
            this.setText("????");
            this.setVerticalTextPosition(this.BOTTOM);
            this.setHorizontalTextPosition(this.CENTER);
            this.setMargin(new Insets(2,2,2,2));
            inactiveIcon = getLocalIcon("images/backing.jpg");
            this.setIcon(inactiveIcon);
            activeCard=false;
        }
        
        public void initDatums(){
            // Because we were initialised before the config file was read, we
            // didn't know how many datums there were. This is called when we
            // find out to initialise the datum array.
            this.datum = new boolean[datums];
            for ( int index=0; index < datums ; index++)
                datum[index] = false;
        }
        
        public void addImage(String path) {
            // get our active image from the path specified in the config file
            activeIcon = getRemoteIcon(path);
        }
        
        public void setActive() {
            // Toggle whether this card is active or not and set the appropriate
            // image.
            if ( activeCard ) {
                this.setIcon(inactiveIcon);
                activeCard = false;
            } else {
                this.setIcon(activeIcon);
                activeCard = true;
            }
        }
        
        private void resetActive() {
            // Set this person to active.
            activeCard = true;
            this.setIcon(activeIcon);
        }
        
        public void setName(String name) {
            // Set our object name and text to be the given name.
            super.setName(name);
            this.setText(name);
            this.name = name;
        }
        
        public String getName() {
            // Return our name. Useful before I made all the attributes public
            return this.name;
        }
        
        // The following function is overloaded. We need to be in the event
        // dispatching thread to update the screen. Do I actually use this?
        private void updateDisplay(boolean alreadyInEDT) {
            if (alreadyInEDT) {
                // we're in the EDT, updateDisplay
                updateDisplay();
            } else {
                //Execute a job on the event-dispatching thread:
                try {
                    SwingUtilities.invokeAndWait(new Runnable() {
                        public void run() {
                            updateDisplay();
                        }
                    });
                } catch (Exception e) {
                    System.err.println("updateDisplay didn't successfully complete");
                }
            }
        }
        
        //Invoke this method ONLY from the event-dispatching thread.
        private void updateDisplay() {
            this.repaint();
        }
        
    }
    
    public class MySAXApp extends DefaultHandler {
        
        /* This is the SAX handler for the XML parser. It's event driven, so
         * when the parser encounters a tag it calls one if the methods implemented
         * in here. Eg. StartElement, EndElement, characters, etc...
         */
        
        private int PersonID = new Integer(-1);
        private int datumID = new Integer(-1);
        private boolean insidePerson = false;
        private boolean insideQuestion = false;
        private boolean insideMap = false;
        private boolean insideName = false;
        private boolean insideImage = false;
        private boolean insideDatum = false;
        private String content = new String();
        
        public MySAXApp() {
            super();
        }
        
        public void startDocument() {
            if (debug)
                System.out.println("Start document");
        }
        
        
        public void endDocument() {
            if (debug)
                System.out.println("End document");
        }
        
        
        public void startElement(String uri, String name,
                String qName, Attributes atts) {
            // In here we set one of our booleans to show which element we are
            // processing. When we hit the end tag we use that to apply the 
            // content to the correct data structure.
            if (debug)
                System.out.println("Start element: " + qName);
            if ("GuessHooPeople".equals(qName)) {
                // The opening GuessHooPeople tag has an attribute which sets 
                // the number of attributes (datums) in this game.
                datums = Integer.parseInt(atts.getValue("datums"));
                // create the datumMap and question arrays.
                datumMap = new String[datums];
                question = new String[datums];
            } else if ("Person".equals(qName)) {
                // we are processing a person element. Initialise its datums &
                // get its ID.
                PersonID = Integer.parseInt(atts.getValue("id"));
                if (debug)
                    System.out.println("Current person is " + PersonID );
                player[PersonID].initDatums();
                computer[PersonID].initDatums();
                // make sure endelement knows we're in a person
                insidePerson = true;
            } else if ("Questions".equals(qName)) {
                // make sure endelement knows we're in a question
                insideQuestion = true;
            } else if ("DatumMap".equals(qName)) {
                // make sure endelement knows we're in the datumMap
                insideMap = true;
            } else if ( "datum".equals(qName) ) {
                // we are a datum inside one of the main elements. Get its ID.
                datumID = Integer.parseInt(atts.getValue("id"));
                if (debug)
                    System.out.println("Datum ID: " + datumID);
                insideDatum = true;
            } else if ("name".equals(qName)) {
                // ensure endelement knows this is a name
                insideName = true;
            } else if ("image".equals(qName)) {
                // ensure endelement knows this is an image
                insideImage = true;
            }
        }
        
        
        public void endElement(String uri, String name, String qName) {
            
            if ( insidePerson ) {
                // we are inside a person
                if (insideName) {
                    // we just processed a name. content will contain the name.
                    player[PersonID].setName(content);
                    computer[PersonID].setName(content);
                    if (debug)
                        System.out.println("Player Name set: " + content);
                } else if ( insideImage ) {
                    // we processed an image, content will contain the path
                    player[PersonID].addImage(content);
                    if (debug)
                        System.out.println("Player Image set: " + content);
                } else if ( insideDatum && content.equals("true") ) {
                    //we are a datum, that's set to true, modify the persons
                    player[PersonID].datum[datumID] = true;
                    computer[PersonID].datum[datumID] = true;
                    if (debug)
                        System.out.println("Datum set: Player[" + PersonID + "]: Datum [" + datumID + "]");
                }
            } else if ( insideDatum && insideMap ) {
                // we are inside a datum for the datumMap
                datumMap[datumID] = new String(content);
                if (debug)
                    System.out.println("Datum Mapping set: [" + datumID + "] " + content);
            } else if ( insideDatum && insideQuestion ) {
                // we are inside a datum for the Questions
                question[datumID] = new String(content);
                if (debug)
                    System.out.println("Question set: " + question[datumID] );
            }
            
            // empty the content string. Ready for the next element
            content="";
            
            // Which ever element we just left, ensure we set the boolean for
            // it to false, we obviously aren't in it anymore.
            if (debug)
                System.out.println("End element: " + qName);
            if ("Person".equals(qName)) {
                insidePerson = false;
            } else if ("Questions".equals(qName)) {
                insideQuestion = false;
            } else if ("DatumMap".equals(qName)) {
                insideMap = false;
            } else if ("name".equals(qName)) {
                insideName = false;
            } else if ("image".equals(qName)) {
                insideImage = false;
            } else if ("datum".equals(qName)) {
                insideDatum = false;
            }
        }
        
        
        public void characters(char ch[], int start, int length) {
            // process the characters inside whichever element we are in and
            // add them to the content string. They will then be processed, by
            // endelement. When we hit the closing tag.
            for (int i = start; i < start + length; i++) {
                switch (ch[i]) {
                    case '\\':
                        //System.out.print("\\\\");
                        break;
                    case '"':
                        //System.out.print("\\\"");
                        break;
                    case '\n':
                        //System.out.print("\\n");
                        break;
                    case '\r':
                        //System.out.print("\\r");
                        break;
                    case '\t':
                        //System.out.print("\\t");
                        break;
                    default:
                        content = content + ch[i];
                        //System.out.print(ch[i]);
                        break;
                }
            }
            if (debug)
                System.out.println("content was: " + content );
            
            
        }
        
    }
    
    public String getAppletInfo() {
        // Return the applet information to the caller.
        return "The GuessHoo Engine\n"
                + "Version: " + this.version + "\n"
                + "Author: Mark Boddington\n"
                + "License: GNU General Public License (version 2)\n"
                + "WebSite: http://www.badpenguin.co.uk\n"
                + "Description: Guess Who Game Engine Applet\n";
    }
        
    public void start() {
        
    }
    
    public void stop() {
        
    }
    
}
