/*
 *  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 java.awt.BorderLayout;
import java.awt.Component;

import javax.swing.JPanel;
import java.awt.Dialog;

import javax.swing.JDialog;

import com.approximatrix.draco.application.ApplicationManager;
import com.approximatrix.draco.datamodel.StatTableModel;
import com.approximatrix.draco.fit.LSFit;
import com.approximatrix.draco.fit.RunNLLS;
import com.approximatrix.draco.general.ui.EquationPanel;
import com.approximatrix.draco.general.ui.InfoWindow;
import com.approximatrix.draco.general.ui.MatrixView;
import com.approximatrix.draco.io.XMLio.AnalysisLoadStore;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;

import java.util.ArrayList;

public class NonLinearLSFit extends FitChildDialog {

	private static final String DIALOG_NAME = "Non-Linear Least Squares Regression";
	
	/** The string for requesting topical help */
	private static final String HELP_KEY = "nllsfit";
	
	private static final String DEFAULT_RESULTS_NAME = "Non-Linear Least Squares Results";
	
	private static final String BAD_PARAMETER_NAME = "NONAME";
	private static final double BAD_PARAMETER_VALUE = Double.NaN;
	
	private JPanel jContentPane = null;
	private JMenuBar jJMenuBar = null;
	private JMenu nl_fit_menu = null;
	private JMenu nl_data_menu = null;
	private JMenu nl_options_menu = null;
	private JMenuItem m_nl_iter_convergence = null;
	private JMenuItem m_cov_matrix = null;
	private JMenuItem m_output_residuals = null;
	private JTabbedPane jTabbedPane = null;
	private EquationPanel jEqnPanel = null;
	private JPanel jParamPanel = null;
	
	private int number_iteration = 1;
	private double tolerance = 0.01;
	private double relaxation = 1.0;
	private StatTableModel current_model = null;
	
	private JScrollPane jScrollPane = null;
	private JTable jParametersTable = null;
	
	private JButton[] add_remove_buttons = null;
	private JMenuItem m_nl_compute = null;
	private JMenuItem m_nl_generate = null;
	
	private RunNLLS analysis = null;
	
	private IterativeProgress solver_progress_dialog = null;
	
	private boolean allow_changes = true;

	/**
	 * This is the default constructor
	 */
	public NonLinearLSFit(StatTableModel information) {
		super(DIALOG_NAME, 
				true, //resizable
				true, //closable
				true, //maximizable
				true);
		current_model = information;
		
		initialize();
	}

	/**
	 * This method initializes this
	 * 
	 * @return void
	 */
	private void initialize() {
		this.setSize(500, 300);
		this.setTitle(DIALOG_NAME);
		this.setJMenuBar(getJJMenuBar());
		this.setContentPane(getJContentPane());
		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		// In order to initialize all menu/toolbars
		this.getMenuItems();
		this.getToolbarButtons();
		
		//this.addWindowListener(new java.awt.event.WindowAdapter() {
		//	public void windowClosing(java.awt.event.WindowEvent e) {
		//		close_window();
		//	}
		//});
		tolerance = IterationControl.GetDefaultTolerance();
		number_iteration = IterationControl.GetDefaultIterations();
		relaxation = IterationControl.GetDefaultRelaxation();
	}

	/**
	 * This method initializes jContentPane
	 * 
	 * @return javax.swing.JPanel
	 */
	private JPanel getJContentPane() {
		if (jContentPane == null) {
			jContentPane = new JPanel();
			jContentPane.setLayout(new BorderLayout());
			jContentPane.add(getJTabbedPane(), 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(getNl_fit_menu());
			jJMenuBar.add(getNl_data_menu());
			jJMenuBar.add(getNl_options_menu());
		}
		return jJMenuBar;
	}

	/**
	 * This method initializes nl_fit_menu	
	 * 	
	 * @return javax.swing.JMenu	
	 */
	private JMenu getNl_fit_menu() {
		if (nl_fit_menu == null) {
			nl_fit_menu = new JMenu();
			nl_fit_menu.setText("Perform Fit");
			nl_fit_menu.add(getM_nl_compute());
			nl_fit_menu.add(getM_nl_generate());
		}
		return nl_fit_menu;
	}

	/**
	 * This method initializes nl_data_menu	
	 * 	
	 * @return javax.swing.JMenu	
	 */
	private JMenu getNl_data_menu() {
		if (nl_data_menu == null) {
			nl_data_menu = new JMenu();
			nl_data_menu.setText("Supporting Data");
			nl_data_menu.add(getM_cov_matrix());
			nl_data_menu.add(getM_output_residuals());
		}
		return nl_data_menu;
	}
	
	protected 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) {
					if(analysis == null) {
						JOptionPane.showMessageDialog(jContentPane,"Please compute the regression first.",  ApplicationManager.APP_NAME+" Error", JOptionPane.ERROR_MESSAGE);
						return;
					}
					double[][] covariances = analysis.getCovarianceMatrix();
					
					// Need data names
					String[] all_names = analysis.getCoefLabels(); 
					
					NonLinearLSFit.this.getMainWindow().openWindow(
							new MatrixView("NLLS Covariance Matrix",
							       all_names,
								   all_names,
								   covariances));
				}
			});
		}
		return m_cov_matrix;
	}
	
	protected 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) {    
					if(analysis == null) {
						JOptionPane.showMessageDialog(jContentPane,"Please compute the regression first.",  ApplicationManager.APP_NAME+" Error", JOptionPane.ERROR_MESSAGE);
						return;
					}
					
					current_model.addColumn(analysis.getResiduals(),"Residuals");
				}
			});
		}
		return m_output_residuals;
	}

	/**
	 * This method initializes nl_options_menu	
	 * 	
	 * @return javax.swing.JMenu	
	 */
	private JMenu getNl_options_menu() {
		if (nl_options_menu == null) {
			nl_options_menu = new JMenu();
			nl_options_menu.setText("Options");
			nl_options_menu.add(getM_nl_iter_convergence());
		}
		return nl_options_menu;
	}

	/**
	 * This method initializes m_nl_iter_convergence	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getM_nl_iter_convergence() {
		if (m_nl_iter_convergence == null) {
			m_nl_iter_convergence = new JMenuItem();
			m_nl_iter_convergence.setText("Iterations and Convergence...");
			m_nl_iter_convergence.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					IterationControl temp = new IterationControl();
					int result = JOptionPane.showConfirmDialog(NonLinearLSFit.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_nl_iter_convergence;
	}

	/**
	 * This method initializes jTabbedPane	
	 * 	
	 * @return javax.swing.JTabbedPane	
	 */
	private JTabbedPane getJTabbedPane() {
		if (jTabbedPane == null) {
			jTabbedPane = new JTabbedPane();
			jTabbedPane.addTab("Model", null, (JPanel)getJEqnPanel(), null);
			jTabbedPane.addTab("Parameters", null, getJParamPanel(), null);
			jTabbedPane.addChangeListener(new javax.swing.event.ChangeListener() {
				public void stateChanged(javax.swing.event.ChangeEvent e) {
					// Nothing...
				}
			});
		}
		return jTabbedPane;
	}

	/**
	 * This method initializes jEqnPanel	
	 * 	
	 * @return javax.swing.JPanel	
	 */
	private EquationPanel getJEqnPanel() {
		if (jEqnPanel == null) {
			jEqnPanel = new EquationPanel(current_model);
		}
		return jEqnPanel;
	}

	/**
	 * This method initializes jParamPanel	
	 * 	
	 * @return javax.swing.JPanel	
	 */
	private JPanel getJParamPanel() {
		if (jParamPanel == null) {
			jParamPanel = new JPanel();
			jParamPanel.add(getJScrollPane(), null);
		}
		return jParamPanel;
	}

	/**
	 * This method initializes jScrollPane	
	 * 	
	 * @return javax.swing.JScrollPane	
	 */
	private JScrollPane getJScrollPane() {
		if (jScrollPane == null) {
			jScrollPane = new JScrollPane();
			jScrollPane.setViewportView(getJParametersTable());
		}
		return jScrollPane;
	}

	/**
	 * This method initializes jParametersTable	
	 * 	
	 * @return javax.swing.JTable	
	 */
	private JTable getJParametersTable() {
		if (jParametersTable == null) {
			DefaultTableModel model = new DefaultTableModel();
			model.addColumn("Parameter Name");
			model.addColumn("Initial Estimate");
			jParametersTable = new JTable(model);
		}
		return jParametersTable;
	}
	
	private void setParameterTabButtons() {
		add_remove_buttons = new JButton[2];
		
		// Add on the buttons
		JButton button = new JButton();
		button.setToolTipText("Add a parameter");
		button.setIcon(ApplicationManager.GetIcon("ADD"));
		button.addMouseListener(new java.awt.event.MouseAdapter() { 
			public void mouseClicked(java.awt.event.MouseEvent e) {
				
				if(allow_changes == false) return;
				
				JTextField textfield = new JTextField();
				textfield.setText("alpha");
				Object[] options = {"Ok","Cancel"};
				int selection = JOptionPane.showOptionDialog(NonLinearLSFit.this,
															 textfield,
															 "Enter a name for new parameter",
															 JOptionPane.YES_NO_OPTION,
															 JOptionPane.QUESTION_MESSAGE,
															 null,
															 options,
															 options[0]);
				if(selection == 0) {
					DefaultTableModel model = (DefaultTableModel)jParametersTable.getModel();
					Object[] row = new Object[2];
					row[0] = textfield.getText();
					row[1] = new Double(0.0);
					model.addRow(row);
					jParametersTable.setModel(model);
					model.fireTableDataChanged();
					model.fireTableStructureChanged();
					textfield = null;
				}
			}
		});
		add_remove_buttons[0] = button;
		
		button = new JButton();
		button.setToolTipText("Remove selected");
		button.setIcon(ApplicationManager.GetIcon("REMOVE"));
		button.addMouseListener(new java.awt.event.MouseAdapter() { 
			public void mouseClicked(java.awt.event.MouseEvent e) {
				
				if(allow_changes == false) return;
				
				int row = jParametersTable.getSelectedRow();
				if(row>=0) {
					DefaultTableModel model = (DefaultTableModel)jParametersTable.getModel();
					model.removeRow(row);
					jParametersTable.setModel(model);
				}
			}
		});
		add_remove_buttons[1] = button;
		
		toolbarButtons = add_remove_buttons;
	}

	/**
	 * This method initializes jMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getM_nl_compute() {
		if (m_nl_compute == null) {
			m_nl_compute = new JMenuItem();
			m_nl_compute.setText("Compute Regression...");
			m_nl_compute.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					doFit();
				}
			});
		}
		return m_nl_compute;
	}

	/**
	 * This method initializes jMenuItem	
	 * 	
	 * @return javax.swing.JMenuItem	
	 */
	private JMenuItem getM_nl_generate() {
		if (m_nl_generate == null) {
			m_nl_generate = new JMenuItem();
			m_nl_generate.setText("Generate Column Data");
		}
		return m_nl_generate;
	}
	
	private void doFit() {
		
		// Lock the equation
		getJEqnPanel().setAllowChanges(false);
		
		// Lock the parameters
		getJParametersTable().setEnabled(false);
		allow_changes = false;
		
		String equation = getJEqnPanel().getEquation();
		String dependent = getJEqnPanel().getDependent();
		
		DefaultTableModel pmodel = (DefaultTableModel)jParametersTable.getModel();
		
		String[] parameters = new String[pmodel.getRowCount()];
		double[] estimates = new double[pmodel.getRowCount()];
		
		for(int i=0;i<pmodel.getRowCount();i++) {
			Object name = pmodel.getValueAt(i,0);
			if(name.getClass() == String.class)
				parameters[i] = (String)name;
			else
				parameters[i] = BAD_PARAMETER_NAME;
			
			Object value = pmodel.getValueAt(i,1);
			if(value.getClass().isAssignableFrom(Number.class))
				estimates[i] = ((Number)value).doubleValue();
			else if(value.getClass() == String.class)
				try {
					estimates[i] = new Double((String)value).doubleValue();
				} catch(NumberFormatException nfe) {
					estimates[i] = BAD_PARAMETER_VALUE;
				}
			else
				estimates[i] = BAD_PARAMETER_VALUE;
		}
		
		// First iteration
		analysis = new RunNLLS(current_model,dependent,equation,parameters,estimates,relaxation);
		super.performAnalysis(analysis, number_iteration, tolerance);
	}
	
	/** 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) {
		analysis = new RunNLLS();
		analysis.setLoadedAnalysis(data, current_model);
		super.analysis = analysis;
		
		this.getJEqnPanel().setEquation(data.getEquation());
		this.getJEqnPanel().setDependent(data.getDependentVariable());
		this.getJEqnPanel().setAllowChanges(false);
		
		// Add the parameters to the list
		DefaultTableModel model = (DefaultTableModel)jParametersTable.getModel();
		String[] params = data.getCoefLabels();
		for(int i=0;i<params.length;i++) {
			Object[] row = new Object[2];
			row[0] = params[i];
			row[1] = new Double(data.getCoef()[i]);
			model.addRow(row);
		}
		jParametersTable.setModel(model);
		jParametersTable.setEnabled(false);
		model.fireTableDataChanged();
		model.fireTableStructureChanged();
		
		this.allow_changes = false;
		
		if(data.getName() != null)
			this.setTitle(data.getName());
		
	}

	/** Returns the menu items for this dialog
	 * 
	 * @return an array of menus
	 * @see com.approximatrix.draco.application.ChildFrame#getMenuItems()
	 */
	public JMenu[] getMenuItems() {
		if(menuItems == null) {
			ArrayList items = new ArrayList();
			
			items.add(getNl_fit_menu());
			items.add(getNl_data_menu());
			items.add(getNl_options_menu());
			
			menuItems = new JMenu[items.size()];
			items.toArray(menuItems);
		}
		return menuItems;
	}

	/** Adds the toolbar buttons for the NLLS dialog
	 * 
	 * @return an array of Components for the toolbar
	 * @see com.approximatrix.draco.application.ChildFrame#getToolbarButtons()
	 */
	public Component[] getToolbarButtons() {
		if(toolbarButtons == null) {
			setParameterTabButtons();
		}
		return toolbarButtons;
	}
	
	/** Returns the controller's dialog state for assigning a parent properly
	 * creation.
	 * 
	 * @return the parent dialog
	 */ 
	public JFrame getParentFrame() {
		return this.getMainWindow();
	}
	
	/** Notifies the parent that the iterative computation has been completed
	 * 
	 */
	public void notifyComplete() {
		analysis = (RunNLLS)solver_progress_dialog.getAnalysis();
		solver_progress_dialog = null;
		
		if(analysis != null) {
		//	getLs_options_menu().setEnabled(false);
		//	getM_gen_column().setEnabled(true);
		//	getLs_fit_info_menu().setEnabled(true);
			String results = analysis.getResults();
			this.getMainWindow().openWindow(new InfoWindow(results,false));
		} else {
			JOptionPane.showMessageDialog(this,"Regression failed!","Iterative Regression",JOptionPane.ERROR_MESSAGE);
		}
	}

	/** Returns the help key for the Non-Linear Least Squares Fit window
	 * 
	 * @return the proper help key, defined by HELP_KEY
	 * @see #HELP_KEY
	 * @see com.approximatrix.draco.application.ChildFrame#getHelpKey()
	 */
	public String getHelpKey() {
		return HELP_KEY;
	}

	@Override
	protected String getResultsWindowName() {
		return DEFAULT_RESULTS_NAME;
	}
	
}  //  @jve:decl-index=0:visual-constraint="10,10"
