
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;

/**
 * This class implements a GUI interface to the KnightsTourBoard class
 */
public class KnightsTour extends JFrame
{
    private KnightsTourBoard m_ktBoard = new KnightsTourBoard(5, 5);
    private Thread m_tourThread;

    /* GUI elements */
    private ActionEventHandler m_handler = new ActionEventHandler(this);
    private JTextField m_widthField;
    private JTextField m_heightField;
    private JTextField m_startXField;
    private JTextField m_startYField;
    private JButton m_startButton;
    private JButton m_cancelButton;
    private JButton m_exitButton;
    private JButton m_resetButton;
    private JPanel m_mainPanel;
    private JPanel m_boardPanel;
    private JLabel m_statusLabel;

    /**
     * Construct the KnightsTour GUI interface
     */
    public KnightsTour()
    {
        super("Josh's Knight's Tour project for CS621");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(500, 500);

        /* set up the main window contents */
        getContentPane().add(m_mainPanel = new JPanel(new BorderLayout()) {{
            setBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9));
            add(new JPanel() {{
                setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
                add(new JPanel() {{
                    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
                    setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 7));
                    add(new JPanel() {{     /* board size panel */
                        setBorder(BorderFactory.createTitledBorder("Board Size"));
                        add(new JPanel() {{
                            setBorder(BorderFactory.createEmptyBorder());
                            setLayout(new GridLayout(2, 2, 5, 5));
                            add(new JLabel("Width:", SwingConstants.RIGHT));
                            add(m_widthField = new JTextField("5"));
                            add(new JLabel("Height:", SwingConstants.RIGHT));
                            add(m_heightField = new JTextField("5"));
                        }});
                    }});
                    add(new Box.Filler(new Dimension(5, 0),
                                       new Dimension(5, 0),
                                       new Dimension(5, 0)));
                    add(new JPanel() {{     /* starting position panel */
                        setBorder(BorderFactory.createTitledBorder("Starting Position"));
                        add(new JPanel() {{
                            setBorder(BorderFactory.createEmptyBorder());
                            setLayout(new GridLayout(2, 2, 5, 5));
                            add(new JLabel("Column (X):", SwingConstants.RIGHT));
                            add(m_startXField = new JTextField("2"));
                            add(new JLabel("Row (Y):", SwingConstants.RIGHT));
                            add(m_startYField = new JTextField("2"));
                        }});
                    }});
                    add(new Box.Filler(new Dimension(5, 0),
                                       new Dimension(5, 0),
                                       new Dimension(5, 0)));
                    add(new JPanel() {{     /* start button panel */
                        setBorder(BorderFactory.createTitledBorder(
                                "Control Knight's Tour"));
                        add(new JPanel() {{
                            setBorder(BorderFactory.createEmptyBorder());
                            setLayout(new GridLayout(2, 2, 5, 5));
                            add(m_startButton = new JButton("Start"));
                            m_startButton.addActionListener(m_handler);
                            add(m_resetButton = new JButton("Reset"));
                            m_resetButton.addActionListener(m_handler);
                            add(m_cancelButton = new JButton("Cancel"));
                            m_cancelButton.addActionListener(m_handler);
                            m_cancelButton.setEnabled(false);
                            add(m_exitButton = new JButton("Exit"));
                            m_exitButton.addActionListener(m_handler);
                        }});
                    }});
                    add(new Box.Filler(new Dimension(0, 0),
                                       new Dimension(Short.MAX_VALUE, 0),
                                       new Dimension(10000, 10000)));
                }});
                add(new JPanel() {{
                    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
                    add(m_statusLabel = new JLabel("Ready."));
                    add(new Box.Filler(new Dimension(0, 0),
                                       new Dimension(10000, 0),
                                       new Dimension(10000, 10000)));
                }});
            }}, BorderLayout.NORTH);
        }});

        createBoardPanel();

        setVisible(true);
    }

    /* Create a JPanel containing a grid showing the step number
     * for each board position in the KnightsTourBoard object */
    private void createBoardPanel()
    {
        int width = m_ktBoard.getWidth();
        int height = m_ktBoard.getHeight();
        JPanel oldBoardPanel = m_boardPanel;
        m_boardPanel = new JPanel(new GridLayout(height, width, 1, 1));
        m_boardPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
        for (int y = 0; y < height; y++)
        {
            for (int x = 0; x < width; x++)
            {
                JLabel l = new JLabel(m_ktBoard.getStepAt(x, y) == 0
                        ? ""
                        : new Integer(m_ktBoard.getStepAt(x, y)).toString(),
                    SwingConstants.CENTER);
                l.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
                m_boardPanel.add(l);
            }
        }
        if (oldBoardPanel != null)
            m_mainPanel.remove(oldBoardPanel);
        m_mainPanel.add(m_boardPanel);
        m_mainPanel.validate();
    }

    /* This helper class catches actions that occur on JButton objects */
    private class ActionEventHandler implements ActionListener
    {
        private KnightsTour m_kt;

        public ActionEventHandler(KnightsTour kt) { m_kt = kt; }

        public void actionPerformed(ActionEvent e)
        {
            if (e.getSource() == m_startButton)
            {
                setUpTour(true);
            }
            else if (e.getSource() == m_cancelButton)
            {
                if (m_tourThread != null)
                {
                    m_tourThread.stop();
                    m_tourThread = null;
                    m_startButton.setEnabled(true);
                    m_cancelButton.setEnabled(false);
                    m_resetButton.setEnabled(true);
                    m_statusLabel.setText("Cancelled.");
                }
            }
            else if (e.getSource() == m_resetButton)
            {
                setUpTour(false);
            }
            else if (e.getSource() == m_exitButton)
            {
                System.exit(0);
            }
        }
    }

    /**
     * Helper method to set up the Knight's Tour GUI board
     * @param runit whether to run the tour or not (false means just reset board)
     */
    private void setUpTour(boolean runit)
    {
        /* first validate input text fields */
        int width, height, startx, starty;
        try
        {
            width = Integer.parseInt(m_widthField.getText());
            height = Integer.parseInt(m_heightField.getText());
            startx = Integer.parseInt(m_startXField.getText());
            starty = Integer.parseInt(m_startYField.getText());
        }
        catch (NumberFormatException nfe)
        {
            m_statusLabel.setText("Non-numerical input!");
            return;
        }
        if (width < 1 || height < 1 || startx < 0 || starty < 0 ||
                startx >= width || starty >= height)
        {
            m_statusLabel.setText("Invalid input!");
            return;
        }

        /* create a new KnightsTourBoard object */
        m_ktBoard = new KnightsTourBoard(width, height);
        createBoardPanel();
        
        if (runit)
        {
            /* run the tour in a separate thread */
            m_startButton.setEnabled(false);
            m_cancelButton.setEnabled(true);
            m_resetButton.setEnabled(false);
            m_statusLabel.setText("Running...");

            m_tourThread = new Thread(new TourThread(startx, starty));
            m_tourThread.start();
        }
        else
        {
            m_statusLabel.setText("Reset.");
        }
    }

    /* This class runs as a separate thread to actually call
     * tour() on the KnightsTourBoard object. This way, the GUI
     * stays responsive and the user can cancel the tour by clicking
     * a button if it runs for too long. */
    private class TourThread implements Runnable
    {
        int m_startx, m_starty;

        /* create the tour thread with the given starting position */
        public TourThread(int startx, int starty)
        {
            m_startx = startx;
            m_starty = starty;
        }

        /* called when the thread starts executing */
        public void run()
        {
            if (m_ktBoard.tour(m_startx, m_starty))
            {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run()
                    {
                        createBoardPanel();
                        m_statusLabel.setText("Finished.");
                        m_startButton.setEnabled(true);
                        m_cancelButton.setEnabled(false);
                        m_resetButton.setEnabled(true);
                    }
                });
            }
            else
            {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run()
                    {
                        createBoardPanel();
                        m_statusLabel.setText("Solution not found!");
                        m_startButton.setEnabled(true);
                        m_cancelButton.setEnabled(false);
                        m_resetButton.setEnabled(true);
                    }
                });
            }
            m_tourThread = null;
        }
    }

    /**
     * This method is invoked first when the program is run.
     * It simply creates a KnightsTour GUI object
     */
    public static void main(String[] args)
    {
        KnightsTour kt = new KnightsTour();
    }
}
