import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;


/**
 * Implements a GUI that can be used for Sudoku games.
 * 
 * @author Dave Matuszek
 * @version Oct 13, 2005
 */
public class SudokuGui implements ActionListener {
    
    private JTextField grid[][];
    private JButton clearButton = new JButton("Clear");
    private JButton problem1Button = new JButton("1");
    private JButton problem2Button = new JButton("2");
    private JButton problem3Button = new JButton("3");
    private JButton solveButton = new JButton("Solve");
    private JButton quitButton = new JButton("Quit");
    
    /**
     * Creates, displays, and executes the GUI for Sudoku games.
     * 
     * @param args Not used.
     */
    public static void main(String[] args) {
        SudokuGui test = new SudokuGui();
        JFrame gui = test.createGui();
        gui.setVisible(true);
    }
    
    /**
     * Creates a 9x9 array of JTextFields, some with colored
     * backgrounds, suitable for use in a Sudoku display.
     * 
     * @return A 9x9 array of TextFields.
     */
    public JTextField[][] createTextFieldGrid() {
        grid = new JTextField[9][9];
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                grid[i][j] = new JTextField();
                if ((i / 3 + j / 3) % 2 == 0) {
                    grid[i][j].setBackground(new Color(230, 230, 255));
                    grid[i][j].setText("    ");
                }
                grid[i][j].setFont(new Font("SansSerif", Font.BOLD, 24));
            }
        }
        return grid;
    }
    
    /**
     * Creates the JFrame used as the GUI for Sudoku games.
     * 
     * @return A JFrame populated with text fields and buttons
     *         suitable for playing a Sudoku game.
     */
    private JFrame createGui() {
        grid = createTextFields();
        JPanel sudokuPanel = createSudokuPanel();
        JPanel buttonPanel = createButtonPanel();
        JFrame mainFrame = new JFrame();
        mainFrame.add(sudokuPanel, BorderLayout.CENTER);
        mainFrame.add(buttonPanel, BorderLayout.SOUTH);
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainFrame.setSize(300, 350);
        return mainFrame;
    }
    
    /**
     * Creates a 9x9 array of text fields. The font is set for every
     * field, and "boxes" (3x3 subarrays) are in alternating colors,
     * in a checkerboard pattern.
     * 
     * @return The array of text fields.
     */
    private JTextField[][] createTextFields() {
        JTextField[][] grid = new JTextField[9][9];
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                grid[i][j] = new JTextField();
                if ((i / 3 + j / 3) % 2 == 0) {
                    grid[i][j].setBackground(new Color(230, 230, 255));
                    grid[i][j].setText("    ");
                }
                grid[i][j].setFont(new Font("SansSerif", Font.BOLD, 24));
            }
        }
        return grid;
    }

    /**
     * Given a 9x9 array of text fields, embeds them in a JPanel
     * that looks like a SudokuSolver puzzle.
     * 
     * @param grid The 9x9 text fields to put into a JPanel.
     * @return A JPanel containing the text fields.
     */
    private JPanel createSudokuPanel() {
        JPanel sudokuPanel = new JPanel();
        sudokuPanel.setLayout(new GridLayout(3, 3));
        
        JPanel[][] subpanels = new JPanel[3][3];
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                subpanels[i][j] = new JPanel();
                sudokuPanel.add(subpanels[i][j]);
                int rowStart = 3 * i;
                int columnStart = 3 * j;
                JPanel panel = subpanels[i][j];
                panel.setLayout(new GridLayout(3, 3));
                for (int i1 = rowStart; i1 < rowStart + 3; i1++) {
                    for (int j1 = columnStart; j1 < columnStart + 3; j1++) {
                        panel.add(grid[i1][j1]);
                    }
                }
            }
        }
        return sudokuPanel;
    }
    
    /**
     * Creates a set of buttons suitable for a Sudoku game
     * and returns them.
     * 
     * @return A JPanel containing buttons.
     */
    private JPanel createButtonPanel() {
        JPanel buttonPanel = new JPanel();
        addButton(clearButton, buttonPanel);
        addButton(problem1Button, buttonPanel);
        addButton(problem2Button, buttonPanel);
        addButton(problem3Button, buttonPanel);
        addButton(solveButton, buttonPanel);
        addButton(quitButton, buttonPanel);
        return buttonPanel;
    }
    
    /**
     * Adds the given button to the given panel, and makes this
     * class the listener for that button.
     * 
     * @param button The button to add.
     * @param panel The panel the button will be added to.
     */
    private void addButton(JButton button, JPanel panel) {
        panel.add(button);
        button.addActionListener(this);
    }
    
    /**
     * Puts the given numbers into the Sudoku display.
     * 
     * @param numbers The numbers to display.
     */
    private void setNumbers(int[][] numbers) {
        for (int i = 0; i < numbers.length; i++) {
            for (int j = 0; j < numbers[i].length; j++) {
                setNumber(i, j, numbers[i][j]);
            }
        }
    }
    
    /**
     * Puts a single number into the Sudoku display, at the
     * given row and column.
     * 
     * @param i The row at which to place the number.
     * @param j The column at which to place the number.
     * @param number The number to be displayed.
     */
    public void setNumber(int i, int j, int number) {
        String text = number == 0 ? " " : " "  + number;
        grid[i][j].setText(text);
    }
    
    /**
     * Gets the numbers currently displayed in the 9x9 Sudoku
     * game and returns them as a 9x9 array of integers.
     * 
     * @return The numbers in the Sudoku game.
     */
    public int[][] getNumbers() {
        int[][] numbers = new int[9][9];
        for (int i = 0; i < numbers.length; i++) {
            for (int j = 0; j < numbers[i].length; j++) {
                numbers[i][j] = getNumber(i, j);
            }
        }
        return numbers;
    }
    
    /** 
     * Gets a single number from the Sudoku display, at the
     * given row and column.
     * 
     * @param i The row from which to get the number.
     * @param j The column from which to get the number.
     * @return The number displayed at that location.
     */
    private int getNumber(int i, int j) {
        String numeral = grid[i][j].getText().trim();
        return Integer.parseInt(numeral);
    }
    
    /**
     * Reads the numbers from the SudokuSolver display and returns them
     * as a 9x9 array of integers.
     * 
     * @return The numbers in the SudokuSolver display, as a 9x9 array.
     */
    private int[][] getArray() {
        int[][] array = new int[9][9];
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                String numeral = grid[i][j].getText().trim();
                if (numeral.trim().length() == 0) {
                    array[i][j] = 0;
                }
                else {
                    array[i][j] = Integer.parseInt(numeral);
                }
            }
        }
        return array;
    }

    /** Handles button clicks.
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent event) {
        JButton button = (JButton)event.getSource();
        if (button == solveButton) {
            int[][] array = getArray();
            new SudokuSolver().solve(array);
            setNumbers(array);
        }
        else if (button == quitButton) {
            System.exit(0);
        }
        else if (button == clearButton) {
            setNumbers(getProblem(0));
        }
        else {
            int buttonNumber = Integer.parseInt(button.getText());
            setNumbers(getProblem(buttonNumber));
        }
    }
    
    /**
     * Creates and returns a SudokuSolver problem. Currently, only a
     * few hard-coded problems are available.
     * 
     * @param n The number of the problem to be returned (0 to clear).
     * @return A SudokuSolver problem.
     */
    private int[][] getProblem(int n) {
        switch (n) {
            case 1:
                return new int[][]
                          { { 0, 0, 4,   0, 0, 0,   0, 6, 7 },
                            { 3, 0, 0,   4, 7, 0,   0, 0, 5 },
                            { 1, 5, 0,   8, 2, 0,   0, 0, 3 },
                                
                            { 0, 0, 6,   0, 0, 0,   0, 3, 1 },
                            { 8, 0, 2,   1, 0, 5,   6, 0, 4 },
                            { 4, 1, 0,   0, 0, 0,   9, 0, 0 },
                                
                            { 7, 0, 0,   0, 8, 0,   0, 4, 6 },
                            { 6, 0, 0,   0, 1, 2,   0, 0, 0 },
                            { 9, 3, 0,   0, 0, 0,   7, 1, 0 } };
            case 2:
                return new int[][]
                          { { 0, 1, 6,   0, 0, 9,   0, 0, 2 },
                            { 0, 0, 0,   0, 3, 1,   0, 4, 0 },
                            { 8, 7, 0,   5, 2, 0,   0, 1, 0 },
                                
                            { 0, 0, 9,   0, 8, 0,   2, 0, 1 },
                            { 5, 0, 0,   6, 0, 7,   0, 0, 9 },
                            { 4, 0, 1,   0, 5, 0,   3, 0, 0 },
                                
                            { 0, 4, 0,   0, 7, 8,   0, 9, 5 },
                            { 0, 9, 0,   3, 6, 0,   0, 0, 0 },
                            { 2, 0, 0,   1, 0, 0,   6, 3, 0 } };
            case 3:
                return new int[][]
                          { { 0, 0, 0,   2, 0, 3,   9, 6, 0 },
                            { 2, 0, 6,   0, 0, 7,   0, 3, 0 },
                            { 7, 0, 0,   1, 5, 0,   8, 0, 0 },
                                
                            { 3, 6, 0,   0, 7, 2,   5, 0, 1 },
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                            { 4, 0, 8,   6, 0, 0,   3, 7, 9 },
                                
                            { 0, 0, 7,   0, 6, 8,   0, 0, 3 },
                            { 0, 3, 0,   7, 0, 0,   4, 0, 6 },
                            { 0, 1, 2,   9, 0, 4,   0, 0, 0 } };
            case 0:
            default:
                return new int[][]
                          { { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                                
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                                
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 },
                            { 0, 0, 0,   0, 0, 0,   0, 0, 0 } };
        }
    }
}
