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

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;


import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.fit.statistics.ExtendedStatistic;
import com.approximatrix.draco.math.EquationEvaluation;
import com.approximatrix.draco.math.VectorSupport;
import com.approximatrix.draco.utility.ResultsUtilities;

public class LSFitResultsGen {

	public static StringBuffer GenerateFailureResults(LSFit parent,
			                                          String reason,
			                                          String title) {
		StringBuffer text = new StringBuffer();
		
		if(ResultsUtilities.USE_HTML)
			text.append(ResultsUtilities.GenHTMLHeader());
		
		text.append(ResultsUtilities.GetTitle(title));
		
		if(!ResultsUtilities.USE_HTML) {
			text.append("Regression Variable: ");
			text.append(parent.getDependentVariable());
			text.append("\n");
			text.append(ResultsUtilities.SeparatorLine());
			text.append("Regression Failed!\n");
			if(reason != null) text.append(reason);
		} else {
			text.append(BuildFailure(parent, reason));
		}
		
		if(ResultsUtilities.USE_HTML)
			text.append(ResultsUtilities.GenHTMLFooter());
		
		return text;
	}
	
	public static StringBuffer GenerateResults(LSFit parent, String title) {
		StringBuffer text = new StringBuffer();
		
		if(ResultsUtilities.USE_HTML)
			text.append(ResultsUtilities.GenHTMLHeader());
			
		text.append(ResultsUtilities.GetTitle(title));
		
		if(ResultsUtilities.USE_HTML)
			text.append(BuildSuccess(parent));
		else
			text.append(BuildTextSuccess(parent));
		
		if (ResultsUtilities.USE_HTML)
			text.append(ResultsUtilities.GenHTMLFooter());
		
		return text;
	}
	
	public static double GetFitStdError(LSFit parent) {
		return Math.sqrt(parent.getResidualSumSqared()/((double)(parent.getResiduals().length-parent.getCoef().length)));
	}
	
	public static double GetRSquared(LSFit parent) {
		return 1.0-(parent.getResidualSumSqared()/parent.getTotalSumSqared());
	}
	
	public static double GetRSquaredAdjusted(LSFit parent) {
		double return_val = 1.0-GetRSquared(parent);
		double dfn = ((double)(parent.getResiduals().length-parent.getCoef().length));
		double dfd = ((double)(parent.getResiduals().length-1));
		return 1.0-return_val*dfd/dfn;
	}
	
	public static double[] GetCoefTScores(LSFit parent) {
		return VectorSupport.DivideElements(parent.getCoef(),
				  VectorSupport.SqrtElements(parent.getCoefVariances()));
	}
	
	public static Number[] GetEstimates(LSFit parent, StatTableModel values) {
		String[] variables = parent.getIndependentVariables();
		StringBuffer equation = new StringBuffer();
		int start_i = 0;
		if(parent.getCoef().length > variables.length) {
			equation.append(new Double(parent.getCoef()[0]).toString());
			equation.append("+");
			start_i++;
		}
		
		for(int i=0;i<variables.length;i++) {
			equation.append(new Double(parent.getCoef()[i+start_i]).toString());
			equation.append("*");
			equation.append(variables[i]);
			if(i<variables.length-1) equation.append("+");
		}
		
		EquationEvaluation data = new EquationEvaluation(parent.getModel(),equation.toString());
		if(!data.processValues()) {
			return null;
		}
		
		return data.getResults();
	}
	
	public static Double[] GetResidualsFromEstimates(LSFit parent, StatTableModel values) {
		// First, get the estimates
		Number[] estimates = GetEstimates(parent, values);
		
		if(estimates == null) return null;
		
		// Next, get the dependent data
		double[] dependent = values.getColumnDouble(values.getColumnNumber(parent.getDependentVariable()));
		if(dependent == null) return null;
		
		// Build a residual array
		Double[] residuals = new Double[estimates.length];
		
		// Subtract
		for(int i=0;i<estimates.length;i++) {
			residuals[i] = null;
			if(estimates[i] != null && dependent[i] != Double.NaN) {
				residuals[i] = new Double(dependent[i]-estimates[i].doubleValue());
			}
		}
		
		return residuals;
	}
	
	/** Constructs and returns the results in pure text formatting
	 * 
	 * @param parent the parent LSFit object
	 * @return the String containing the results
	 */
	private static String BuildTextSuccess(LSFit parent) {
		StringBuffer text = new StringBuffer();
		
		text.append("Regression Variable: ");
		text.append(parent.getDependentVariable());
		text.append("\n");

		text.append(ResultsUtilities.SeparatorLine());
		
		// text=print_special_options(text);
		text.append("Sum Squared of the Residuals: ");
		text.append(ResultsUtilities.OutNumber(parent.getResidualSumSqared()));
		text.append("\n");
		text.append("Standard Error of the Fit   : ");
		text.append(ResultsUtilities.OutNumber(GetFitStdError(parent)));
		text.append("\n");
		text.append("R-Squared Value             : ");
		text.append(ResultsUtilities.OutNumber(GetRSquared(parent)));
		text.append("\n");
		text.append("Adjusted R-Squared Value    : ");
		text.append(ResultsUtilities.OutNumber(GetRSquaredAdjusted(parent)));
		text.append("\n");
		
		text.append(ResultsUtilities.SeparatorLine());
		
		// For generating output without constant
		int adder = 0;
		
		double[] coefs = parent.getCoef();
		double[] variances = parent.getCoefVariances();
		double[] tscores = GetCoefTScores(parent);
		String[] labels = parent.getCoefLabels();

		for(int i=0;i<labels.length;i++) {
			text.append(labels[i]);
			text.append(": " );
			text.append(ResultsUtilities.OutNumber(coefs[i]));
			text.append("\tStd Err: ");
			text.append(ResultsUtilities.OutNumber(Math.sqrt(variances[i])));
			text.append("\tt-Score: ");
			text.append(ResultsUtilities.OutNumber(tscores[i]));
			text.append("\n");
		}
		
		text.append(ResultsUtilities.SeparatorLine());
		
		text.append(GetExtendedStatisticsTable(parent));
		
		return text.toString();
	}
	
	/** Builds a success report using properly formatted HTML
	 * 
	 * @param parent the parent LSFit object
	 * @return a String containing the formatted HTML
	 */
	private static String BuildSuccess(LSFit parent) {
		StringBuffer rv = new StringBuffer();
		
		rv.append(GetRegressionVar(parent));
		
		rv.append(ResultsUtilities.SeparatorLine());
		
		String[][] table = new String[4][2];
		table[0][0] = "Sum Squared of the Residuals: "; table[0][1] = ResultsUtilities.OutNumber(parent.getResidualSumSqared());
		table[1][0] = "Standard Error of the Fit: ";    table[1][1] = ResultsUtilities.OutNumber(GetFitStdError(parent));
		table[2][0] = "R-Squared Value: ";              table[2][1] = ResultsUtilities.OutNumber(GetRSquared(parent));
		table[3][0] = "Adjusted R-Squared Value: ";     table[3][1] = ResultsUtilities.OutNumber(GetRSquaredAdjusted(parent));
		rv.append(ResultsUtilities.GenerateTable(table,false));
		
		rv.append(ResultsUtilities.SeparatorLine());
		
		double[] coefs = parent.getCoef();
		double[] variances = parent.getCoefVariances();
		double[] tscores = GetCoefTScores(parent);
		String[] labels = parent.getCoefLabels();

		table = new String[labels.length+1][4];
		
		table[0][0] = "Coefficient"; table[0][1] = "Value"; table[0][2] = "Std. Err."; table[0][3] = "t-Score";
		
		for(int i=1;i<=labels.length;i++) {
			table[i][0] = labels[i-1];
			table[i][1] = ResultsUtilities.OutNumber(coefs[i-1]);
			table[i][2] = ResultsUtilities.OutNumber(Math.sqrt(variances[i-1]));
			table[i][3] = ResultsUtilities.OutNumber(tscores[i-1]);
		}
		rv.append(ResultsUtilities.GenerateTable(table,true,true));
		
		rv.append(ResultsUtilities.SeparatorLine());
		
		rv.append(GetExtendedStatisticsTable(parent));
		
		return rv.toString();
	}
	
	/** Builds a Failure report using HTML formatting
	 * 
	 * @param parent the parent analysis
	 * @param reason the reason for failure
	 * @return a properly formatted String containing valid HTML
	 */
	private static String BuildFailure(LSFit parent, String reason) {
		StringBuffer rv = new StringBuffer();
		
		rv.append(GetRegressionVar(parent));
		
		rv.append(ResultsUtilities.SeparatorLine());
		
		rv.append("<BR>");
		rv.append("Regression Failed!");
		rv.append("</BR>");
		
		if(reason != null) {
			rv.append("<BR>");
			rv.append(reason);
			rv.append("</BR>");
		}
		
		return rv.toString();
	}
	
	/** Generates and returns the regression variable string in proper
	 * format. 
	 * 
	 * @param parent the parent LSFit object
	 * @return the resulting String
	 */
	private static String GetRegressionVar(LSFit parent) {
		if(ResultsUtilities.USE_HTML) {
			String[][] table = new String[1][2];
			table[0][0] = "Regression Variable:";
			table[0][1] = "<B>"+parent.getDependentVariable()+"</B>";
			return ResultsUtilities.GenerateTable(table,false);
		} else
			return "Regression Variable: "+parent.getDependentVariable();
	}
	
	private static String GetExtendedStatisticsTable(LSFit parent) {
		List<ExtendedStatistic> stats = parent.getExtendedStatistics();
		
		if(stats == null) return "";
		if(stats.size() == 0) return "";
		
		StringBuffer buffer = new StringBuffer();
		if(ResultsUtilities.USE_HTML)
			buffer.append("<h3>Extended Statistics</h3><br>");
		else
			buffer.append("Extended Statistics:\n");
		
		String[][] table = new String[stats.size()][2];
		int i = 0;
		for(ExtendedStatistic stat : stats) {
			table[i][0] = stat.getName();
			try {
				table[i][1] = ResultsUtilities.OutNumber(stat.getValue());
			} catch(NullPointerException npe) {
				table[i][1] = "Error";
			}
			i++;
		}
		buffer.append(ResultsUtilities.GenerateTable(table,false,false));
		return buffer.toString();
	}
	
}
