import objectdraw.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * Demonstration of a representation for arithmetic expressions.
 * 
 * @author Russell C. Bjork 
 * @version March 20, 2008
 */

public class ExpressionDemonstration extends WindowController
{
    /** Main method for demonstration
     */
    public static void main(String [] args) {
        
        ExpressionDemonstration demo = new ExpressionDemonstration();
        demo.startController(WINDOW_WIDTH, WINDOW_HEIGHT);
    }
       
    /** Add GUI facilities
     */
    public void begin() {
        
        JPanel gui = new JPanel();
        getContentPane().add(gui, BorderLayout.SOUTH);

        newButton = new JButton("New Expression");
        gui.add(newButton);
        toStringButton = new JButton("Convert to string");
        gui.add(toStringButton);
        evaluateButton = new JButton("Evaluate");
        gui.add(evaluateButton);
        derivativeButton = new JButton("Derivative");
        gui.add(derivativeButton);
        simplifyButton = new JButton("Simplify");
        gui.add(simplifyButton);
        originalButton = new JButton("Original expression");
        gui.add(originalButton);
        
        enableButtons(false);
        
        // Add action listeners to the buttons
        
        newButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                try {
                    String text = JOptionPane.showInputDialog(ExpressionDemonstration.this, 
                                                              "New expression:");
                    if (text == null)
                        return;
                    canvas.clear();
                    originalExpression = parse(text);
                    currentExpression = originalExpression;
                    drawCurrent();
                    enableButtons(true);
                    originalButton.setEnabled(false);
                }
                catch(Exception exception) {
                    JOptionPane.showMessageDialog(ExpressionDemonstration.this, 
                                                  "Exception: " + exception);
                    enableButtons(false);
                }
            }
        });
        
        toStringButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(ExpressionDemonstration.this, 
                                              "Result: " + currentExpression.toString());
            }
        });
        
        evaluateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    String xString = JOptionPane.showInputDialog(ExpressionDemonstration.this, 
                                                                 "X Value:");
                    if (xString == null)
                        return;
                    double x = Double.parseDouble(xString);
                    JOptionPane.showMessageDialog(ExpressionDemonstration.this, 
                                                  "Result: " + currentExpression.evaluate(x));
                    
                }
                catch(Exception exception) {
                    JOptionPane.showMessageDialog(ExpressionDemonstration.this, 
                                                  "Exception: " + exception);
                }
                
            }
        });
        
        derivativeButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                currentExpression = currentExpression.derivative();
                drawCurrent();
                originalButton.setEnabled(true);
            }
        });
        
        simplifyButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                currentExpression = currentExpression.simplify();
                drawCurrent();
                originalButton.setEnabled(true);
            }
        }); 
        
        originalButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                currentExpression = originalExpression;
                drawCurrent();
                originalButton.setEnabled(false);
            }
        });
    }
        
        
    /** Create an Expression object from a textual representation
     * 
     *  @param text the text to 
     *  @exception IllegalArgumentException if text does not represent
     *             a fully-parenthesized expression consisting only of
     *             constants, the variable X, and the arithmetic operators
     *             +, -, *, /, and %
     *  @exception NumberFormatExpression if text contains a malformed
     *             number
     */
    public static Expression parse(String text) throws IllegalArgumentException,
                                                       NumberFormatException {
                                                           
        text = text.trim(); // Remove extraneous whitespace
        switch(text.charAt(0)) {
            
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
            
                return new Constant(Double.parseDouble(text));
                
            case 'X': case 'x':
            
                if (text.length() == 1)
                    return new VariableX();
                else
                    throw new IllegalArgumentException();
                    
            case '(':
            
                if (text.charAt(text.length() - 1) == ')') {
                    
                    // Find the operator
                    
                    int opPosition = 1;
                    int parenthesesCount = 0;
                    boolean operatorFound = false;
                    
                    while(opPosition < text.length() - 1 && ! operatorFound) {
                        char possible = text.charAt(opPosition);
                        if (possible == '(')
                            parenthesesCount ++;
                        else if (possible == ')')
                            parenthesesCount --;
                        if (isOperator(possible) && parenthesesCount == 0)
                            operatorFound = true;
                        else
                            opPosition ++;
                        }
                             
                    if (! operatorFound)
                        throw new IllegalArgumentException();
                    else if (opPosition == 1) {
                        if (text.charAt(1) == '-')
                            return new Negation(parse(text.substring(2, text.length() - 1)));
                        else
                            throw new IllegalArgumentException();
                    }
                    else {
                        Expression left = parse(text.substring(1, opPosition));
                        Expression right = parse(text.substring(opPosition+1, text.length()-1));
                        switch(text.charAt(opPosition)) {
                            case '+': return new Sum(left, right);
                            case '-': return new Difference(left, right);
                            case '*': return new Product(left, right);
                            case '/': return new Quotient(left, right);
                            default: return null; // To keep the compiler happy
                        }
                    }
                }
                else
                    throw new IllegalArgumentException();
                    
            default:
            
                throw new IllegalArgumentException();
        }
    }
    
    /** Display an expression as a tree
     * 
     *  @param expression the expression to display
     */
    public void display(Expression expression) {
        startController();
        expression.draw(0, 0, canvas.getWidth(), canvas);
    }
    
    /** Set the enabled state of the buttons that depend on there being a valid expression
     * 
     *  @param enabled the enabled state to set them to
     */
    private void enableButtons(boolean enabled) {
        toStringButton.setEnabled(enabled);
        evaluateButton.setEnabled(enabled);
        derivativeButton.setEnabled(enabled);
        simplifyButton.setEnabled(enabled);
        originalButton.setEnabled(enabled);
    }
    
    /** Draw the current expression
     */
    private void drawCurrent() {
        canvas.clear();
        currentExpression.draw(0, 5, canvas.getWidth(), canvas);
    }
    
    /** Test to see whether a character is an operator
     * 
     *  @param toTest the character to be tested
     *  @return true if it is one of +, -, *, or /
     */
    private static boolean isOperator(char toTest) {
        return toTest == '+' || toTest == '-' || toTest == '*' || toTest == '/';
    }
    
    private Expression originalExpression, currentExpression;
    private JButton newButton, toStringButton, evaluateButton, 
            derivativeButton, simplifyButton, originalButton;
    
    private static final int WINDOW_WIDTH = 1000;
    private static final int WINDOW_HEIGHT = 700;
}
