/* @(#)FifteenPuzzle.java   9 October 2008 */

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 FifteenPuzzle
    extends DisplayPanel
{
    /////////////////
    // Static Data //
    /////////////////
    
    // Colors //
    
    /** The tile color. */
    public static final Color tileColor = Colors.burlywood;
    
    /** The puzzle background color. */
    public static final Color puzzleColor = Colors.black;
    
    /** The panel background color. */
    public static final Color panelColor = Colors.white;
    
    // Measurements and numbers //
    
    /** The size of a block and a tile box. */
    public static final int blockSize = 100;
    
    /** The bounds of a block and a tile box. */
    public static final XRect bounds =
        new XRect(0, 0, blockSize, blockSize);
    
    /** Half of the block size. */
    public static final int halfSize = blockSize / 2;
    
    /** The gap between tiles. */
    public static final int gap = 2;
    
    /** The spacing between tiles = blockSize + gap. */
    public static final int spacing = blockSize + gap;
    
    /** The gap between panel items. */
    public static final int panelGap = 20;
    
    /** The number of tiles on each side of the puzzle square. */
    public static final int size = 4;
    
    /** The count of puzzle pieces = size * size - 1. */
    public static final int count = size * size - 1;
    
    // The static blank block //
    
    /** The blank block. */
    protected static Paintable blank = null;
    
    
    ///////////////////////////
    // Static Initialization //
    ///////////////////////////
    
    static { makeBlank(); }
    
    
    /////////////////
    // Member Data //
    /////////////////
    
    // Game State //
    
    /** The puzzle pieces. */
    protected Paintable[] pieces = new Paintable[count];
    
    /** Row for blank. */
    protected int r = size - 1;
    
    /** Col for blank. */
    protected int c = size - 1;
    
    // Game Board //
    
    /** The tiles on the game board. */
    protected TileBox[][] tiles = new TileBox[size][size];
    
    /** The paintable sequence with the tiles on the game board. */
    protected PaintableSequence sequence = new PaintableSequence();
    
    /** The game board. */
    protected PaintableComponent board = new PaintableComponent(sequence);
    
    /** The mouse action adapter for the game board. */
    protected MouseActionAdapter adapter = board.getMouseActionAdapter();
    
    // Actions = Objects that encapsulate behavior //
    
    /** The action to handle the mouse clicks on the tiles. */
    protected MouseAction clickAction =
        new MouseAction("Click Action") {
            public void mouseActionPerformed(MouseEvent mevt) {
                click(mevt);
            }
        };
        
    /** The action to start a new puzzle. */
    protected SimpleAction newPuzzle = new SimpleAction("New Puzzle") {
        public void perform() { newPuzzle(); }
    };
    
    // Graphical user interface //
    
    /** The contents of the main panel in the GUI. */
    protected Object[] mainStuff = { board, newPuzzle };
    
    /** The main panel in the GUI. */
    protected VTable mainPanel =
        new VTable(mainStuff, panelGap, panelGap, CENTER);
    
    
    /////////////////
    // Constructor //
    /////////////////
    
    /** The constructor. */
    public FifteenPuzzle() {
        initializeGame();
        initializeBoard();
        initializePanel();
        initializeMouse();
    }
    
    
    ////////////////////
    // Member Methods //
    ////////////////////
    
    /** The code to make the puzzle pieces. */
    protected void makePuzzlePieces() {
        Font font = new Font("serif", Font.BOLD, 72);
        
        for (int i = 0; i < count; i++) {
            pieces[i] = new TextPaintable((i+1) + "", font);
            
            // this is a hack to move the physical center
            // of the text to the center coordinates of a
            // block
            
            XRect r = pieces[i].getOriginalBounds2D();
            double x = halfSize - r.getCenterX();
            double y = halfSize - r.getCenterY();
            
            pieces[i].move(x, y);
        }
    }
    
    
    /**
     * The code to build and space the tiles
     * and insert them into the paintable sequence.
     */
    protected void makeTiles() {
        for (int row = 0; row < size; row++)
            for (int col = 0; col < size; col++) {
                tiles[row][col] = new TileBox(blank);
                tiles[row][col].setBackgroundPaint(tileColor);
                tiles[row][col].setDefaultOriginalBounds2D(bounds);
                
                int x = spacing * col;
                int y = spacing * row;
                
                tiles[row][col].move(x, y);
                
                sequence.appendPaintable(tiles[row][col]);
            }
    }
    
    
    /** The code to initialize a new game. */
    protected void newPuzzle() {
        int [] permutation = ProbStatTools.randomEvenPermutation(count);
        
        for (int i = 0; i < count; i++) {
            int row = i / size;
            int col = i % size;
            
            setBlock(row, col, pieces[permutation[i]]);
        }
        
        setBlock(size - 1, size - 1, blank);
        
        r = size - 1;
        c = size - 1;
    }
    
    
    /** The code to initialize the game board and game. */
    protected void initializeGame() {
        makePuzzlePieces();
        makeTiles();
        newPuzzle();
    }
    
    
    /** The code to initialize the board settings. */
    protected void initializeBoard() {
        board.setBackground(puzzleColor);
        board.setOpaque(true);
        board.lineBorder(puzzleColor, gap);
    }
    
    
    /** The code to initialize and install the main panel. */
    protected void initializePanel() {
        mainPanel.setBackground(panelColor);
        mainPanel.emptyBorder(panelGap);
        add(mainPanel);
    }
    
    
    /** The code to initialize the mouse listener. */
    protected void initializeMouse() {
        adapter.addMouseClickedAction(clickAction);
    }
    
    
    /** The mouse click action method. */
    protected void click(MouseEvent mevt) {
        // look for the tile hit by the mouse click
        int x = mevt.getX();
        int y = mevt.getY();
        
        TileBox tile = (TileBox) sequence.hits(x, y);
        
        // do nothing if click is in the gap between tiles
        if (tile == null)
            return;
        
        // compute the row and col of the tile
        int row = y / spacing;
        int col = x / spacing;
        
        // same row
        if (row == r) {
            if (col == c)
                return;
            
            while (c > col) {
                setBlock(r, c, getBlock(r, c - 1));
                c--;
            }
            
            while (c < col) {
                setBlock(r, c, getBlock(r, c + 1));
                c++;
            }
            
            setBlock(r, c, blank);
            return;
        }
        
        // same col
        if (col == c) {
            if (row == r)
                return;
            
            while (r > row) {
                setBlock(r, c, getBlock(r - 1, c));
                r--;
            }
            
            while (r < row) {
                setBlock(r, c, getBlock(r + 1, c));
                r++;
            }
            
            setBlock(r, c, blank);
            return;
         }
    }
    
    
    /** Sets the given block in the given row and col. */
    protected void setBlock(int row, int col, Paintable block) {
        tiles[row][col].setPaintable(block);
    }
    
    
    /** Returns the block in the given row and col. */
    protected Paintable getBlock(int row, int col) {
        return tiles[row][col].getPaintable();
    }
    
    
    ////////////////////
    // Static Methods //
    ////////////////////
    
    /** Make the blank block. */
    private static void makeBlank() {
        blank = new ShapePaintable();
        blank.setDefaultBounds2D(bounds);
    }
    
    
    ///////////////////////////////////////////////////////
    // The method main: Entry point to start the program //
    ///////////////////////////////////////////////////////
    
    /** The main method. */
    public static void main(String[] args) {
        new FifteenPuzzle().frame("15 Puzzle");
    }
    
}