import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
 * Displays a dialog box in which the user can enter an array
 * of int values. The array can have up to MAX_ARRAY_SIZE rows
 * and columns.<p>
 * Sample use:  int[][] array = ArrayReader.getArray();
 * 
 * @author David Matuszek
 * @version Oct 12, 2006
 */
public class ArrayReader implements ActionListener {
    private JDialog window;
    private int[][] array;
    private int rows;
    private int columns;
    private boolean arrayIsOk;
    private boolean gettingValues;
    private final int MAX_ARRAY_SIZE = 20;
    
    private JTextField rowField;
    private JTextField columnField;
    private JTextField[][] values;
    private JButton cancelButton;
    private JButton okButton;
    private JPanel sizeInputPanel;
    private JPanel arrayInputPanel;
    private JPanel buttonPanel;
    private JPanel arrayPanel;
    
    /**
     * Opens a dialog box in which the user can enter the size
     * of a two-dimensional int array, then enter the values.
     * 
     * @return The array created by the user (or null if cancelled).
     */
    public static int[][] getArray() {
        ArrayReader reader = new ArrayReader();
        reader.run();
        return reader.array;        
    }
    
    /**
     * Implements the work of the main method.
     */
    private void run() {
        createSizeInputPanel();
        createButtonPanel();
        window = new JDialog((JFrame)null, "Array input", true);
        createArraySizeGui();
        attachButtonListeners();
        window.pack();
        window.setVisible(true);
        gettingValues = false;
    }

    /**
     * Creates panel 'sizeInputPanel' for inputting the desired
     * number of rows and columns.
     */
    private void createSizeInputPanel() {
        sizeInputPanel = new JPanel();
        rowField = new JTextField(5);
        columnField = new JTextField(5);
        sizeInputPanel.add(rowField);
        sizeInputPanel.add(new JLabel("rows,  "));
        sizeInputPanel.add(columnField);
        sizeInputPanel.add(new JLabel("columns"));
    }

    /**
     * Creates panel 'buttonPanel' with 'Cancel' and 'OK' buttons.
     */
    private void createButtonPanel() {
        buttonPanel = new JPanel();
        cancelButton = new JButton("Cancel");
        okButton = new JButton("  OK  ");
        buttonPanel.add(cancelButton);
        buttonPanel.add(okButton);
    }

    /**
     * Creates panel 'arrayPanel' containing subpanels for
     * labeling the input, inputting array values, and holding
     * the 'OK' and 'Cancel' buttons.
     */
    private void createArrayPanel() {
        arrayPanel = new JPanel();
        arrayPanel.setLayout(new BorderLayout());
        arrayPanel.add(new JLabel("Enter array values (blank = 0):"),
                       BorderLayout.NORTH);
        createArrayInputPanel();
        arrayPanel.add(arrayInputPanel, BorderLayout.CENTER);
        arrayPanel.add(buttonPanel, BorderLayout.SOUTH);
    }

    /**
     * Creates panel 'arrayInputPanel' with a grid of the previously determined
     * number of rows and columns, for inputting array values.
     */
    private void createArrayInputPanel() {
        arrayInputPanel = new JPanel();
        arrayInputPanel.setLayout(new GridLayout(rows, columns));
        array = new int[rows][columns];
        values = new JTextField[rows][columns];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                values[i][j] = new JTextField(4);
                arrayInputPanel.add(values[i][j]);
            }
        }
    }

    /**
     * Creates the initial window for choosing an array size.
     */
    private void createArraySizeGui() {
        window.setLayout(new BorderLayout());
        window.add(new JLabel("Choose array size:"), BorderLayout.NORTH);
        window.add(sizeInputPanel, BorderLayout.CENTER);
        window.add(buttonPanel, BorderLayout.SOUTH);
    }

    /**
     * Activates the 'Cancel' and 'OK' buttons.
     */
    private void attachButtonListeners() {
        cancelButton.addActionListener(this);
        okButton.addActionListener(this);
    }

    /**
     * Adds to the previously created GUI the panels necessary for
     * inputting array values.
     */
    private void extendGui() {
        window.remove(buttonPanel);
        rowField.setEditable(false);
        columnField.setEditable(false);
        createArrayPanel();
        window.add(arrayPanel, BorderLayout.SOUTH);
        window.pack();
        window.validate();
        gettingValues = true;
    }

    /**
     * Handles all button clicks.
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent event) {
        if (event.getSource() == cancelButton) {
            array = null;
            window.dispose();
        }
        else if (event.getSource() == okButton){
            if (!gettingValues) {
                getRowAndColumnSizes();
                if (isLegalSize(rows) && isLegalSize(columns)) {
                    extendGui();
                    gettingValues = true;
                }
            }
            else {
                arrayIsOk = false;
                getArrayValues();
                if (arrayIsOk) {
                    window.dispose();
                }
            }
        }
    }
    
    /**
     * Tests whether the parameter is a legal value for the number
     * of rows or columns of the array.
     * @param n The number to be tested.
     * @return <code>true</code> if n is a suitable size for an array.
     */
    private boolean isLegalSize(int n) {
        return n > 0 && n <= MAX_ARRAY_SIZE;
    }

    /**
     * Reads in all the array values from the GUI.
     * 
     * @return <code>true</code> if all values are legal integers.
     */
    private boolean getArrayValues() {
        arrayIsOk = true;
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                array[i][j] = getValue(values[i][j]);
                if (array[i][j] == Integer.MAX_VALUE) {
                    arrayIsOk = false;
                }
            }
        }
        return arrayIsOk;
    }

    /**
     * Gets an integer value from the given text field. If the value
     * is a legal integer, the background of the text field becomes
     * or remains white, otherwise it becomes pink.
     * 
     * @param field The field to be read.
     * @return The integer value in that field, or Integer.MAX_VALUE
     *         if the value in the field is not an integer.
     */
    private int getValue(JTextField field) {
        String s = field.getText().trim();
        if (s.length() == 0) return 0;
        try {
            int n = Integer.parseInt(s);
            field.setBackground(Color.WHITE);
            return n;
        }
        catch (NumberFormatException e) {
            field.setBackground(Color.PINK);
            return Integer.MAX_VALUE;
        }
    }

    /**
     * Get values for the instance variables 'rows' and 'columns'
     * from the GUI.
     */
    private void getRowAndColumnSizes() {
        rows = getNumber(rowField);
        columns = getNumber(columnField);
    }
    
    /**
     * Puts up an alert if the number in the given field is not in
     * the range 1..MAX_ARRAY_SIZE.
     * 
     * @param field The field to be read.
     * @return The number in that field, or zero if the number is illegal.
     */
    private int getNumber(JTextField field) {
        String message = "Sizes must be integers 1.." + MAX_ARRAY_SIZE;
        try {
            int number = Integer.parseInt(field.getText());
            if (isLegalSize(number)) return number;
            JOptionPane.showMessageDialog(window, message);
        }
        catch (NumberFormatException e) {
            JOptionPane.showMessageDialog(window, message);
        }
        return 0;
    }
}
