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

import javax.swing.JPanel;

import java.awt.Component;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JOptionPane;
import java.util.ArrayList;

import com.approximatrix.draco.datamodel.IndepDepTableModel;
import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.fit.LSFit;
import com.approximatrix.draco.fit.LSIterative;
import com.approximatrix.draco.fit.SimpleMLEFit;
import com.approximatrix.draco.fit.functions.*;
import com.approximatrix.draco.general.ui.InfoWindow;
import com.approximatrix.draco.general.ui.MatrixView;
import com.approximatrix.draco.io.XMLio.AnalysisLoadStore;


import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;

import javax.swing.JTable;
import javax.swing.ButtonGroup;


/**
 * Least Squares Fit base class providing the necessary routines for 
 * displaying the least squares fit dialog.  Most routines necessary for
 * modifying the dialog have been provided.  This class can be extended to be
 * used in cases where a simple least-squares fit is needed as an
 * intermediate step, such as in a two-stage least squares fit problem.  The
 * routine has two internal subclasses for dealing with the actual fit itself
 * and the same fit with autocorrellation.
 * 
 * @author Jeffrey Armstrong
 * @version	1.0
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class SimpleIterativeFit extends FitChildDialog {
	
	private static final String DIALOG_NAME = "Simple Iterative Fit";
	
	/** The help keys for all possible window types, indexed by their appropriate
	 * static integers.
	 */
	private static final String[] HELP_KEYS = {"logitfit","probitfit" };
	
	public static final int LOGIT_REGRESSION = 0;
	public static final int PROBIT_REGRESSION = 1; 
	
	private javax.swing.JPanel jContentPane = null;

	private StatTableModel current_model;
	
	private JMenuBar jJMenuBar = null;
	private JMenu ls_fit_menu = null;
	private JMenu ls_fit_info_menu = null;
	private JMenuItem m_compute_regress = null;
	private JMenuItem m_gen_column = null;
	private JMenuItem m_cov_matrix = null;
	private JMenu ls_options_menu = null;
	private JTable selection_table = null;
	private JScrollPane jScrollPane = null;
	private ButtonGroup variance_buttongroup = null;
	private ButtonGroup autocorrelation_method_buttongroup = null;
	
	private IndepDepTableModel selection_model = null;
	
	private JMenuItem m_output_residuals = null;
	
	private JPanel jMenuToolbarPanel = null;

	private int fit_type = LOGIT_REGRESSION;
	private int number_iteration = 1;
	private double tolerance = 0.01;
	private double relaxation = 1.0;
	
	private LSIterative analysis = null;
	private IterativeProgress solver_progress_dialog = null;

	private JMenuItem m_iter_and_tol = null;
	
	/**
	 * Constructor which accepts a StatTablemodel input for data and
	 * a window manager for association of the resulting dialog
	 * 
	 * @param information	A statistics data model to pull info from
	 * @param main_manager	Window manager needed for association of the
	 * 						dialog with the application
	 */
	public SimpleIterativeFit(StatTableModel information, int type) {
		super(DIALOG_NAME, 
				true, //resizable
				true, //closable
				true, //maximizable
				true);
		current_model = information;
		
		initialize(type);
		
		if(information == null) {
			System.err.println("Error with input to SimpleIterativeFit");
		}
		
	}
	
	/**
	 * This method initializes the dialog.
	 * 
	 * @return void
	 */
	private void initialize(int type) {
		this.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
		this.setName("least_squares_dialog");
		
		this.setSize(334, 235);
		this.setContentPane(getJContentPane());
		
		this.fit_type = type;
		
		// In order to initialize all menu/toolbars
		this.getMenuItems();
		this.getToolbarButtons();
		
		switch(type) {
			case LOGIT_REGRESSION:
				this.setTitle("Logit Regression");
				break;
			case PROBIT_REGRESSION:
				this.setTitle("Probit Regression");
				break;
		}
		
		this.number_iteration = IterationControl.GetDefaultIterations();
		this.tolerance = IterationControl.GetDefaultTolerance();
		this.relaxation = IterationControl.GetDefaultRelaxation();
		
		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		//this.addWindowListener(new java.awt.event.WindowAdapter() { 
		//	public void windowClosing(java.awt.event.WindowEvent e) {    
		//		close_window();
		//	}
		//});

		// Put information in the list
		String[] indep_items = new String[current_model.getColumnCount()];
		for(int i=0;i<current_model.getColumnCount();i++) {
			indep_items[i] = current_model.getColumnName(i);
		}		
		
		selection_model = new IndepDepTableModel(indep_items);
		selection_table.setModel(selection_model);
		
		getLs_options_menu().setEnabled(true);
		getM_gen_column().setEnabled(false);
		getLs_fit_info_menu().setEnabled(false);
	}
	
	/**
	 * Provides access to the currently stored StatTableModel.
	 * 
	 * @return StatTableModel
	 */
	public StatTableModel getCurrentModel() {
		return current_model;
	}
	
	/**
	 * This method initializes jContentPane
	 * 
	 * @return javax.swing.JPanel
	 */
	private javax.swing.JPanel getJContentPane() {
		if(jContentPane == null) {
			jContentPane = new javax.swing.JPanel();
			jContentPane.setLayout(new java.awt.BorderLayout());
			
			jContentPane.add(getJScrollPane(), java.awt.BorderLayout.CENTER);
		}
		return jContentPane;
	}
	
	/**
	 * This method initializes jJMenuBar	
	 * 	
	 * @return javax.swing.JMenuBar	
	 */    
	private JMenuBar getJJMenuBar() {
		if (jJMenuBar == null) {
			jJMenuBar = new JMenuBar();
			jJMenuBar.add(getLs_fit_menu());
			jJMenuBar.add(getLs_fit_info_menu());
			jJMenuBar.add(getLs_options_menu());
		}
		return jJMenuBar;
	}
	/**
	 * This method initializes jMenu	
	 * 	
	 * @return javax.swing.JMenu	
	 */    
	private JMenu getLs_fit_menu() {
		if (ls_fit_menu == null) {
			ls_fit_menu = new JMenu();
			ls_fit_menu.setText("Perform Fit");
			ls_fit_menu.add(getM_compute_regress());
			ls_fit_menu.add(getM_gen_column());
		}
		return ls_fit_menu;
	}
	/**
	 * This method initializes jMenu	
	 * 	
	 * @return javax.swing.JMenu	
	 */    
	private JMenu getLs_fit_info_menu() {
		if (ls_fit_info_menu == null) {
			ls_fit_info_menu = new JMenu();
			ls_fit_info_menu.setText("Supporting Data");
			ls_fit_info_menu.add(getM_cov_matrix());
			ls_fit_info_menu.add(getM_output_residuals());
		}
		return ls_fit_info_menu;
	}
	/**
	 * This method initializes jMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */    
	public JMenuItem getM_compute_regress() {
		if (m_compute_regress == null) {
			m_compute_regress = new JMenuItem();
			m_compute_regress.setText("Compute Regression...");
			m_compute_regress.addActionListener(new java.awt.event.ActionListener() { 
				public void actionPerformed(java.awt.event.ActionEvent e) {    
					if(analysis == null) {
						doFit();
					} else {
						displayResults();
					}
				}
			});
		}
		return m_compute_regress;
	}
	/**
	 * This method initializes jMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */    
	public JMenuItem getM_gen_column() {
		if (m_gen_column == null) {
			m_gen_column = new JMenuItem();
			m_gen_column.setText("Generate Column Data");
			m_gen_column.addActionListener(new java.awt.event.ActionListener() { 
				public void actionPerformed(java.awt.event.ActionEvent e) {   
					
					if(analysis == null) {
						doFit();
					}
					
					Number[] col_data = analysis.getFittedData();
					
					StringBuffer title = new StringBuffer();
					title.append(analysis.getDependentVariable());
					title.append("_HAT");
					
					// Now that we have data, fill in a new column
					current_model.addColumn(col_data,title.toString());						
					
				}
			});
		}
		return m_gen_column;
	}
	/**
	 * This method initializes jMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */    
	public JMenuItem getM_cov_matrix() {
		if (m_cov_matrix == null) {
			m_cov_matrix = new JMenuItem();
			m_cov_matrix.setText("View Covariance Matrix...");
			m_cov_matrix.addActionListener(new java.awt.event.ActionListener() { 
				public void actionPerformed(java.awt.event.ActionEvent e) {

					double[][] covariances = analysis.getCovarianceMatrix();
					
					// Need data names
					String[] just_vars = analysis.getIndependentVariables();
					String[] all_names = null; 
					
					all_names = new String[just_vars.length+1];
					all_names[0] = "Constant";
					for(int i=0;i<just_vars.length;i++) {
						all_names[i+1] = just_vars[i];
					}
					
					SimpleIterativeFit.this.getMainWindow().openWindow(
							new MatrixView("Covariance Matrix",
							       all_names,
								   all_names,
								   covariances));
				}
			});
		}
		return m_cov_matrix;
	}
	/**
	 * This method initializes jMenu	
	 * 	
	 * @return javax.swing.JMenu	
	 */    
	public JMenu getLs_options_menu() {
		if (ls_options_menu == null) {
			ls_options_menu = new JMenu();
			ls_options_menu.setText("Options");
			ls_options_menu.add(getM_iter_and_tol());
		}
		return ls_options_menu;
	}
	/**
	 * This method initializes jTable	
	 * 	
	 * @return javax.swing.JTable	
	 */    
	private JTable getSelection_table() {
		if (selection_table == null) {
			selection_table = new JTable();
		}
		return selection_table;
	}
	/**
	 * This method initializes jScrollPane	
	 * 	
	 * @return javax.swing.JScrollPane	
	 */    
	private JScrollPane getJScrollPane() {
		if (jScrollPane == null) {
			jScrollPane = new JScrollPane();
			jScrollPane.setViewportView(getSelection_table());
		}
		return jScrollPane;
	}
	/**
	 * This method initializes jMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */    
	public JMenuItem getM_output_residuals() {
		if (m_output_residuals == null) {
			m_output_residuals = new JMenuItem();
			m_output_residuals.setText("Output Residuals to Column");
			m_output_residuals.addActionListener(new java.awt.event.ActionListener() { 
				public void actionPerformed(java.awt.event.ActionEvent e) {    
					current_model.addColumn(analysis.getResiduals(),"Residuals");
				}
			});
		}
		return m_output_residuals;
	}
	
	public JTable getTable() {
		return selection_table;
	}
	
	private void doFit() {
		
		String dep = selection_model.getDependentVariable();
		String[] indep_cols = selection_model.getIndependentVariables();
		
		if(dep == null) {
			JOptionPane.showMessageDialog(jContentPane,"Please select a dependent variable",  "Data Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		if(indep_cols == null) {
			JOptionPane.showMessageDialog(jContentPane,"Please select an independent variable",  "Data Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		// If we got this far, we must actually be running the analysis
		selection_table.setEnabled(false); // To block any further changes
		ls_options_menu.setEnabled(false);
		
		switch(fit_type) {
			case LOGIT_REGRESSION:
				//first_iteration = new RunLogit(indep_cols, dep, current_model);
				analysis = new SimpleMLEFit(current_model,dep,indep_cols,
						                           new LogitLikelihood(),
						                           SimpleMLEFit.TECHNIQUE_SECANT);
				break;
			case PROBIT_REGRESSION:
				//first_iteration = new RunLogit(indep_cols, dep, current_model);
				analysis = new SimpleMLEFit(current_model,dep,indep_cols,
						                           new ProbitLikelihood(),
						                           SimpleMLEFit.TECHNIQUE_SECANT);
				break;
		}
		
		super.performAnalysis(analysis, number_iteration, tolerance);
	}
	
	/** Notifies the parent that the iterative computation has been completed
	 * 
	 */
	public void notifyComplete() {
		analysis = solver_progress_dialog.getAnalysis();
		solver_progress_dialog = null;
		
		if(analysis != null) {
			getLs_options_menu().setEnabled(false);
			getLs_fit_info_menu().setEnabled(true);
			getM_gen_column().setEnabled(true);
			getLs_fit_info_menu().setEnabled(true);
			String results = analysis.getResults();
			SimpleIterativeFit.this.getMainWindow().openWindow(
					new InfoWindow(results,false));
		} else {
			JOptionPane.showMessageDialog(this,"Regression failed!","Iterative Regression",JOptionPane.ERROR_MESSAGE);
		}
	}

	/**
	 * This method initializes m_iter_and_tol	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getM_iter_and_tol() {
		if (m_iter_and_tol == null) {
			m_iter_and_tol = new JMenuItem();
			m_iter_and_tol.setText("Iterations and Convergence...");
			m_iter_and_tol.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					IterationControl temp = new IterationControl();
					int result = JOptionPane.showConfirmDialog(SimpleIterativeFit.this,
															   temp,
															   "Interation Controls",
															   JOptionPane.OK_CANCEL_OPTION,
															   JOptionPane.PLAIN_MESSAGE);
					if(result == 0) {
						number_iteration = temp.getIterations();
						tolerance = temp.getTolerance();
						relaxation = temp.getRelaxation();
					}
				}
			});
		}
		return m_iter_and_tol;
	}
	
	/** Retuns the analysis as an LSFit object
	 * 
	 * @return a valid, complete LSFit object
	 */
	public LSFit getAnalysis() {
		return analysis;
	}
	
	/** Used for restoring an LSFit analysis to a dialog
	 * 
	 * @param values a valid, loaded AnalysisLoadStore dataset
	 */
	public void setAnalysis(AnalysisLoadStore data) {
		
		// Need to construct the analysis variable based on the analysis type
		if(data.getAnalysisSubType().compareTo(LogitLikelihood.class.getName())==0 ||
		   data.getAnalysisSubType().compareTo(ProbitLikelihood.class.getName())==0) {
			analysis = new SimpleMLEFit();
		}
		
		super.analysis = analysis;
		
		// Fill in the check boxes
		String[] indep = data.getIndependentVariables();
		for(int i=0;i<indep.length;i++) ((IndepDepTableModel)selection_table.getModel()).setIndepenedentChecked(indep[i]);
		((IndepDepTableModel)selection_table.getModel()).setDepenedentChecked(data.getDependentVariable());
		((IndepDepTableModel)selection_table.getModel()).fireTableDataChanged();
		
		// Make the table unchangeable
		selection_table.setEnabled(false); // To block any further changes
		ls_options_menu.setEnabled(false);

		analysis.setLoadedAnalysis(data, current_model);
		if(data.getName() != null)
			this.setTitle(data.getName());
		
	}
	
	/** Returns the parent frome for modal iterative progress dialogs
	 * 
	 * @return the MainWindow object enclosing this dialog
	 * @see com.approximatrix.draco.fit.ui.IterativeProgressParent#getParentFrame()
	 */
	public JFrame getParentFrame() {
		return this.getMainWindow();
	}

	/** Returns the menu items associated with the iterative fit dialog
	 * 
	 * @return an array of JMenu objects
	 * @see com.approximatrix.draco.application.ChildFrame#getMenuItems()
	 */
	public JMenu[] getMenuItems() {
		if(menuItems == null) {
			ArrayList items = new ArrayList();
			
			items.add(getLs_fit_menu());
			items.add(getLs_fit_info_menu());
			items.add(getLs_options_menu());
			
			menuItems = new JMenu[items.size()];
			items.toArray(menuItems);
		}
		return menuItems;
	}

	/** Currently there are no toolbar buttons for this dialog
	 * 
	 * @return null since there are no buttons
	 * @see com.approximatrix.draco.application.ChildFrame#getToolbarButtons()
	 */
	public Component[] getToolbarButtons() {
		return null;
	}
	
	/** Returns the help key for the Simple Iterative Fit window
	 * 
	 * @return the proper help key, defined by HELP_KEYS
	 * @see #HELP_KEYS
	 * @see com.approximatrix.draco.application.ChildFrame#getHelpKey()
	 */
	public String getHelpKey() {
		return HELP_KEYS[fit_type];
	}

	protected String getResultsWindowName() {
		return this.getTitle()+" Results";
	}
	
 }  //  @jve:decl-index=0:visual-constraint="121,30"
