/* @(#)GuessingGameTester.java 1.0  26 September 2004 */

/* Useful imports */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.jpf.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.quick.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.regex.*;

// import java.text.*;

public class GuessingGameTester extends JPF 
{
    
    public static void main(String[] args) { 
        // To optionally adjust the look and feel,
        // remove the comments from one of the two statements below.
        
        // LookAndFeelTools.showSelectLookAndFeelDialog();
        LookAndFeelTools.adjustAllDefaultFontSizes(10);
        
        new GuessingGameTester();
    }
    
    
    public void GuessingGame() {
        new GuessingGameInit();
    }
    
    
    public void TestRandomRange() {
       printRange("Random Range", new RandomRange());
    }
    
    
    public void TestUserRange() {
       printRange("User Range", new UserRange());
    }
    
    
    private void printRange(String title, Range range) {
        console.out.println(title);
        console.out.println("Minimum = " + range.getMinimum());
        console.out.println("Maximum = " + range.getMaximum());
        console.out.println();
    }
    
}


/**
 * <p>Class Range provides an integer range specified directly by its endpoints.</p>
 *
 * <p>Class Range is also the base class for building specialized range classes.</p>
 *
 * <p>A range should be immutable once construction is complete.</p>
 */
class Range {

    private int minimum = 0;
    
    private int maximum = 0;
    
    
    /**
     * <p>The public constructor that requires the range endpoints.</p>
     *
     * <p>The endpoints do not need to be ordered.</p>
     */
    public Range(int a, int b) {
        setRange(a, b);
    }
    
    
    /** Returns the range minimum. */
    public int getMinimum() { return minimum; }
    
    
    /** Returns the range maximum. */
    public int getMaximum() { return maximum; }
    
    
    /** The protected default constructor available only to derived classes. */
    protected Range() { }
    
    
    /**
     * The protected setRange method available only to this class and to
     * derived classes.
     */
    protected void setRange(int a, int b) {
        minimum = Math.min(a, b);
        maximum = Math.max(a, b);
    }
    
}


/**
 * <p>The class RandomRange provides a random range whose endpoints are between
 * 1 and Integer.MAX_VALUE = 2147483647.</p>
 */
class RandomRange extends Range {

    public RandomRange() {
        setRange(MathUtilities.randomInt(1, Integer.MAX_VALUE),
                 MathUtilities.randomInt(1, Integer.MAX_VALUE));
    }
    
}


/**
 * <p>The class UserRange provides a range specified by the user at construction.</p>
 */
class UserRange extends Range implements JPTConstants {
    
    private int a;
    private int b;
    
    private int width = TextFieldView.getSampleWidth("0000000000000000");
    
    /** The input text field for end point A. */
    private final TextFieldView endpointA = new TextFieldView("", width);
    
    /** The input text field for end point B. */
    private final TextFieldView endpointB = new TextFieldView("", width);
    
    
    /** The input panel. */
    private TablePanel inputPanel =
        new TablePanel(
            new Object[][] {
                { "End Point A", new Halo(endpointA) },
                { "End Point B", new Halo(endpointB) } },
            10, 10, WEST);
    
    
    /**
     * The action to extract the endpoints from the user data
     * and set the range.
     */
    private SimpleAction inputAction = new SimpleAction("OK") {
        public void perform() {
            a = endpointA.demandInt();
            b = endpointB.demandInt();
            setRange(a, b);
        }
    };
    
    
    /** The dialog to obtain the input. */
    private GeneralDialog inputDialog =
        new GeneralDialog
            (inputPanel, "User Defined Range", new Object[][] {{ inputAction }});
    
    
    /** The constructor. */
    public UserRange() {
        // The range will be set when the dialog closes
        // Arrange that the input action must happen no matter how dialog closes
        inputDialog.setWindowClosingAction(inputAction, DialogAction.AUTO_CLOSE);
        inputDialog.setVisible(true);
    }
    
}


/**
 * <p>The class RangeFactory permits the definition of a Range to be
 * deferred to run time choices of the user.</p>
 *
 * <p>Since this class is abstract, it must be instantiated by one or
 * more derived classes.</p>
 */
abstract class RangeFactory {

    /** Returns a range encapsulated or constructed by this factory. */
    public abstract Range makeRange();
}


/**
 * <p>The class ConstantRangeFactory is designed to return the same
 * range object with each call of its method makeRange.</p>
 */
class ConstantRangeFactory extends RangeFactory {

    private Range range;
    
    public ConstantRangeFactory(int a, int b) {
        range = new Range(a, b);
    }
    
    public Range makeRange() { return range; }
    
}


/**
 * <p>The class RandomRangeFactory is designed to return a new random
 * range object with each call of its method makeRange.</p>
 */
class RandomRangeFactory extends RangeFactory {
    
    public Range makeRange() { return new RandomRange(); }
    
}


/**
 * <p>The class UserRangeFactory is designed to return a new user defined
 * range object with each call of its method makeRange.</p>
 */
class UserRangeFactory extends RangeFactory {
    
    public Range makeRange() { return new UserRange(); }
    
}


/**
 * <p>The class GuessingGameInit displays a GUI in which the user may
 * select the range of numbers and thereby initiate a GuessingGame.</p>
 *
 * <p>This GUI remains open so that the user may start multiple games
 * and even have several games active simultaneously.</p>
 */
class GuessingGameInit implements JPTConstants {

    /** Highest power of 10 to use in the standard ranges. */
    private int limit = 6;
    
    /** The active radio button panel that is the heart of the GUI. */
    private StringObjectRadioPanel rangePanel;
    
    
    /**
     * The action to initiate a new GuessingGame whenever one of the
     * radio buttons is clicked.
     */
    private SimpleAction makeGameAction =
        new SimpleAction("Make Guessing Game") {
            public void perform() {
                makeGame();
            }
        };
    
    
    /**
     * The constructor that opens the GUI to permit a user to select
     * the initial range for a guessing game.
     */
    public GuessingGameInit() {
        // Make radio button panel
        makeRangePanel();
        
        // Open GUI frame.
        JPTFrame.createQuickJPTFrame("Make Game", rangePanel, NORTH);
    }
    
    
    /**
     * <p>This method makes the pairs consisting of the strings that will
     * be displayed in the radio button panel and the objects that will
     * be associated with the strings.</p>
     *
     * <p>Each string will be associated with a RangeFactory object.  If
     * that option is selected in the radio button panel then the factory
     * will be used to make a new range that is then used to initialize a
     * new instance of a GuessingGame.</p>
     *
     * <p>So, in short, a string in the radio button panel associates with
     * a range factory object that constructs a range object that is used
     * to construct a new guessing game.</p>
     */
    private Object[][] makePairs() {
        Object[][] pairs = new Object[limit + 3][2];
        
        // create the ranges of the form 1 to "a power of 10"
        int endpoint = 10;
        
        for (int i = 0; i < limit; i++) {
            pairs[i][0] = "1 to " + endpoint;
            pairs[i][1] = new ConstantRangeFactory(1, endpoint);
            
            endpoint *= 10;
        }
        
        // create the range 1 to 2147483647
        endpoint = Integer.MAX_VALUE;
        
        pairs[limit][0] = "1 to " + endpoint;
        pairs[limit][1] = new ConstantRangeFactory(1, endpoint);
        
        // create a random range
        pairs[limit + 1][0] = "Random Range";
        pairs[limit + 1][1] = new RandomRangeFactory();
        
        // create a user defined range
        pairs[limit + 2][0] = "User Defined Range";
        pairs[limit + 2][1] = new UserRangeFactory();
        
        return pairs;
    }
    
    
    /** Make the radio button panel using the pairs and the make game action. */
    private void makeRangePanel() {
        rangePanel = new StringObjectRadioPanel(makePairs(), makeGameAction);
    }
    
    
    /**
     * The method that instantiates the make game action by obtaining the
     * range factory associated with the selected option and using that
     * factory to make a range that is used to build a new GuessingGame.
     */
    private void makeGame() {
        RangeFactory factory = (RangeFactory) rangePanel.getSelectedObject();
        new GuessingGame(factory.makeRange());
    }
    
}


/**
 * <p>The class GuessingGame displays a GUI for the guessing game and
 * manages the game progress as the user makes guesses.</p>
 */
class GuessingGame implements JPTConstants {

    private Range current;
    
    private int value = 0;
    
    private int guess = 0;
    
    private int guessCount = 0;
    
    private int width =
        TextFieldView.getSampleWidth("00000000000000000000000000000000");
    
    private JLabel minimumView = new JLabel("");
    
    private JLabel maximumView = new JLabel("");
    
    private TextFieldView guessView = new TextFieldView("", width);
    
    private JLabel countView = new JLabel("");
    
    private JLabel commentView = new JLabel("");
    
    
    /** The action to process a user guess. */
    private SimpleAction makeGuessAction =
        new SimpleAction("Make Guess") {
            public void perform() {
                processGuess();
            }
        };
    
    
    /** The action to reveal the hidden answer to the user. */
    private SimpleAction revealAnswerAction =
        new SimpleAction("Reveal Answer") {
            public void perform() {
                revealAnswer();
            }
        };
    
    
    /**
     * The guess panel to show the current minimum, maximum, and number of
     * previous guesses plus the text field for input of the next guess.
     */
    private TablePanel guessPanel =
        new TablePanel(
            new Object[][] {
                { "Minimum", minimumView         },
                { "Maximum", maximumView         },
                { "Count",   countView           },
                { "Guess",   new Halo(guessView) } },
            10, 10, WEST);
    
    
    /**
     * The view panel that combines the guess panel, the button to trigger
     * a guess, the comment that follows the guess, and the button to show
     * the hidden answer if the user becomes frustrated.
     */
    private TablePanel viewPanel =
        new TablePanel(
            new Object[] {
                guessPanel,
                makeGuessAction,
                commentView,
                revealAnswerAction },
            VERTICAL, 10, 10, CENTER);
    
    
    /**
     * The contructor that builds the guessing game GUI initialized with
     * a range.
     */
    public GuessingGame(Range initial) {
        // Initialize state
        current = initial;
        value = MathUtilities.randomInt(current.getMinimum(), current.getMaximum());
        
        // Add action listener to the guess view
        guessView.addActionListener(makeGuessAction);
        
        // Initialize view
        resetView();
        
        // Open GUI frame
        JPTFrame.createQuickJPTFrame("Guessing Game", viewPanel);
    }
    
    
    /** Get a new guess within the range of the current minimum and maximum. */
    private int getGuess() {
        RangeFilter filter =
            new RangeFilter.Long
                (current.getMinimum(), current.getMaximum());
        
        return guessView.demandInt(filter);
    }
    
    
    /** Process the user guess. */
    private void processGuess() {
        guess = getGuess();
        guessCount++;
        resetView();
    }
    
    
    /** Reset the view depending on current state. */
    private void resetView() {
        countView.setText("" + guessCount);
        
        // Handle the first view separately
        if (guessCount == 0) {
            firstView();
            return;
        }
        
        // The "Too Low" view
        if (guess < value) {
            current = new Range(guess + 1, current.getMaximum());
            
            guessView.setViewState("");
            commentView.setText("Too Low");
        }
        
        else
        // The "Too High" view
        if (guess > value) {
            current = new Range(current.getMinimum(), guess - 1);
            
            guessView.setViewState("");
            commentView.setText("Too High");
        }
        
        else
        // The "Absolutely Correct" view
        {
            current = new Range(value, value);
            
            commentView.setText("Absolutely Correct");
            
            makeGuessAction.setEnabled(false);
        }
        
        // reset the minimum and maximum after changes to the current range
        resetMinMax();
    }
    
    
    /** Handle the first view. */
    private void firstView() {
        resetMinMax();
        commentView.setText("Make Your First Guess");
    }
    
    
    /** Reset the minimum and maximum after changes to the current range. */
    private void resetMinMax() {
        minimumView.setText("" + current.getMinimum());
        maximumView.setText("" + current.getMaximum());
    }
    
    
    /** Open a dialog to reveal the hidden number being guessed. */
    private void revealAnswer() {
        GeneralDialog.showOKDialog(new JLabel("The hidden answer is " + value), "Answer");
    }
    
}
