/****************************************************************************
 **
 ** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
 **
 ** This file is part of Qt Jambi.
 **
 ** ** This file may be used under the terms of the GNU General Public
** License version 2.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of
** this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
** http://www.trolltech.com/products/qt/opensource.html
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://www.trolltech.com/products/qt/licensing.html or contact the
** sales department at sales@trolltech.com.

 **
 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 **
 ****************************************************************************/

package com.trolltech.examples;

import java.util.*;
import com.trolltech.qt.gui.*;

@QtJambiExample(name = "Calculator")
public class Calculator extends QMainWindow {

    public static void main(String[] args) {

        QApplication.initialize(args);
        Calculator calculator = new Calculator();
        calculator.show();
        QApplication.exec();
    }

    private QLineEdit lineEdit;
    private QTextBrowser textBrowser;

    private Vector<Function> infixFunctions = new Vector<Function>();
    private Hashtable<String, Function> functions = new Hashtable<String, 
                                                                  Function>();

    public Calculator() {
        Vector<String> uiTypes = new Vector<String>(3);
        uiTypes.add("Simple");
        uiTypes.add("Normal");
        uiTypes.add("Dockable");

        setWindowIcon(new QIcon("classpath:com/trolltech/images/qt-logo.png"));
                
        String item = QInputDialog.getItem(this, tr("Ui selector"), 
                                           tr("Ui configurations:"), uiTypes,
                                           0, false);
                
        if (item == null || item.equals("Simple")) {
            Ui_CalculatorSimple uiSimple = new Ui_CalculatorSimple();
            uiSimple.setupUi(this);
            lineEdit = uiSimple.lineEdit;
            textBrowser = uiSimple.textBrowser;
        } else if (item.equals("Normal")) {
            Ui_CalculatorNormal uiNormal = new Ui_CalculatorNormal();
            uiNormal.setupUi(this);
            lineEdit = uiNormal.lineEdit;
            textBrowser = uiNormal.textBrowser;
        } else if (item.equals("Dockable")) {
            Ui_CalculatorDockable uiDockable = new Ui_CalculatorDockable();
            uiDockable.setupUi(this);
            lineEdit = uiDockable.lineEdit;
            textBrowser = uiDockable.textBrowser;
        }
        

        Function function= new Function("abs") {
            public double result(double[] args) throws ParseException {
                checkNumberOfArguments(1,args);
                return Math.abs(args[0]);
            }
        };
        functions.put(function.name, function);

        function= new Function("pow") {
            public double result(double[] args) throws ParseException {
                checkNumberOfArguments(2,args);
                return Math.pow(args[0], args[1]);
            }
        };
        functions.put(function.name, function);

        function= new Function("cos") {
            public double result(double[] args) throws ParseException {
                checkNumberOfArguments(1,args);
                return Math.cos(args[0]);
            }
        };

        functions.put(function.name, function);

        function= new Function("sin") {
            public double result(double[] args) throws ParseException {
                checkNumberOfArguments(1,args);
                return Math.sin(args[0]);
            }
        };

        functions.put(function.name, function);

        function= new Function("random") {
            public double result(double[] args) throws ParseException {
                checkNumberOfArguments(0,args);
                return Math.random();
            }
        };
        functions.put(function.name, function);

        function= new Function("min") {
            public double result(double[] args) {
                double minimum = args[0];
                for (int i = 1; i < args.length; i++) {
                    minimum = Math.min(minimum, args[i]);
                }
                return minimum;
            }
        };
        functions.put(function.name, function);

        infixFunctions.add(new Function("*") {
            public double result(double[] args) {
                double product = args[0];
                for (int i = 1; i < args.length; i++) {
                    product *= args[i];
                }
                return product;
            }
        });
        infixFunctions.add(new Function("/") {
            public double result(double[] args) {
                double quotient = args[0];
                for (int i = 1; i < args.length; i++) {
                    quotient /= args[i];
                }
                return quotient;
            }
        });

        infixFunctions.add(new Function("-") {
            public double result(double[] args) {
                double difference = args[0];
                for (int i = 1; i < args.length; i++) {
                    difference -= args[i];
                }
                return difference;
            }
        });
        
        infixFunctions.add(new Function("+") {
            public double result(double[] args) {
                double sum = 0;
                for (int i = 0; i < args.length; i++) {
                    sum += args[i];
                }
                return sum;
            }
        });
    }

    public void on_button_equal_clicked() {
        String expression = lineEdit.text();
        String result = "";
        boolean error = false;
        try {
            result = Double.toString(evaluate(parse(expression)));
        } catch (ParseException exception) {
            result = "Error: <font color=\"red\">" 
                     + exception.getMessage() + "</font>";
            error = true;
        }

        textBrowser.append(expression + "<b> = " + result + "</b><br>");
        if (error)
            result = expression;
        lineEdit.setText(result);
    }

    public void type(String s) {
        lineEdit.setText(lineEdit.text() + s);
    }

    public void typeAround(String s) {
        lineEdit.setText(s + "(" + lineEdit.text() + ")");
    }

    public void on_button_1_clicked() {
        type("1");
    }

    public void on_button_2_clicked() {
        type("2");
    }

    public void on_button_3_clicked() {
        type("3");
    }

    public void on_button_4_clicked() {
        type("4");
    }

    public void on_button_5_clicked() {
        type("5");
    }

    public void on_button_6_clicked() {
        type("6");
    }

    public void on_button_7_clicked() {
        type("7");
    }

    public void on_button_8_clicked() {
        type("8");
    }

    public void on_button_9_clicked() {
        type("9");
    }

    public void on_button_0_clicked() {
        type("0");
    }

    public void on_button_add_clicked() {
        type("+");
    }

    public void on_button_subtract_clicked() {
        type("-");
    }

    public void on_button_multiply_clicked() {
        type("*");
    }

    public void on_button_devide_clicked() {
        type("/");
    }

    public void on_button_comma_clicked() {
        type(".");
    }

    public void on_button_left_clicked() {
        type("(");
    }

    public void on_button_right_clicked() {
        type(")");
    }

    public void on_button_sin_clicked() {
        typeAround("sin");
    }

    public void on_button_cos_clicked() {
        typeAround("cos");
    }
    
    public void on_button_random_clicked() {
        type("random()");
    }

    public void on_button_functions_clicked() {
        Vector<String> functionKeys = new Vector<String>(functions.size());
        functionKeys.addAll(functions.keySet());

        String key = QInputDialog.getItem(this, tr("Function selector"), 
                                          tr("Available functions:"), 
                                          functionKeys, 0, false);
        if (key != null) 
            type(key);
    }

    public double evaluate(Object o) throws ParseException {
        double result = 0;
        if (o instanceof Vector) {
            Vector vector = (Vector) o;
            if (vector.isEmpty())
                return 0;
            return evaluate(vector.firstElement());
        }

        else if (o instanceof Function) {
            Function function= (Function) o;
            result = function.evaluateFunction();
        }

        else if (o instanceof String) {
            try {
                result = Double.parseDouble((String) o);
            } catch (NumberFormatException exception) {
                throw new ParseException(exception.getMessage());
            }
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    public Vector parse(String expression) throws ParseException {
        Stack<Vector> stack = new Stack<Vector>();
        stack.push(new Vector());

        String delimiter = "()";
        for (Iterator iterator = infixFunctions.iterator(); iterator.hasNext();) {
            Function function= (Function) iterator.next();
            delimiter += function.name;

        }
        StringTokenizer tokenizer = new StringTokenizer(expression.trim(), delimiter, true);

        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if (token.equals("(")) {
                stack.peek().add(new Vector());
                stack.push((Vector) stack.peek().lastElement());
            } else if (token.equals(")")) {
                if (stack.isEmpty())
                    throw new ParseException("Missing starting parenthesis");
                prioritize(stack.pop());

            } else {
                String[] tmp = token.trim().split(" ");
                for (int i = 0; i < tmp.length; i++) {
                    if (stack.peek() == null) {
                        throw new ParseException("Missing left side parenthesis");
                    }
                    if(!tmp[i].equals(""))
                        stack.peek().add(tmp[i]);
                }
            }
        }
        if(stack.isEmpty())
            throw new ParseException("Not enough staring parantheses");
        prioritize(stack.peek());
        if (stack.size() > 1)
            throw new ParseException("Not enough closing parentheses");
        return stack.peek();
    }

    @SuppressWarnings("unchecked")
    private void prioritize(Vector vector) throws ParseException {

        Function unaryMinusProt = new Function("unaryMinus") {
            public double result(double[] args) {
                return -args[0];
            }
        };

        Object[] vectorArray = vector.toArray();
        int r = 0;
        for (int i = 0; i < vectorArray.length; i++) {
            Function function= functions.get(
                    vectorArray[i].toString().toUpperCase());

            if (function!= null) {
                function= (Function) function.clone();
                if((i - r + 1) >= vectorArray.length)
                    throw new ParseException(
                            "Could not find parameters for function: " 
                            + function.name);
                if (vectorArray[i - r + 1] instanceof Vector)
                    function.arguments.addAll((Vector) vectorArray[i - r + 1]);
                vector.remove(i - r + 1);
                vector.set(i - r, function);
                r += 1;
            }
        }

        for (Iterator iterator = infixFunctions.iterator(); iterator.hasNext();) {
            Function function= (Function) iterator.next();

            vectorArray = vector.toArray();

            r = 0;
            for (int i = 0; i < vectorArray.length; i++) {
                Object element = vectorArray[i];
                if (element instanceof String) {
                    if (element.equals(function.name)) {
                        function= (Function) function.clone();
                        if ((i - r - 1 < 0 && !element.equals("-")) 
                            || i - r + 1 >= vector.size())
                            throw new ParseException(
                                    "Problems at infix function:" 
                                     + function.name);

                        if (i - r - 1 < 0) {
                            Function minus = (Function) unaryMinusProt.clone();
                            minus.arguments.add(vector.elementAt(i - r + 1));
                            vector.set(i - r, minus);
                            vector.remove(i - r + 1);
                            r += 1;
                            i++;
                        } else {
                            function.arguments.add(vector.elementAt(i - r - 1));

                            if (vector.elementAt(i - r + 1).equals("-")) {

                                Function minus = (Function) unaryMinusProt.clone();
                                minus.arguments.add(vector.elementAt(i - r + 2));
                                function.arguments.add(minus);

                                vector.set(i - r, function);
                                vector.remove(i - r + 2);
                                vector.remove(i - r + 1);
                                vector.remove(i - r - 1);
                                r += 3;
                                i++;

                            } else {

                                function.arguments.add(vector.elementAt(i - r + 1));

                                vector.set(i - r, function);
                                vector.remove(i - r + 1);
                                vector.remove(i - r - 1);
                                r += 2;
                                i++;
                            }
                        }
                    }
                }
            }
        }
    }

    private abstract class Function implements Cloneable {
        Vector arguments;
        String name;

        public Function(String name) {
            this.name = name.toUpperCase();
        }

        public String toString() {
            String signature = "";
            signature += "{function_" + name + "_ ";
            for (Iterator iterator = arguments.iterator(); iterator.hasNext();) {
                signature += iterator.next();
                if (iterator.hasNext())
                    signature += " ";
            }
            signature += "}";
            return signature;
        }

        protected Object clone() {
            Function function= null;
            try {
                function = (Function) super.clone();
                function.arguments = new Vector();
            } catch (CloneNotSupportedException exception) {
                exception.printStackTrace();
            }

            return function;
        }

        public double evaluateFunction() throws ParseException {
            double[] args = new double[arguments.size()];
            int i = 0;
            for (Iterator iterator = arguments.iterator(); iterator.hasNext();) {
                args[i] = evaluate(iterator.next());
                i++;
            }
            return result(args);
        }
        
        protected void checkNumberOfArguments(int size, double[] args) throws ParseException {
            if(args.length!=size)
                throw new ParseException("Wrong number of arguments to function " + name + ": Expected " + size + ".");
        }
        
        public abstract double result(double[] args) throws ParseException;
    }

    class ParseException extends Exception {
        private static final long serialVersionUID = 1L;

        public ParseException(String error) {
            super(error);
        }
    }
}
