/***************************************************************************
 *   Copyright (C) 2009 by Paul Lutus                                      *
 *   lutusp@arachnoid.com                                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
package plcalc;

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

/**
 *
 * @author lutusp
 */
public class Calculator {

    PLCalcMainPanel parent;
    JTextField textStack[];
    Stack<Double> numStack;
    Stack<String> prevStack, nextStack;
    String entryModes[] = {"dd.dddd", "hh mm ss", "hh mm.mmmm", "ff ii ii/ii"};
    String error = "";
    static double degToRad = Math.PI / 180.0;
    static double radToDeg = 180.0 / Math.PI;

    public Calculator(PLCalcMainPanel p) {
        parent = p;
        textStack = new JTextField[]{parent.XTextField, parent.YTextField, parent.ZTextField, parent.TTextField};
        numStack = new Stack<Double>();
        prevStack = new Stack<String>();
        nextStack = new Stack<String>();
        error = "OK";
    }

    

    public void showStackDisp() {
        if (numStack != null) {
            String fmt = parent.getCurrentFormat();
            int i;
            for (i = 0; i < numStack.size() && i < textStack.length; i++) {
                double v = numStack.elementAt(i);
                String ns = parent.specialFormat(fmt, v);
                ns = processSpecialDisplayModes(ns, v);
                textStack[i].setText(ns);
            }
            for (; i < textStack.length; i++) {
                textStack[i].setText("");
            }
        }
        parent.setStatus(error, !error.equals("OK"));
        parent.EntryTextField.requestFocus();
    }

    String processSpecialDisplayModes(String ns, double v) {
        String output = "";
        int mode = parent.InputModeComboBox.getSelectedIndex();
        int h, m, s, d;
        double dm;
        String sign = (v < 0) ? "-" : "";
        v = Math.abs(v);
        switch (mode) {
            case 0: // default decimal display mode
                return ns;
            case 1: // hh mm ss
                v += 0.000138;
                h = (int) v;
                v = (v - h) * 60.0;
                m = (int) v;
                v = (v - m) * 60.0;
                s = (int) v;
                output = String.format(parent.locale, "%dh %dm %ds", h, m, s);
                break;
            case 2: // hh mm.mmmm
                h = (int) v;
                dm = (v - h) * 60.0;
                output = String.format(parent.locale, "%dh %." + "6fm", h, dm);
                break;
            case 3: // ff ii ii/ii
                d = 32; // resolution 1/32 inch
                double roundval = 1.0 / (d * 24);
                v += roundval;
                h = (int) v;
                v = (v - h) * 12.0;
                m = (int) v;
                v = (v - m) * d;
                s = (int) v;
                // reduce fraction
                int gcd = comp_gcd(s, d);
                s /= gcd;
                d /= gcd;
                output = String.format(parent.locale, "%d ft.", h);
                if (m != 0 || s != 0) {
                    if (m != 0) {
                        output += String.format(parent.locale, " %d", m);
                    }
                    if (s != 0) {
                        output += String.format(parent.locale, " %d/%d", s, d);
                    }
                    output += " in.";
                }
                break;
        }
        return ns + " (" + sign + output + ")";
    }

    int comp_gcd(int a, int b) {
        if (b == 0) {
            return a;
        }
        return comp_gcd(b, a % b);
    }

    void clearStack(boolean show) {
        error = "OK";
        numStack = new Stack<Double>();
        if (show) {
            showStackDisp();
        }
    }

    void pushStack(double v) {
        numStack.add(0, v);
    }

    double popStack() {
        if (numStack.size() > 0) {
            return numStack.remove(0);
        } else {
            error = "Not enough entries.";
            return 0.0;
        }
    }

    void processKey(KeyEvent evt) {
        int kcode = evt.getKeyCode();
        //String name = evt.getKey_Text(kcode);
        if (kcode == KeyEvent.VK_UP) {
            if (prevStack.size() > 0) {
                String old = parent.EntryTextField.getText();
                if (old.length() > 0) {
                    nextStack.push(old);
                }
                String s = prevStack.pop();
                parent.EntryTextField.setText(s);
            } else {
                parent.setStatus("No entries.", true);
            }
        } else if (kcode == KeyEvent.VK_DOWN) {
            if (nextStack.size() > 0) {
                String ns = parent.EntryTextField.getText();
                if (ns.length() > 0) {
                    prevStack.push(ns);
                }
                String s = nextStack.pop();
                parent.EntryTextField.setText(s);
            } else {
                parent.setStatus("No entries.", true);
            }
        }
    }

    void enterAction() {
        error = "OK";
        if (parent.activeTab == 0) {
            if (parent.EntryTextField.getText().length() > 0) {
                actOnEntry();
            } else if (numStack.size() > 0) {
                double x = numStack.get(0);
                pushStack(x);
            } else {
                parent.setStatus("No Entry.", true);
            }
            showStackDisp();
        }
    }

    void enterAction(KeyEvent evt) {
        if (evt.getKeyChar() == '\n') {
            enterAction();
        }
    }

    void actOnEntry() {
        error = "OK";
        double v, min, sec, div;
        double sign;
        String s = parent.EntryTextField.getText();
        prevStack.push(s);
        String comArray[] = s.split("\\s+");
        int mode = parent.InputModeComboBox.getSelectedIndex();
        int i = 0;
        while (i < comArray.length) {
            if (testNumeric(comArray[i])) {
                v = parent.getDouble(comArray[i]);
                if (mode == 0) {
                    pushStack(v); // normal decimal entry
                } else { // all special modes allow minutes
                    sign = (v < 0) ? -1.0 : 1.0;
                    v = Math.abs(v);
                    if (i < comArray.length - 1 && testNumeric(comArray[i + 1])) {
                        min = parent.getDouble(comArray[++i]);
                        if (mode != 3) {
                            v += min / 60.0;
                            // test for modes that allow seconds
                            if ((mode == 1) && i < comArray.length - 1 && testNumeric(comArray[i + 1])) {
                                sec = parent.getDouble(comArray[++i]);
                                v += sec / 3600.0;
                            }
                        } else { // mode 3: ff ii ii/ii
                            v += min / 12.0;
                            if (i < comArray.length - 1) {
                                String sf[] = comArray[i + 1].split("/");
                                if (sf.length == 2 && testNumeric(sf[0]) && testNumeric(sf[1])) {
                                    sec = parent.getDouble(sf[0]);
                                    div = parent.getDouble(sf[1]);
                                    v += (sec / div) / 12.0;
                                    i++;
                                }
                            }
                        }
                    }
                    pushStack(sign * v);

                }
            } else { // not a number
                processCommand2(comArray[i]);
            }
            i++;
        }
        parent.EntryTextField.setText("");
    }

    boolean testNumeric(String s) {
        boolean result = false;
        try {
            parent.getDoubleThrowsException(s);
            result = true;
        } catch (Exception e) {
        }
        return result;
    }

    boolean testFor(int n) {

        boolean outcome = (numStack.size() >= n);
        if (!outcome) {
            error = "Not enough entries.";
        }
        return outcome;
    }

    void processCommand2(String s) {
        if (s.length() == 0) {
            showStackDisp();
            return;
        }
        s = s.toLowerCase();
        // That one cannot switch on a string
        // is a sad commentary on Java
        double x, y;
        if ("chs".equals(s)) {
            if (testFor(1)) {
                pushStack(-popStack());
            }
        } else if ("r-d".equals(s)) {
            if (testFor(1)) {
                pushStack(popStack() * radToDeg);
            }
        } else if ("ent".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                pushStack(x);
                pushStack(x);
            }
        } else if ("clr".equals(s)) {
            clearStack(true);
        } else if ("yrx".equals(s)) {
            if (testFor(2)) {
                x = popStack();
                y = popStack();
                pushStack(Math.pow(y, 1.0 / x));
            }
        } else if ("sq".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                pushStack(x * x);
            }
        } else if ("d-r".equals(s)) {
            if (testFor(1)) {
                pushStack(popStack() * degToRad);
            }
        } else if ("pi".equals(s)) {
            pushStack(Math.PI);
        } else if ("e".equals(s)) {
            pushStack(Math.exp(1));
        } else if ("drop".equals(s)) { // means "drop"
            if (testFor(1)) {
                popStack();
            }
        } else if ("1/x".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                if (x == 0.0) {
                    error = "Division by zero.";
                    pushStack(x);
                } else {
                    pushStack(1.0 / x);
                }
            }
        } else if ("swap".equals(s)) {
            if (testFor(2)) {
                x = popStack();
                y = popStack();
                pushStack(x);
                pushStack(y);
            }
        } else if ("+".equals(s)) {
            if (testFor(2)) {
                x = popStack();
                pushStack(popStack() + x);
            }
        } else if ("-".equals(s)) {
            if (testFor(2)) {
                x = popStack();
                pushStack(popStack() - x);
            }
        } else if ("*".equals(s)) {
            if (testFor(2)) {
                x = popStack();
                pushStack(popStack() * x);
            }
        } else if ("/".equals(s)) {
            if (testFor(2)) {
                x = popStack();
                if (x == 0.0) {
                    error = "Division by zero.";
                    pushStack(x);
                } else {
                    pushStack(popStack() / x);
                }
            }
        } else if ("^".equals(s)) {
            if (testFor(2)) {
                x = popStack();
                y = popStack();
                pushStack(Math.pow(y, x));
            }
        } else if ("sin".equals(s)) {
            if (testFor(1)) {
                x = popStack() * degToRad;
                pushStack(Math.sin(x));
            }
        } else if ("cos".equals(s)) {
            if (testFor(1)) {
                x = popStack() * degToRad;
                pushStack(Math.cos(x));
            }
        } else if ("tan".equals(s)) {
            if (testFor(1)) {
                x = popStack() * degToRad;
                pushStack(Math.tan(x));
            }
        } else if ("asin".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                pushStack(Math.asin(x) * radToDeg);
            }
        } else if ("acos".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                pushStack(Math.acos(x) * radToDeg);
            }
        } else if ("atan".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                pushStack(Math.atan(x) * radToDeg);
            }
        } else if ("sqrt".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                if (x < 0.0) {
                    error = "Domain error.";
                    pushStack(x);
                } else {
                    pushStack(Math.sqrt(x));
                }
            }
        } else if ("lnx".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                if (x < 0.0) {
                    error = "Domain error.";
                    pushStack(x);
                } else {
                    pushStack(Math.log(x));
                }
            }
        } else if ("ex".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                pushStack(Math.exp(x));
            }
        } else if ("logx".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                if (x < 0.0) {
                    error = "Domain error.";
                    pushStack(x);
                } else {
                    pushStack(Math.log(x) / Math.log(10));
                }
            }
        } else if ("tenx".equals(s)) {
            if (testFor(1)) {
                x = popStack();
                pushStack(Math.pow(10.0, x));
            }
        }
        showStackDisp();
    }

    void processCommand(String s) {
        error = "OK";
        if (parent.activeTab == 0) {
            actOnEntry();
            processCommand2(s);
        }
    }
}
