/*
 *  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.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;


public class EquationEvaluation {
		
	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 StatTableModel current_model = null;
	private String eqn_string;
		
	private Double[] results = null;
	
	public EquationEvaluation(StatTableModel information, String unprocessed_eqn) {
			
		// Set the statmodel
		current_model = information;			
		eqn_string = unprocessed_eqn;
		
	}
						
	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;
	}
		
	public boolean processValues() {
		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 Double[] getResults() {
		return results;
	}
		
}

