/*
 * FormulaInterpreter.java
 *
 * Created on August 19, 2013, 3:30 PM
 */

package xal.tools.formula;

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


/**
 * FormulaInterpreter is a class used to evaluate a formula with a given set of variables provided by the user.
 * @author  tap
 */
final public class FormulaInterpreter {
	/** standard script header */
	static final private String STANDARD_SCRIPT_HEADER;

	/** script engine to perform the formula evaluation */
	final private ScriptEngine SCRIPT_ENGINE;


	// static initializer
	static {
		STANDARD_SCRIPT_HEADER = generateStandardScriptHeader();
	}

	
    /** Creates a new instance of FormulaInterpreter */
    public FormulaInterpreter() {
        SCRIPT_ENGINE = new ScriptEngineManager().getEngineByName( "JavaScript" );		// standard scrip engine shipped with Java
		if ( SCRIPT_ENGINE == null ) {
			System.err.println( "Error: JavaScript engine is missing!" );
			throw new RuntimeException( "JavaScript engine is missing and needed for the Formula Interpreter!" );
		}
		try {
			SCRIPT_ENGINE.eval( STANDARD_SCRIPT_HEADER );
		}
		catch ( ScriptException exception ) {
			throw new RuntimeException( exception );
		}
    }


	/** getnerate the standard script header */
	static private String generateStandardScriptHeader() {
		final StringBuffer buffer = new StringBuffer();
		appendMappedMathFunctionToGlobal( "min", buffer );
		appendMappedMathFunctionToGlobal( "max", buffer );
		appendMappedMathFunctionToGlobal( "abs", buffer );
		appendMappedMathFunctionToGlobal( "pow", buffer );
		appendMappedMathFunctionToGlobal( "exp", buffer );
		appendMappedMathFunctionToGlobal( "log", buffer );
		appendMappedMathFunctionToGlobal( "sin", buffer );
		appendMappedMathFunctionToGlobal( "cos", buffer );
		appendMappedMathFunctionToGlobal( "tan", buffer );
		appendMappedMathFunctionToGlobal( "asin", buffer );
		appendMappedMathFunctionToGlobal( "acos", buffer );
		appendMappedMathFunctionToGlobal( "atan", buffer );
		return buffer.toString();
	}


	/** append the mapped math function to the specified buffer */
	static private void appendMappedMathFunctionToGlobal( final String name, final StringBuffer buffer ) {
		buffer.append( mapMathFunctionToGlobal( name ) );
	}


	/** Map the Math function to a global function */
	static private String mapMathFunctionToGlobal( final String name ) {
		return "var " + name + " = Math." + name + "; ";
	}
    
        
    /** 
     * Set the named variable to the specified value.  Overrides the previous value (if any) of the variable.
     * @param name      name of the variable
     * @param value     value assigned to the variable
     */
    final public void setVariable( final String name, final double value ) {
        SCRIPT_ENGINE.put( name, value );
    }
    
    
    /**
     * Determine if a variable of the specified name exists and has been assigned a value.
     * @param name The name of the variable for which to check.
     * @return true if the variable exists; false otherwise.
     */
    final public boolean hasVariable( final String name ) {
        return SCRIPT_ENGINE.get( name ) != null;
    }
    
    
    /** 
     * Evaluate the specified formula.
     * @return The result of evaluating the formula with the present variable values.
     */
    final public double evaluate( final String formula ) {
		try {
			final Object result = SCRIPT_ENGINE.eval( formula );
			if ( result instanceof Number ) {
				return ((Number)result).doubleValue();
			}
			else {
				throw new RuntimeException( "Formula does not evaluate to a number." );
			}
		}
		catch ( ScriptException exception ) {
			throw new RuntimeException( exception );
		}
    }
}

@@ -573,16 +573,71 @@
 NGINE;%0A%0A
+%09/** formula to evaluate */%0A%09private String _formula;%0A%0A
 %0A%09// sta
@@ -1151,16 +1151,17 @@
  );%0A%09%09%7D%0A
+%0A
 %09%09try %7B%0A
@@ -1205,24 +1205,42 @@
 T_HEADER );%0A
+%09%09%09_formula = %22%22;%0A
 %09%09%7D%0A%09%09catch 
@@ -3266,32 +3266,144 @@
  != null;%0A    %7D%0A
+%0A%0A%09/** compile the specified formula */%0A%09public void compile( final String formula ) %7B%0A%09%09_formula = formula;%0A%09%7D%0A
     %0A    %0A    /*
@@ -3412,17 +3412,29 @@
 %0A     * 
-E
+Compile and e
 valuate 
@@ -3550,30 +3550,24 @@
      */%0A    
-final 
 public doubl
@@ -3576,16 +3576,298 @@
 evaluate
+( final String formula ) %7B%0A%09%09compile( formula );%0A%09%09return performEvaluation( formula );%0A    %7D%0A%0A%0A%09/** evaluate the compiled formula */%0A%09public double evaluate() %7B%0A%09%09return performEvaluation( _formula );%0A%09%7D%0A%0A%0A%09/** perform the evaluation internally */%0A%09private double performEvaluation
 ( final 
@@ -4217,16 +4217,13 @@
  );%0A%09%09%7D%0A
-    
+%09
 %7D%0A%7D%0A

Update the formula interpreter to have methods found in the old interpreter.