/*****************************************************************************

 JEP 2.4.1, Extensions 1.1.1
      April 30 2007
      (c) Copyright 2007, Nathan Funk and Richard Morris
      See LICENSE-*.txt for license information.

 *****************************************************************************/

package org.nfunk.jep;


import java.util.*;

import org.nfunk.jep.function.*;


/**
 * This class is used for the evaluation of an expression. It uses the Visitor
 * design pattern to traverse the function tree and evaluate the expression
 * using a stack.
 * <p>
 * Function nodes are evaluated by first evaluating all the children nodes, then
 * applying the function class associated with the node. Variable and constant
 * nodes are evaluated by pushing their value onto the stack.
 * <p>
 * Some changes implemented by rjm. Nov 03. Added hook to SpecialEvaluationI.
 * Clears stack before evaluation. Simplifies error handling by making visit
 * methods throw ParseException. Changed visit(ASTVarNode node) so messages not
 * calculated every time.
 */
public class EvaluatorVisitor implements ParserVisitor, EvaluatorI {
	/** Stack used for evaluating the expression */
	protected Stack stack;

	/** The current error list */
	//protected Vector errorList;

	/** The symbol table for variable lookup */
	protected SymbolTable symTab;

	/** Flag for errors during evaluation */
	//protected boolean errorFlag;

	/** Debug flag */
	protected static final boolean debug = false;

	/** TrapNull **/
	protected boolean trapNullValues = true;

	/** Constructor. Initialise the stack member */
	public EvaluatorVisitor() {
		//errorList = null;
		symTab = null;
		stack = new Stack();
	}

	/**
	 * Adds an error message to the list of errors
	 */
	/*protected void addToErrorList(String errorStr) {
		if (errorList != null) {
			errorList.addElement(errorStr);
		}
	}*/

	/**
	 * Returns the value of the expression as an object. The expression tree is
	 * specified with its top node. The algorithm uses a stack for evaluation.
	 * <p>
	 * The symTab parameter can be null, if no variables are expected in the
	 * expression. If a variable is found, an error is added to the error list.
	 * <p>
	 * An exception is thrown, if an error occurs during evaluation.
	 * 
	 * @return The value of the expression as an object.
	 * @throws ParseException if there is a problem with the evaluation.
	 */
	public Object getValue(Node topNode, SymbolTable symTab_in) throws ParseException {

		// check if arguments are ok
		if (topNode == null) {
			throw new ParseException("topNode parameter is null");
		}

		// set member vars
		//errorList = errorList_in;
		symTab = symTab_in;
		//errorFlag = false;
		stack.removeAllElements();
		// rjm addition ensure stack is correct before beginning.
		// njf changed from clear() to removeAllElements for 1.1 compatibility

		// evaluate by letting the top node accept the visitor
		topNode.jjtAccept(this, null);
		/*
		} catch (ParseException e) {
			this.addToErrorList("Error: "+e.getMessage());
			return null;
		}
		if(errorFlag) return null;
		*/

		// something is wrong if not exactly one item remains on the stack
		// or if the error flag has been set
		if (stack.size() != 1) {
			throw new ParseException("Stack corrupted");
		}

		// return the value of the expression
		return stack.pop();
	}

	/*
	 * The following methods was used to facilitate 
	 * using visitors which implemented a interface
	 * which sub-classed ParserVisitor.
	 *  
	 * If sub-classed to extend to implement a different visitor
	 * this method should be overwritten to ensure the correct 
	 * accept method is called.
	 * This method simply calls the jjtAccept(ParserVisitor this,Object data) of node.
	 *
	 * We no longer need this as we use ParseVisitor everywhere,
	 * but kept for future reference.
	 * 
	private Object nodeAccept(Node node, Object data) throws ParseException
	{
		return node.jjtAccept(this,data);
	}
	*/

	/**
	 * Evaluates a given node, in the current context.
	 * 
	 * @param node The node to evaluate
	 * @return result of the evaluation
	 */
	@Override
	public Object eval(Node node) throws ParseException {
		node.jjtAccept(this, null);
		return stack.pop();
	}

	/**
	 * Evaluates a PostfixMathCommandI with given arguments. Not used in normal
	 * use.
	 * 
	 * @param pfmc the command to evaluate.
	 * @param children the parameters to the function.
	 * @return the value of the function
	 * @throws ParseException
	 */
	public Object eval(PostfixMathCommandI pfmc, Node children[]) throws ParseException {
		if (pfmc instanceof SpecialEvaluationI) {
			ASTFunNode node = new ASTFunNode(ParserTreeConstants.JJTFUNNODE);
			node.setFunction("TmpFun", pfmc);
			node.jjtOpen();
			for (int i = 0; i < children.length; ++i)
				node.jjtAddChild(children[i], i);
			node.jjtClose();
			return ((SpecialEvaluationI) pfmc).evaluate(node, null, this, new Stack(), this.symTab);
		}
		if (pfmc instanceof CallbackEvaluationI) {
			ASTFunNode node = new ASTFunNode(ParserTreeConstants.JJTFUNNODE);
			node.setFunction("TmpFun", pfmc);
			node.jjtOpen();
			for (int i = 0; i < children.length; ++i)
				node.jjtAddChild(children[i], i);
			node.jjtClose();
			Object val = ((CallbackEvaluationI) pfmc).evaluate(node, this);
			return val;
		}

		Stack lstack = new Stack();
		for (int i = 0; i < children.length; ++i) {
			if (!(children[i] instanceof ASTConstant)) throw new ParseException("buildConstantNode: arguments must all be constant nodes");
			lstack.push(((ASTConstant) children[i]).getValue());
		}
		pfmc.setCurNumberOfParameters(children.length);
		pfmc.run(lstack);
		return lstack.pop();
	}

	/**
	 * This method should never be called when evaluation a normal expression.
	 */
	@Override
	public Object visit(SimpleNode node, Object data) throws ParseException {
		throw new ParseException("No visit method for " + node.getClass().getName());
	}

	/**
	 * This method should never be called when evaluating a normal expression.
	 */
	@Override
	public Object visit(ASTStart node, Object data) throws ParseException {
		throw new ParseException("Start node encountered during evaluation");
	}

	/**
	 * Visit a function node. The values of the child nodes are first pushed
	 * onto the stack. Then the function class associated with the node is used
	 * to evaluate the function.
	 * <p>
	 * If a function implements SpecialEvaluationI then the evaluate method of
	 * PFMC is called.
	 */
	@Override
	public Object visit(ASTFunNode node, Object data) throws ParseException {

		if (node == null) return null;
		PostfixMathCommandI pfmc = node.getPFMC();

		// check if the function class is set
		if (pfmc == null) throw new ParseException("No function class associated with " + node.getName());

		// Some operators (=) need a special method for evaluation
		// as the pfmc.run method does not have enough information
		// in such cases we call the evaluate method which passes
		// all available info. Note evaluating the children is
		// the responsibility of the evaluate method. 
		if (pfmc instanceof SpecialEvaluationI) {
			return ((SpecialEvaluationI) pfmc).evaluate(node, data, this, stack, this.symTab);
		}
		if (pfmc instanceof CallbackEvaluationI) {
			Object val = ((CallbackEvaluationI) pfmc).evaluate(node, this);
			stack.push(val);
			return val;
		}
		if (debug == true) {
			System.out.println("Stack size before childrenAccept: " + stack.size());
		}

		// evaluate all children (each leaves their result on the stack)

		data = node.childrenAccept(this, data);

		if (debug == true) {
			System.out.println("Stack size after childrenAccept: " + stack.size());
		}

		if (pfmc.getNumberOfParameters() == -1) {
			// need to tell the class how many parameters it can take off
			// the stack because it accepts a variable number of params
			pfmc.setCurNumberOfParameters(node.jjtGetNumChildren());
		}

		// try to run the function

		pfmc.run(stack);

		if (debug == true) {
			System.out.println("Stack size after run: " + stack.size());
		}

		return data;
	}

	/**
	 * Visit a variable node. The value of the variable is obtained from the
	 * symbol table (symTab) and pushed onto the stack.
	 */
	@Override
	public Object visit(ASTVarNode node, Object data) throws ParseException {

		// old code
		//		if (symTab == null)
		//			throw new ParseException(message += "the symbol table is null");

		// Optimise (table lookup is costly?)
		//		Object temp = symTab.get(node.getName());

		// new code

		// try to get the variable object
		Variable var = node.getVar();
		if (var == null) {
			String message = "Could not evaluate " + node.getName() + ": ";
			throw new ParseException(message + "the variable was not found in the symbol table");
		}

		// get the variable value
		Object temp = var.getValue();

		if (trapNullValues && temp == null) {
			String message = "Could not evaluate " + node.getName() + ": ";
			throw new ParseException(message + "variable not set");
		}
		// all is fine
		// push the value on the stack
		stack.push(temp);
		return data;
	}

	/**
	 * Visit a constant node. The value of the constant is pushed onto the
	 * stack.
	 */
	@Override
	public Object visit(ASTConstant node, Object data) {
		stack.push(node.getValue());
		return data;
	}


	/**
	 * Tests whether null variable values are trapped by evaluator.
	 * 
	 * @return true is nulls are trapped
	 */
	public boolean isTrapNullValues() {
		return trapNullValues;
	}

	/**
	 * Sets the behaviour when a variable's value is null. If true an exception
	 * will be thrown is a variable value is null. If false then the value will
	 * be passed to other functions, this may cause error else where.
	 * 
	 * @param b
	 */
	public void setTrapNullValues(boolean b) {
		trapNullValues = b;
	}

}
