/*
 *  Draco Econometrics
 *  Copyright (C) 2006-2007 Approximatrix, LLC
 *  http://www.approximatrix.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 3 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, see <http://www.gnu.org/licenses/>.
 */

package com.approximatrix.draco.math;


import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import com.approximatrix.draco.datamodel.Constant;
import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.math.mesp.psuedologic.PseudoLogic;
import com.graphbuilder.math.Expression;
import com.graphbuilder.math.ExpressionTree;
import com.graphbuilder.math.ExpressionParseException;
import com.graphbuilder.math.VarMap;
import com.graphbuilder.math.FuncMap;

import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.bsf.util.CodeBuffer;

public class EquationEvaluation {
	
	private static final String SCRIPT_EXPR_BEAN = "expr";
	private static final String SCRIPT_MODEL_BEAN = "model";
	private static final String SCRIPT_RESULT_BEAN = "result";
	
	private static final String EXPRESSION_LANG = "ruby";
	private static final String EXPRESSION_SCRIPT = "/expression/expression.rb";
		
	private static final String INDEX_TERM = "i";
	private static final String MATH_OPERATORS = "+-*/()";
	
	private static final String BACK_COMMAND = "back";
	private static final String FORWARD_COMMAND = "forward";
	private static final Number DEFAULT_BACK_VALUE = null; // new Double(0.0);

    private static CodeBuffer EvalScript = null;
		
	private StatTableModel current_model = null;
	private String eqn_string;
		
	private Number[] results = null;
	
	private BSFManager scriptManager = null;
	
	public static void Compile() {
	    String script = LoadExpressionScript();
	    BSFManager tempManager = new BSFManager();
	    EvalScript = new CodeBuffer();
	    try {
	        tempManager.compileScript(EXPRESSION_LANG,"(java)",1,1,script,EvalScript);
	    } catch(BSFException be) {
	        System.err.println("WARN: Failed to compile eqn eval script");
	        be.printStackTrace();
	        EvalScript = null;
	    }
	}

	private static String LoadExpressionScript() {
		InputStream is = EquationEvaluation.class.getResourceAsStream(EXPRESSION_SCRIPT);
		StringBuffer buffer = new StringBuffer();
		try {
			int b;
			while((b = is.read())>0) {
				buffer.append((char)b);
			}
			is.close();
		} catch (IOException ioe) {
			// Nothing for now
		}
		return buffer.toString();
	}

	
	public EquationEvaluation(StatTableModel information, String unprocessed_eqn) {
			
		// Set the statmodel
		current_model = information;			
		eqn_string = unprocessed_eqn;
		
		scriptManager = new BSFManager();
	}
						
	private String adjust_back_forward_values(String the_equation, int current_index) {
		
		while(the_equation.indexOf(BACK_COMMAND) != -1 || 
			  the_equation.indexOf(FORWARD_COMMAND) != -1) {
			
			int back_command_loc = the_equation.indexOf(BACK_COMMAND);
			int forward_command_loc = the_equation.indexOf(FORWARD_COMMAND);
			if(back_command_loc == -1) back_command_loc = the_equation.length()+10;
			if(forward_command_loc == -1) forward_command_loc = the_equation.length()+10;
			int command_loc = Math.min(forward_command_loc,back_command_loc);
			String the_command = BACK_COMMAND;
			if(command_loc == forward_command_loc)
				the_command =  FORWARD_COMMAND;
				
			StringBuffer new_string = new StringBuffer(the_equation.substring(0,command_loc));
				
			// Get variable and back size
			int start_var = command_loc+the_command.length()+1;
			int end_var = start_var + the_equation.substring(start_var).indexOf(',');
			int start_order = end_var + 1;
			int end_order = start_order + the_equation.substring(start_order).indexOf(')');

			String variable = the_equation.substring(start_var,end_var);
			int order = new Integer(the_equation.substring(start_order,end_order)).intValue();
			
			// Get the value (or zero if appropriate)
			int wanted_val = current_index - order;
			if(the_command.compareTo(FORWARD_COMMAND) == 0)
				wanted_val = current_index + order;
				
			Number the_value = DEFAULT_BACK_VALUE;
				
			if(wanted_val >= 0 && wanted_val < current_model.getRowCount()) {
				int var_column = current_model.getColumnNumber(variable);

				if(var_column == -1) {
					return null;
				}
				
				if(current_model.getValueAt(wanted_val,var_column) == null) {
					return null;
				}
				
				// We can access this value
				the_value = ((Number)current_model.getValueAt(wanted_val,var_column));
					
			}
			
			// If the value is null, return null
			if(the_value == null)
				return null;
			
			// Reinsert the value into the equation
			new_string.append("(");
			new_string.append(the_value.toString());
			new_string.append(")");
			
			// Add the remaining equation
			int end_eqn_pos = end_order+1;
			if(end_eqn_pos < the_equation.length()) {
				new_string.append(the_equation.substring(end_eqn_pos));
			}
								
			// Reset the eqn_string
			the_equation = new_string.toString();
		}
		
		return the_equation;
	}
	
	protected void initializeBeans(String expr, ArrayList results) {
		try {
			scriptManager.declareBean(SCRIPT_EXPR_BEAN, expr, String.class);
			scriptManager.declareBean(SCRIPT_MODEL_BEAN, this.current_model, StatTableModel.class);
			scriptManager.declareBean(SCRIPT_RESULT_BEAN, results, ArrayList.class);
		} catch(BSFException be) {
			// nothing for now
		}
	}
	
	protected void cleanupBeans() {
		try {
			scriptManager.undeclareBean(SCRIPT_EXPR_BEAN);
			scriptManager.undeclareBean(SCRIPT_MODEL_BEAN);
			scriptManager.undeclareBean(SCRIPT_RESULT_BEAN);
		} catch(BSFException be) {
			// nothing for now
		}
	}
		
	public boolean processValues() {
	    Object script = EvalScript;
	    if(script == null) 
    		script = LoadExpressionScript(); 
    		
		ArrayList results_list = new ArrayList(current_model.getRowCount());
		
		if(script == null) return false;
		
		boolean ret = true;
		initializeBeans(eqn_string,results_list);
		try {
			scriptManager.exec(EXPRESSION_LANG, "(java)", 1, 1, script);
		} catch(BSFException be) {
			results_list.clear();
			be.printStackTrace();
			ret = false;
		} finally {
			cleanupBeans();
		}
		
		if(results_list.size() > 0) {
			results = new Double[current_model.getRowCount()];
			for(int i=0;i<results_list.size();i++) {
				Object val = results_list.get(i);
				if(val instanceof Number)
					results[i] = (Number)val;
				else
					results[i] = null;
			}
		}
		
		return ret;
	}
	
	public boolean mespProcessValues() {
		results = new Double[current_model.getRowCount()];
		boolean return_val = true;
			
		// Build a separate eqn for each row, replacing
		// column names with numbers
		for(int i=0;i<results.length;i++) {
				
			// update back values
			String adjusted_eqn = adjust_back_forward_values(eqn_string.toString(),i);
			
			// If adjusted_eqn is null, then the equation could nto be dealt with at this index
			if(adjusted_eqn == null) {
				//JOptionPane.showMessageDialog(jContentPane,"Problem with lead/lag terms", "Equation Error", JOptionPane.ERROR_MESSAGE);
				//continue;
				results[i] = null;
				continue;
			}
				
			Expression x = ExpressionTree.parse(adjusted_eqn);
				
			// Set variable map up
			VarMap vm = new VarMap(true); // Case sensitive
			boolean search = true;
			//for(int j=0;j<current_model.getColumnCount() && search;j++) {
			for(int j=0;j<current_model.getColumnCount();j++) {
				
				if(current_model.getNumberAt(i,j) != null) {
					vm.setValue(current_model.getColumnName(j),((Number)current_model.getNumberAt(i,j)).doubleValue());
				}
			}
			vm.setValue(INDEX_TERM,(double)i);
			
			// Add constants to the variable map
			ArrayList constants = current_model.getConstants();
			if(constants.size() > 0) {
				for(int j=0;j<constants.size();j++) {
					Constant one = (Constant)constants.get(j);
					vm.setValue(one.getName(),one.getValue().doubleValue());
				}
			}
				
			// Set up the function map
			//FuncMap fm = getFunctionMap();
			FuncMap fm = new FuncMap();
			fm.loadDefaultFunctions();
			fm = PseudoLogic.AddLogicFunctions(fm);
				
			// Evaluate
			try {
				results[i] = new Double(x.eval(vm,fm));
			} catch(java.lang.RuntimeException re) {
				//JOptionPane.showMessageDialog(jContentPane,re.getMessage(), "Equation Error", JOptionPane.ERROR_MESSAGE);
				results[i] = null;
			}
			
			// Default all NaN values to no value (null)
			if(results[i]!= null) {
				//if(results[i].isNaN())
				//	results[i] = null;
			}
			
		}
		return return_val;
	}
		
	public Number[] getResults() {
		return results;
	}
		
}

