import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;


/**
 * This is the GUI for the Logo2006 program, as assigned to
 * the CIT594 class during the Spring of 2006.
 * <p>
 * As provided, the program handles all the GUI work and loads
 * and saves files appropriately. It is the student's job to
 * enhance this class to execute Logo2006 programs and display
 * the resultant graphics. Places that need to be extended are
 * marked with task flags.
 * <p>
 * To display tasks in Eclipse, choose <b>
 * Window -> Show View -> Other... -> General -> Tasks
 * </b>). This will put a new 'Tasks' tab in the lower
 * right-hand pane of the Eclipse window.
 * 
 * @author David Matuszek
 * @author ADD YOUR NAME HERE
 * @version Mar 10, 2006
 */
class LogoGui extends JFrame {
    private static final long serialVersionUID = 1L;
    private static final int CANVAS_WIDTH = 600;
    private static final int CANVAS_HEIGHT = 600;
    private static final int YES = JOptionPane.YES_OPTION;
    private static final int NO = JOptionPane.NO_OPTION;
    private static final int CANCEL = JOptionPane.CANCEL_OPTION;
    private static Interpreter interpreter;
    
    private String programText = "";
    private File currentFile = null;
    private String fullNameOfCurrentFile = "";
    private int fileHashCode = 0;
    private boolean displayingSyntaxTree = false;
    private boolean programIsPaused = false;
    
    // Declarations of widgets used in the GUI
    private JMenuBar myJMenuBar;
    private JMenu fileMenu;
    private JMenuItem loadMenuItem;
    private JMenuItem saveMenuItem;
    private JMenuItem saveAsMenuItem;
    private JMenuItem exitMenuItem;
    
    private JButton programTextButton;
    private JButton syntaxTreeButton;
    private JButton startButton;
    private JButton pauseButton; // doubles as Resume button
    private JButton stopButton;
    private JButton clearButton;
    
    private JSlider speedControlSlider;
    
    private TurtleCanvas canvas;
    private JTextArea textArea;
    
    /**
     * Creates the GUI for the Logo 2006 interpreter.
     */
    public LogoGui() {
        createGui();
        displayGui();
    }
    
    /**
     * Returns the current program text, regardless of whether
     * the text window is showing the program text or the
     * corresponding abstract syntax tree.
     * 
     * @return The current program text.
     */
    public String getProgramText() {
        if (displayingSyntaxTree) {
            return programText;
        }
        else {
            return textArea.getText();
        }
    }
    
    /**
     * Returns the JPanel on which the turtle is to draw.
     * 
     * @return The turtle's canvas.
     */
    public TurtleCanvas getCanvas() {
        return canvas;
    }

    /**
     * Creates the GUI for the Logo interpreter.
     */
    private void createGui() {
        createWidgets();
        addWidgetsToGui();
        addListeners();
        disableMostControls();
        programText = "";
        fileHashCode = programText.hashCode();
    }
    
    private void displayGui() {
        pack();
        setLocationRelativeTo(getParent());
        setVisible(true);
    }

    /**
     * Creates the widgets used in the GUI.
     */
    private void createWidgets() {
        createFileMenu();        
        createButtons();
        createSpeedSlider();
        
        canvas = new TurtleCanvas();
        textArea = new JTextArea();
    }

    /**
     * Creates the File menu.
     */
    private void createFileMenu() {
        myJMenuBar = new JMenuBar();
        fileMenu = new JMenu("File");
        loadMenuItem = new JMenuItem("Load...");
        saveMenuItem = new JMenuItem("Save");
        saveAsMenuItem = new JMenuItem("Save As...");
        exitMenuItem = new JMenuItem("Exit");
        
        myJMenuBar.add(fileMenu);
        fileMenu.add(loadMenuItem);
        fileMenu.add(saveMenuItem);
        fileMenu.add(saveAsMenuItem);
        fileMenu.add(new JSeparator());
        fileMenu.add(exitMenuItem);
    }

    /**
     * Creates all buttons used in the GUI.
     */
    private void createButtons() {
        programTextButton = new JButton("Show Program Text");
        syntaxTreeButton = new JButton("Show Syntax Tree");
        
        startButton = new JButton("Start");
        pauseButton = new JButton("Pause");
        stopButton = new JButton("Stop");
        clearButton = new JButton("Clear");
    }

    /**
     * Creates the speed control.
     */
    private void createSpeedSlider() {
        speedControlSlider = new JSlider(SwingConstants.HORIZONTAL, 0, 100, 25);
        speedControlSlider.setMajorTickSpacing(20);
        speedControlSlider.setMinorTickSpacing(5);
        speedControlSlider.setPaintTicks(true);
        speedControlSlider.setPaintLabels(true);
    }

    /**
     * Arranges all the widgets in the GUI.
     */
    private void addWidgetsToGui() {
        setJMenuBar(myJMenuBar);
        setLayout(new BorderLayout());
        
        initializeCanvas();
        add(canvas, BorderLayout.CENTER);
        
        JPanel programView = new JPanel();
        arrangeProgramView(programView);
        add(programView, BorderLayout.EAST);
        
        JPanel controlPanel = new JPanel();
        arrangeControlPanel(controlPanel);  
        add(controlPanel, BorderLayout.SOUTH);      
    }

    /**
     * Sets the size and color of the canvas.
     */
    private void initializeCanvas() {
        Dimension size = new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT);
        canvas.setSize(size);
        canvas.setPreferredSize(size);
        canvas.setMinimumSize(size);
        canvas.setMaximumSize(size);
        canvas.setBackground(Color.WHITE);
    }

    /**
     * Arranges the program view, consisting of a text area which can
     * show either the program text or the abstract syntax tree, and
     * buttons to switch between the two views.
     * 
     * @param programView The panel to be arranged.
     */
    private void arrangeProgramView(JPanel programView) {
        JPanel programControls = new JPanel();
        programView.setLayout(new BorderLayout());
        programControls.add(programTextButton);
        programControls.add(syntaxTreeButton);
        programView.add(programControls, BorderLayout.NORTH);
        JScrollPane scrollPane = new JScrollPane(textArea);
        textArea.setText(programText);
        programView.add(scrollPane, BorderLayout.CENTER);
    }

    /**
     * Adds the buttons and speed control to the control panel.
     * 
     * @param controlPanel The panel to be arranged.
     */
    private void arrangeControlPanel(JPanel controlPanel) {
        controlPanel.add(startButton);
        controlPanel.add(pauseButton);
        controlPanel.add(stopButton);
        controlPanel.add(clearButton);
        controlPanel.add(new JLabel("          Speed:"));
        controlPanel.add(speedControlSlider);
    }

    /**
     * Adds listeners for each GUI widget.
     */
    private void addListeners() {
        addMenuItemListeners();
        addTextViewListeners();
        addExecutionControlListeners();
        addTextAreaListener();
    }

    /**
     * Adds listeners for the items in the File menu.
     */
    private void addMenuItemListeners() {
        loadMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                loadFile();
            }
        });
        saveMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                saveFile();
            }
        });
        saveAsMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                saveFileAs();
            }
        });
        exitMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                exit();
            }
        });
    }

    /**
     * Adds listeners for Show Program Text and Show Syntax Tree
     * buttons.
     */
    private void addTextViewListeners() {
        programTextButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showProgramText();
            }
        });
        syntaxTreeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                showSyntaxTree();
            }
        });
    }

    /**
     * Adds listeners for buttons that control Logo program
     * execution.
     */
    private void addExecutionControlListeners() {
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                start();
            }
        });
        pauseButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                pauseOrResume();
            }
        });
        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                stop();
            }
        });
        clearButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                clear();
            }
        });
        speedControlSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                changeSpeed();
            }
        });
    }

    /**
     * Adds a listener to the text area, so that the Start button,
     * the Show Syntax Tree button, and the Save and Save As menu
     * items are enabled and disabled at the appropriate times.
     */
    private void addTextAreaListener() {
        textArea.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent e) {
                startButton.setEnabled(true);
                saveMenuItem.setEnabled(true);
                saveAsMenuItem.setEnabled(true);
                syntaxTreeButton.setEnabled(true);
            }
            public void removeUpdate(DocumentEvent e) {
                if (textArea.getText().length() == 0) {
                    startButton.setEnabled(false);
                    syntaxTreeButton.setEnabled(false);
                }
            }
            public void changedUpdate(DocumentEvent e) {
            }
            
        });
    }
    
    /**
     * Disables all but the Load... and Exit menu items, the Clear
     * button, and the speed control slider. This is the appropriate
     * state for the GUI to be in before a program has been entered.
     */
    private void disableMostControls() {
        saveMenuItem.setEnabled(false);
        saveAsMenuItem.setEnabled(false);
        startButton.setEnabled(false);
        pauseButton.setEnabled(false);
        stopButton.setEnabled(false);
        programTextButton.setEnabled(false);
        syntaxTreeButton.setEnabled(false);
    }

    /**
     * Reads a Logo program in from a file and displays it.
     */
    private void loadFile() {
        if (currentFile != null && fileHashCode != textArea.getText().hashCode()) {
            int yesNo =
                confirm("Your current program has not been saved.\n" +
                        "Would you like to save it before loading another?");
            if (yesNo == YES) {
                saveFile();
            }
            else if (yesNo == CANCEL) {
                return;
            }
        }
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Load which Logo file?");
        int result = chooser.showOpenDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            BufferedReader reader = null;
            try {
                currentFile = chooser.getSelectedFile();
                if (currentFile != null) {
                    fullNameOfCurrentFile = currentFile.getCanonicalPath();
                    setTitle(fullNameOfCurrentFile);
                    reader =
                        new BufferedReader(new FileReader(fullNameOfCurrentFile));
                    programText = "";
                    String line;
                    while ((line = reader.readLine()) != null) {
                        programText += line + "\n";
                    }
                    showProgramText();
                    fileHashCode = programText.hashCode();
                }
            }
            catch (IOException e) {
                tellUser("Unable to load file " + fullNameOfCurrentFile);
            }
            finally {
                try {
                    if (reader != null) reader.close();
                }
                catch (IOException e) {
                    System.err.println("Unable to close file " + fullNameOfCurrentFile);
                }
            }
        }
    }

    /**
     * Saves the program file in the same location that it was
     * loaded from.
     */
    private void saveFile() {
        if (currentFile == null) {
            saveFileAs();
            return;
        }
        else {
            PrintWriter writer = null;
            try {
                fullNameOfCurrentFile = currentFile.getCanonicalPath();
                setTitle(fullNameOfCurrentFile);
                writer =
                    new PrintWriter(new FileOutputStream(fullNameOfCurrentFile));
                writer.print(getProgramText());
                fileHashCode = getProgramText().hashCode();
            }
            catch (IOException e) {
                tellUser("Unable to save file " + fullNameOfCurrentFile);
            }
            finally {
                if (writer != null) writer.close();
            }
        }
    }

    /**
     * Saves the Logo program in a file that the user chooses.
     */
    private void saveFileAs() {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Save file as?");
        int result = chooser.showSaveDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            File newFile = chooser.getSelectedFile();
            if (newFile != null) {
                if (newFile.exists()) {
                    int overwrite =
                        confirm("This file already exists. \n" +
                                "Do you want to overwrite it? ");
                    if (overwrite == NO || overwrite == CANCEL) return;
                }
                currentFile = newFile;
                saveFile();
            }       
        }
    }
    
    /**
     * Handles a click on the window closing icon. If the current program
     * has not been saved, the user will be given the option of saving
     * the program or cancelling the windoe closing.
     * 
     * @see javax.swing.JFrame#processWindowEvent(java.awt.event.WindowEvent)
     */
    @Override
    protected void processWindowEvent(WindowEvent e) {
        if (e.getID() == WindowEvent.WINDOW_CLOSING) {
            exit();
        }
    }
    
    /**
     * Quits the program. If there are unsaved changes, the user
     * has the option to save them before quitting, or to cancel
     * the exit operation altogether.
     */
    private void exit() {
        if (fileHashCode != textArea.getText().hashCode()) {
            int yesNo =
                confirm("Your current program has not been saved.\n" +
                        "Would you like to save it before quitting?");
            if (yesNo == CANCEL) {
                return;
            }
            else if (yesNo == YES) {
                saveFile();
            }
        }
        dispose();
        System.exit(0);
    }

    /**
     * Provide a message that some partially implemented GUI widget
     * needs further implementation by the CIT594 student.
     * TODO Eliminate this method and all uses of it.
     * 
     * @param what The control that needs to be implemented.
     * @param more Additional information about what needs to be done.
     */
    private void unimplemented(String what, String more) {
        tellUser("You need to implement the\n" + what + ". \n\n" + more);        
    }
    
    /**
     * Displays the given message in a message dialog.
     * 
     * @param message The message to display.
     */
    private void tellUser(String message) {
        JOptionPane.showMessageDialog(this, message);
    }
    
    /**
     * Displays a standard confirmation dialog.
     * 
     * @param message The message to be displayed.
     * @return The result returned by the confirmation dialog.
     */
    private int confirm(String message) {
        return JOptionPane.showConfirmDialog(this, message);
    }

    
    /**
     * Displays the program text.
     */
    private void showProgramText() {
        textArea.setText(programText);
        displayingSyntaxTree = false;
        programTextButton.setEnabled(false);
        syntaxTreeButton.setEnabled(true);
        textArea.setEditable(true);
    }
    
    /**
     * Displays the abstract syntax tree corresponding to the current
     * program text.
     */
    private void showSyntaxTree() {
        programText = textArea.getText();
        // TODO Parse program text and display parse tree here
        displayingSyntaxTree = true;
        programTextButton.setEnabled(true);
        syntaxTreeButton.setEnabled(false);
        textArea.setEditable(false);
        unimplemented("Show Abstract Syntax Tree button",
                      "I've implemented showing the program text, \n" +
                      "but you need to call your Parser on the \n" +
                      "program text and display the resulting \n" +
                      "abstract syntax tree.");
    }
    
    /**
     * Starts execution of the Logo program (from the beginning).
     */
    private void start() {
        // TODO Start the Logo program running
        startButton.setEnabled(false);
        pauseButton.setEnabled(true);
        stopButton.setEnabled(true);
        Parser parser = new Parser(getProgramText());
        try {
            if (parser.program()) {
                Tree<Token> tree = parser.getParseTree();
                interpreter.interpret(tree);
            }
            else tellUser("Syntax error somewhere in program.");
        }
        catch (LogoException e) {
            tellUser(e.toString());
        }
        catch (RuntimeException e) {
            tellUser(e.toString());
            throw new RuntimeException(e);
        }
    }
    
    /**
     * If the Logo program is running, pauses the program; but if the
     * program is paused, resumes running it from where it left off.
     */
    private void pauseOrResume() {
        if (programIsPaused) {
            // TODO Resume running the paused Logo program
            pauseButton.setText("Pause");
            startButton.setEnabled(false);
            pauseButton.setEnabled(true);
            stopButton.setEnabled(true);
        }
        else {
            // TODO Pause the Logo program
            pauseButton.setText("Resume");
            startButton.setEnabled(false);
            pauseButton.setEnabled(true);
            stopButton.setEnabled(true);
        }
        unimplemented("Pause/Resume button",
                      "You should pause or resume running the \n" +
                      "Logo program when this button is clicked.");
    }
    
    /**
     * Terminates execution of the Logo program. The program
     * may be executed again, but only from the beginning, not
     * from wherever it may have been when it was stopped. 
     */
    private void stop() {
        // TODO Stop the Logo program
        pauseButton.setText("Pause");
        startButton.setEnabled(true);
        pauseButton.setEnabled(false);
        stopButton.setEnabled(false);
        unimplemented("Stop button",
                      "You should end the Logo program \n" +
                      "when this button is clicked.");
    }
    
    /**
     * Erases the drawing area.
     */
    private void clear() {
        Color oldColor = canvas.getForeground();
        canvas.setForeground(Color.WHITE);
        canvas.getGraphics().fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
        canvas.setForeground(oldColor);
    }
    
    /**
     * Changes the speed at which the turtle draws, to correspond to
     * the speed set by the speed control slider. A speed of zero
     * means no movement, while the maximum speed available on the
     * slider means that there is no delay between turtle actions.
     */
    private void changeSpeed() {
        // TODO Set the delay between turtle turns and moves
        // (Only actions that visibly change the display should be affected)
        unimplemented("Speed control slider",
                      "You should control the speed of the Logo \n" +
                      "turtle with this slider. Zero means no \n" +
                      "movement, and is essentially equivalent \n" +
                      "to \"Pause\"; maximum speed should impose \n" +
                      "no delay at all on the turtle.");
    }

    /**
     * Test method to display GUI. Will not be used in the completed
     * Logo2006 program.
     * 
     * @param args Unused.
     */
    public static void main(String[] args) {
        interpreter = new Interpreter();
    }
}
