/* @(#)ConcentrationGame.java   13 November 2006 */

/* 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.ParseException;

public class ConcentrationGame
    extends DisplayPanel
    implements WindowConstants
{
    {
        LookAndFeelTools.setNetFontSizeAdjustment(4);
    }
    
    
    /** Main panel gap. */
    private static final int maingap = 10;
    
    /** Tile panel gap. */
    private static final int tilegap = 5;
    
    
    /** The tile size. */
    private static final int edge = 100;
    
    /** The tile bounds. */
    private static XRect bounds = new XRect(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 paintable blocks for the Concentration Game
     * that are used to create the game tiles.
     */
    private Paintable[] blocks = null;
    
    /** The count of paintable blocks. */
    private int blockCount = -1;
    
    
    /**
     * The game tiles corresponding to those paintables
     * installed in the Concentration Game.
     */
    private GameTile[][] tiles = null;
    
    
    /** Selected game tile #1. */
    private GameTile tile1 = null;
    
    /** Selected game tile #2. */
    private GameTile tile2 = null;
    
    
    /** The grid size. */
    private int gridSize = -1;
    
    /** The count of tiles in the tile square. */
    private int tileCount = -1;
    
    /** The skip index for odd grid sizes. */
    private int skipIndex = -1;
    
    
    /**
     * The index list that selects each paintable
     * block an even number of times
     * to provide block pairs for the tile square.
     *
     * This array should have size = tileCount.
     */
    private int[] indexList = null;
    
    
    /** The number of guesses so far. */
    private int guesses = 0;
    
    /** The number of matches so far. */
    private int matches = 0;
    
    
    /** The game over flag. */
    private boolean gameOver = false;
    
    
    /** The action for showing or hiding tile pairs. */
    private MouseAction click =
        new MouseAction("Click Action") {
            public void mouseActionPerformed(MouseEvent mevt) {
                click(mevt);
            }
        };
    
        
    /** The action for revealing all tiles. */
    private SimpleAction revealAll =
        new SimpleAction("Reveal All") {
            public void perform() {
                revealAll();
            }
        };
    
        
    /** The progress message with guesses and matches. */
    private Annotation message = new Annotation("");
    
    
    public ConcentrationGame(Paintable[] blocks, int gridSize) {
        this.blocks = blocks;
        this.gridSize = gridSize;
        
        String message = "Cannot initialize Concentration Game\n";
        
        if (blocks == null) {
            message += "Paintable blocks array is null";
            addObject(message);
            return;
        }
        
        blockCount = blocks.length;
        
        if (blockCount == 0) {
            message += "Paintable blocks array has length 0";
            addObject(message);
            return;
        }
        
        setParameters();
        makeIndexList();
        makeGameTiles();
        makePanels();
    }
    
    
    /**
     * Set the tile count and skip index
     * based on the grid size.
     * 
     * Force grid size between 3 and 6.
     */
    private void setParameters() {
        if (gridSize < 3)
            gridSize = 3;
        else
        if (gridSize > 6)
            gridSize = 6;
       
        tileCount = gridSize * gridSize;
        
        if ((gridSize % 2) == 0) {
            skipIndex = -1;
        }
        else {
            skipIndex = gridSize / 2;
            tileCount--;
        }
    }
    
    
    /**
     * Makes a list of index values into the blocks array.
     * Each index occurs an even number of times so that
     * each block will have a matching block.
     */
    private void makeIndexList() {
        int neededBlocks= tileCount / 2;
        
        if (blockCount >= neededBlocks)
            indexList =
                ProbStatTools.selectWithNoRepetition
                    (blockCount, neededBlocks);
        else
            indexList =
                ProbStatTools.selectWithRepetition
                    (blockCount, neededBlocks);
        
        indexList = ProbStatTools.repeatData(indexList, 2);
        
        indexList = ProbStatTools.randomPermutation(indexList);
    }
    
    
    private void makeGameTiles() {
        tiles = new GameTile[gridSize][gridSize];
        
        int listIndex = 0;
        
        for (int row = 0; row < gridSize; row++)
            for (int col = 0; col < gridSize; col++) {
                if ((row == skipIndex) && (col == skipIndex))
                    continue;
                
                int index = indexList[listIndex];
                
                tiles[row][col] =
                    new GameTile
                        (blocks[index], tileBackground,
                         edge, edge, false, true);
                
                tiles[row][col].getMouseActionAdapter().
                    addMouseReleasedAction(click);
                
                listIndex++;
            }
    }
    
    
    /** Make the panels for the main GUI. */
    private void makePanels() {
        setMessage();
        
        TablePanel tilePanel = new TablePanel
            (tiles, tilegap, tilegap, CENTER);
        
        JButton button = new JButton(revealAll);
        
        Object[] mainStuff = { tilePanel, message, button };
        
        VTable mainPanel = new VTable
            (mainStuff, maingap, maingap, CENTER);
        
        mainPanel.emptyBorder(tilegap);

        tilePanel.setBackground(panelBackground);
        button.setBackground(panelBackground);
        mainPanel.setBackground(panelBackground);
        
        addObject(mainPanel);
    }
    
    
    /** 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) + "%"; 
        }
        
        message.setText(text);
    }
    
    
    /** Check for end of game and do any appropriate actions. */
    private void checkForEndOfGame() {
        if (matches >= (tileCount / 2))
            GeneralDialog.showOKDialog
                (GameText.getCongratulations(), "");
    }
    
    
    /** The click action method. */
    private synchronized void click(MouseEvent mevt) {
        GameTile tile = (GameTile) mevt.getComponent();
        
        boolean doClick = !gameOver && tile.isActive();
            
        if (doClick) {
            // make invisible the tiles that did not match
            if ((tile1 != null) && (tile2 != null)) {
                tile1.setPainted(false);
                tile2.setPainted(false);
                tile1 = null;
                tile2 = null;
            }
            
            // make the first tile visible
            if (tile1 == null) {
                tile1 = tile;
                tile1.setPainted(true);
            }
            // make the second tile visible
            else if (tile != tile1) {
                tile2 = tile;
                tile2.setPainted(true);
                
                guesses++;
                
                // check match of tile1 and tile2
                if (tile1.isEqualTo(tile2)) {
                    tile1.setActive(false);
                    tile2.setActive(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++) {
                GameTile tile = tiles[row][col];
                
                if (tile != null) {
                    tile.setActive(false);
                }
            }
        
        // then make all tiles visible
        for (int row = 0; row < gridSize; row++)
            for (int col = 0; col < gridSize; col++) {
                GameTile tile = tiles[row][col];
                
                if (tile != null) {
                    tile.setPainted(true);
                }
            }
        
        gameOver = true;
    }
    
    
    /**
     * <p>The main program actually launches and frames
     * a new ConcentrationSettings object.</p>
     * 
     * <p>The ConcentrationGame is then initialized and
     * framed via that object.</p>
     */
    public static void main(String[] args) {
        ConcentrationSettings settings =
            new ConcentrationSettings();
        
        JPTFrame frame = settings.frame("Settings", WEST);
        
        frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    
}

