/*
 * PostfixCalculator.java  $Revision: 1.1 $  $Date: 2007/10/02 03:51:20 $
 *
 * Copyright (c) Meldcraft, Inc. 2006
 */
package com.meldcraft.postfixcalculator;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.Stack;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

/**
 * Based on ScriptCalc http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/index.html
 * 
 * @version $Revision: 1.1 $ $Date: 2007/10/02 03:51:20 $
 * @author Rob Richards
 */
public class PostfixCalculator
{
    private ScriptEngineManager mEngineManager;
    private Invocable[]         mInvocableEngines;
    private Stack<Number>       mNumStack;
    private StringBuilder       mText = new StringBuilder();
    private boolean             mReset = false;
    private String[]            mScriptNames = {"/scripts/F1.js",
                                                "/scripts/F2.js",
                                                "/scripts/F3.js",
                                                "/scripts/F4.js"
                                               };

    /** Creates a new instance of CalcModel */
    public PostfixCalculator()
    {
        mNumStack = new Stack<Number>();
        initScriptEngines();
    }

    public Number[] getStack()
    {
        return mNumStack.toArray(new Number[0]);
    }

    public String calculate(String item)
    {
        System.err.println("=== " + item);
        if ((item != null) && (item.length() > 0))
        {
            Operation op = null;
            try
            {
                op = Operation.valueOf(item.toUpperCase());
            }
            catch (IllegalArgumentException e)
            {
                // ignored
            }

            if (op != null)
            {
                performOperation(op, mReset ? null : new BigDecimal(mText.toString()));
                mReset = true;
            }
            else if (item.length() == 1)
            {
                String numbers = "0123456789";
                if (numbers.indexOf(item) != -1)
                {
                    if (mReset)
                    {
                        mReset = false;
                        mText.setLength(0);
                    }
                    mText.append(item);
                }
                else if (item.equals("."))
                {
                    if (mReset)
                    {
                        mReset = false;
                        mText.setLength(0);
                    }
                    else
                    {
                        int idx = mText.indexOf(item);
                        if (idx != -1)
                        {
                            mText.delete(idx, idx+1);
                            
                        }
                    }
                    mText.append(item);
                }
            }
        }
System.err.println(mNumStack);
        return mText.toString();
    }

    protected void performOperation(Operation operation, Number number)
    {
        Double result = null;
        double op1 = Double.NaN;
        double op2 = Double.NaN;
        switch (operation)
        {

        case ADD:
            if (number != null)
            {
                mNumStack.push(number);
            }
            if (mNumStack.size() >= 2)
            {
                op2 = mNumStack.pop().doubleValue();
                op1 = mNumStack.pop().doubleValue();
                result = op1 + op2;
                mNumStack.push(result);
            }
            break;
        case SUBTRACT:
            if (number != null)
            {
                mNumStack.push(number);
            }

            if (mNumStack.size() >= 2)
            {
                op2 = mNumStack.pop().doubleValue();
                op1 = mNumStack.pop().doubleValue();
                result = op1 - op2;
                mNumStack.push(result);
            }
            break;
        case MULTIPLY:
            if (number != null)
            {
                mNumStack.push(number);
            }
            if (mNumStack.size() >= 2)
            {
                op2 = mNumStack.pop().doubleValue();
                op1 = mNumStack.pop().doubleValue();
                result = op1 * op2;
                mNumStack.push(result);
            }
            break;
        case DIVIDE:
            if (number != null)
            {
                mNumStack.push(number);
            }
            if (mNumStack.size() >= 2)
            {
                op2 = mNumStack.pop().doubleValue();
                op1 = mNumStack.pop().doubleValue();
                result = op1 / op2;
                mNumStack.push(result);
            }
            break;

        case PEEK:
            break;
        case POP:
            break;
        case ENTER:
            mNumStack.push(number);
            break;
        case CLEAR:
            result = 0.0;
            mNumStack.clear();
            break;

        case F1:
            result = executeUserFunction(0);
            break;
        case F2:
            result = executeUserFunction(1);
            break;
        case F3:
            result = executeUserFunction(2);
            break;
        case F4:
            result = executeUserFunction(3);
            break;

        }

        if (result != null)
        {
            mText.setLength(0);
            mText.append(result);
        }
    }

    private Double executeUserFunction(int funcNumber)
    {
        Double result = null;

        try
        {
            Invocable invocableEngine = mInvocableEngines[funcNumber];
            if (invocableEngine != null)
            {
                result = (Double) invocableEngine.invokeFunction("calculate", mNumStack);
            }
        }
        catch (ScriptException ex)
        {
            System.out.println("Error in script");
        }
        catch (NoSuchMethodException ex)
        {
            System.out.println("Script does not contain 'calculate' function.");
        }
        catch (NullPointerException npe)
        {
            System.out.println("Script does not exist.");
        }
        return result;
    }

    private void initScriptEngines()
    {
        mEngineManager = new ScriptEngineManager();
        mInvocableEngines = new Invocable[mScriptNames.length];
        for (int x = 0; x < mInvocableEngines.length; ++x)
        {
            InputStream is = this.getClass().getResourceAsStream(mScriptNames[x]);
            if (is == null)
            {
                continue;
            }
            try
            {
                Reader reader = new InputStreamReader(is);
                ScriptEngine engine = mEngineManager.getEngineByName("ECMAScript");
                engine.eval(reader);
                mInvocableEngines[x] = (Invocable) engine;
            }
            catch (ScriptException ex)
            {
                System.out.printf("There is an error in script: %s\n", mScriptNames[x]);
            }
        }
    }

    public static enum Operation
    {
        ADD, SUBTRACT, MULTIPLY, DIVIDE, ENTER, POP, PEEK, CLEAR, F1, F2, F3, F4
    }
}
