/* @(#)ConcentrationGame.java 1.0  15 November 2004 */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import javax.swing.*;

/** The class for the Concentration game. */
class ConcentrationGame implements JPTConstants
{
    
    /** The font size of the large font. */
    private static int fontSize = 72;
    
    /** The large font for the congratulations message and text based tiles. */
    private static Font largeFont = new Font("serif", Font.PLAIN, fontSize);
    
    /** The paintable for the congratulations message. */
    private static TextPaintable congratulations =
        new TextPaintable("Congratulations", largeFont);
    
    /** The height of the letter A for centering text vertically in a tile. */
    private static int heightOfA = getHeightOfA();
    
    
    /** The tile size. */
    private static int edge = 100;
    
    /** The x-center for text in a tile. */
    private static int xCenter = edge/2;
    
    /** The y-center for text in a tile. */
    private static int yCenter = (edge + heightOfA)/2;
    
    /** The tile bounds. */
    private static Rectangle2D bounds =
        new Rectangle2D.Double(0, 0, edge, edge);
    
    
    /** The tile background color. */
    private static Color tileBackground =
        Colors.burlywood;
    
    /** The panel background color. */
    private static Color panelBackground = Colors.cornflowerblue;
    
    
    /** The images obtained from the image files in the image directory. */
    /** The images obtained algorithmically from the game shapes. */
    private static Image[] shapeImages = GameShapes.getGameShapeImages();
    
    /** The images obtained from letters of the alphabet. */
    private static Image[] alphabetImages = makeAlphabetImages();
    
    /** The images obtained from numbers from 0 to 99. */
    private static Image[] numberImages = makeNumberImages();
    
    
    /** The size of the tile square. */
    private static int gridSize = 0;
    
    /** The count of tiles in the tile square. */
    private static int tileCount = 0;
    
    
    /** The radio panel for selecting photo or shape images. */
    private static RadioPanel selectImages =
        new RadioPanel(
            new String[] {
                "Use photo images for tiles",
                "Use shape images for tiles",
                "Use letters from A to Z for tiles",
                "Use numbers from 0 to 99 for tiles" },
            0 );
    
    /** The radio panel for selecting the tile grid size. */
    private static RadioPanel selectGridSize =
        new RadioPanel(
            new String[] {
                "Use 3 by 3 grid",
                "Use 4 by 4 grid",
                "Use 5 by 5 grid",
                "Use 6 by 6 grid" },
            3 );
    
    /** The enclosure for the radio panels. */
    private static TablePanel initialPanel =
        new TablePanel(
            new Object[] {
                new Display(selectImages,   null, "Select Images"   ),
                new Display(selectGridSize, null, "Select Grid Size") },
            VERTICAL, 10, 10, CENTER);
                
    
    /** The images in the current game. */
    private Image[] images = null;
    
    /** The length of the current images array. */
    private int length = 0;
    
    
    /** The image tiles corresponding to the selected images. */
    private ImageTile[][] tiles = null;
    
    
    /**
     * The image index list that selects each image an even number of times
     * to provide image pairs for the tile square.
     *
     * This array should have size = tileCount.
     */
    private int[] imageIndexList = null;
    
    
    /** The number of guesses. */
    private int guesses = 0;
    
    /** The number of matches. */
    private int matches = 0;
    
    
    /** Selected tile #1. */
    private ImageTile tile1 = null;
    
    /** Selected tile #2. */
    private ImageTile tile2 = null;
    
    
    /** The frame for the game. */
    private JPTFrame frame;
    
    /** The panel with the tiles. */
    private TablePanel tilePanel;
    
    /** The progress message with guesses and matches. */
    private Annotation message;
    
    /** The button to launch a new game. */
    private JButton newGameButton;
    
    /** The button to reveal the solution to an impatient user. */
    private JButton revealAllButton;
    
    /** The button panel. */
    private TablePanel miniPanel;
    
    /** The main enclosure panel. */
    private TablePanel mainPanel;
    
    
    /** The game over flag. */
    private boolean gameOver = false;
    
    
    /** The action for showing or hiding tile pairs. */
    private MouseAction clickAction =
        new MouseAction("Click Action") {
            public void mouseActionPerformed(MouseEvent mevt) {
                click(mevt);
            }
        };
    
    /** The action for revealing all tiles. */
    private SimpleAction revealAllAction =
        new SimpleAction("Reveal All") {
            public void perform() {
                revealAll();
            }
        };
    
    /** The action to launch a new game. */
    private SimpleAction newGameAction =
        new SimpleAction("New Game") {
            public void perform() {
                frame.dispose();
                ConcentrationGame.newGame();
            }
        };
    
    
    /** The constructor. */
    public ConcentrationGame() {
        initializeGame();
    }
    
    
    /** The initializer. */
    private void initializeGame() {
        try {
        GeneralDialog.showOKCancelDialog
        	(initialPanel, "Concentration Choices");
        }
        catch (CancelledException ex) {
            return;
        }
        
        switch (selectImages.getSelectedIndex()) {
            case 1:
                images = shapeImages;
                break;
            
            case 2:
                images = alphabetImages;
                break;
            
            case 3:
                images = numberImages;
                break;
            
            case 0:
            default:
                images = Images.getImages();
                break;
        }
        
        length = images.length;
        
        if (length == 0) {
            GeneralDialog.showOKDialog("No image tiles", "Error");
            return;
        }
        
        gridSize  = 3 + selectGridSize.getSelectedIndex();
        
        tileCount = gridSize * gridSize;
        
        if ((tileCount % 2) != 0)
            tileCount--;
        
        selectRandomizedImageIndexList();
        makeTiles();
        makePanels();
    }
    
    
    /** Selects a list of image index pairs that are then randomized. */
    private void selectRandomizedImageIndexList() {
        // select a random set of image indices
        int neededImages = tileCount / 2;
        
        if (length >= neededImages)
            imageIndexList =
                ProbStatTools.selectWithNoRepetition(length, neededImages);
        else
            imageIndexList =
                ProbStatTools.selectWithRepetition(length, neededImages);
        
        // repeat each image index twice
        imageIndexList = ProbStatTools.repeatData(imageIndexList, 2);
        
        // permute the image index list
        imageIndexList = ProbStatTools.randomPermutation(imageIndexList);
    }
    
    
    /** Makes the tiles for the game. */
    private void makeTiles() {
        tiles = new ImageTile[gridSize][gridSize];
        
        int skipIndex = -1;
        
        if ((gridSize % 2) != 0)
            skipIndex = gridSize / 2;
        
        int listIndex = 0;
        
        for (int row = 0; row < gridSize; row++)
            for (int col = 0; col < gridSize; col++) {
                if ((row == skipIndex) && (col == skipIndex))
                    continue;
                
                int imageIndex = imageIndexList[listIndex];
                
                tiles[row][col] =
                    new ImageTile
                        (images[imageIndex], tileBackground,
                         edge, edge, false, true);
                
                tiles[row][col].getMouseActionAdapter().
                    addMouseReleasedAction(clickAction);
                
                listIndex++;
            }
    }
    
    
    /** Make the panels for the main GUI. */
    private void makePanels() {
        tilePanel = new TablePanel(tiles, 5, 5, CENTER);
        
        setMessage();
        
        newGameButton   = new JButton(newGameAction);
        revealAllButton = new JButton(revealAllAction);
        
        miniPanel = new TablePanel(
            new Object[] { newGameButton, revealAllButton },
            HORIZONTAL, 40, 40, CENTER);
        
        mainPanel = new TablePanel(
            new Object[] { tilePanel, message, miniPanel },
            VERTICAL, 10, 10, CENTER);
        
        mainPanel.setMinimumColumnWidth
            (0, 4 * TextFieldView.getSampleWidth("0000000000"));
        
        tilePanel.setBackground(panelBackground);
        miniPanel.setBackground(panelBackground);
        mainPanel.setBackground(panelBackground);
        
        newGameButton.setBackground(panelBackground);
        revealAllButton.setBackground(panelBackground);
    }
    
    
    /** Check for end of game and do any appropriate actions. */
    private void checkForEndOfGame() {
        if (matches >= (tileCount / 2))
            GeneralDialog.showOKDialog(congratulations, "");
    }
    
    
    /** Set the message with guesses and matches. */
    private void setMessage() {
        String text =
            "Guesses = " + guesses + "     Matches = " + matches;
        
        if (matches> 0) {
            double ratio = (100.0 * matches) / guesses;
            
            text += "     Ratio = " + ((int) ratio) + "%"; 
        }
        
        if (message == null)
            message = new Annotation(text);
        else
            message.setText(text);
    }
    
    
    /** The click action method. */
    private synchronized void click(MouseEvent mevt) {
        ImageTile tile = (ImageTile) mevt.getComponent();
        
        boolean doClick = !gameOver && tile.isMouseActive();
            
        if (doClick) {
            // make invisible the tiles that did not match
            if ((tile1 != null) && (tile2 != null)) {
                tile1.setTileVisible(false);
                tile2.setTileVisible(false);
                tile1 = null;
                tile2 = null;
            }
            
            // make the first tile visible
            if (tile1 == null) {
                tile1 = tile;
                tile1.setTileVisible(true);
            }
            // make the second tile visible
            else if (tile != tile1) {
                tile2 = tile;
                tile2.setTileVisible(true);
                
                guesses++;
                
                // check match of tile1 and tile2
                if (tile1.isEqualTo(tile2)) {
                    tile1.setMouseActive(false);
                    tile2.setMouseActive(false);
                    
                    tile1 = null;
                    tile2 = null;
                    
                    matches++;
                }
                
                setMessage();
            
                checkForEndOfGame();
            }
        }
    }
    
    
    /** The method to reveal the solution. */
    public synchronized void revealAll() {
        // disable mouse actions first
        for (int row = 0; row < gridSize; row++)
            for (int col = 0; col < gridSize; col++) {
                ImageTile tile = tiles[row][col];
                
                if (tile != null) {
                    tile.setMouseActive(false);
                }
            }
        
        // then make all tiles visible
        for (int row = 0; row < gridSize; row++)
            for (int col = 0; col < gridSize; col++) {
                ImageTile tile = tiles[row][col];
                
                if (tile != null) {
                    tile.setTileVisible(true);
                }
            }
        
        gameOver = true;
    }
    
    
    /** Launch a new Concentration game. */
    public static void newGame() {
        ConcentrationGame game = new ConcentrationGame();
        
        if (game.length > 0)
            game.frame
            	= JPTFrame.createQuickJPTFrame
            		("Concentration", game.mainPanel, NORTH);
    }
    
    
    /** Generate the alphabet images array. */
    private static Image[] makeAlphabetImages() {
        Image[] images = new Image[26];
        
        int baseIndex = Character.getNumericValue('A');
        
        for (char c = 'A'; c <= 'Z'; c++) {
            int i = Character.getNumericValue(c) - baseIndex;
            
            images[i] = makeImageFromText("" + c);
        }
        
        return images;
    }
    
    
    /** Generate the number images array. */
    private static Image[] makeNumberImages() {
        Image[] images = new Image[100];
        
        for (int i = 0; i <= 99; i++)
            images[i] = makeImageFromText("" + i);
        
        return images;
    }
    
    
    /** Make an image from text. */
    private static Image makeImageFromText(String s) {
        TextPaintable paintable =
            new TextPaintable
                (s, largeFont, Colors.black, TextBounds.TIGHT,
                 TextAnchor.CENTER_BASELINE, xCenter, yCenter);
        
        paintable.setDefaultBounds2D(bounds);
        
        return PaintableTools.makeBufferedImage(paintable);
    }
    
    
    /** Find the text height of the letter A. */
    private static int getHeightOfA() {
        TextPaintable paintable =
            new TextPaintable
                ("A", largeFont, Colors.black, TextBounds.TIGHT,
                 TextAnchor.CENTER_BASELINE, 0, 0);
        
        Rectangle2D textbounds = paintable.getBounds2D();
        
        return (int) textbounds.getHeight();
    }
}
