package org.geogebra.desktop.gui.view.data;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;

import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.table.DefaultTableModel;

import org.geogebra.common.gui.view.data.OneVarModel;
import org.geogebra.common.gui.view.data.StatisticsModel;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.desktop.gui.inputfield.MyTextFieldD;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.main.LocalizationD;

/**
 * Extended JPanel that contains interactive sub-panels for performing one
 * variable inference with the current data set.
 * 
 * @author G. Sturr
 * 
 */
public class OneVarInferencePanelD extends JPanel
		implements ActionListener, FocusListener, StatPanelInterface {
	private static final long serialVersionUID = 1L;
	// ggb fields
	private AppD app;
	private Kernel kernel;
	private DataAnalysisViewD statDialog;
	private StatTable resultTable;

	// GUI
	private JLabel lblHypParameter;
	private JLabel lblTailType;
	private JLabel lblNull;
	private JLabel lblConfLevel;
	private JLabel lblSigma;
	private JLabel lblResultHeader;
	private JButton btnCalculate;
	private MyTextFieldD fldNullHyp;
	private MyTextFieldD fldConfLevel;
	private MyTextFieldD fldSigma;
	private JComboBox cbAltHyp;
	private JPanel testPanel;
	private JPanel intPanel;
	private JPanel mainPanel;
	private JPanel resultPanel;
	private Box sigmaPanel;
	// flags
	private boolean isIniting;
	private boolean isTest = true;
	private boolean isZProcedure;

	private final LocalizationD loc;
	private final OneVarModel model;

	/***************************************
	 * Construct a OneVarInference panel
	 */
	public OneVarInferencePanelD(AppD app, DataAnalysisViewD statDialog) {

		isIniting = true;
		this.app = app;
		this.loc = app.getLocalization();
		this.kernel = app.getKernel();
		this.statDialog = statDialog;
		this.model = new OneVarModel();

		this.setLayout(new BorderLayout());
		this.createGUIElements();
		this.updateGUI();
		this.setLabels();

		isIniting = false;
	}

	// ============================================================
	// Create GUI
	// ============================================================

	private void createGUIElements() {

		JRadioButton btnLeft = new JRadioButton(OneVarModel.tail_left);
		JRadioButton btnRight = new JRadioButton(OneVarModel.tail_right);
		JRadioButton btnTwo = new JRadioButton(OneVarModel.tail_two);
		ButtonGroup group = new ButtonGroup();
		group.add(btnLeft);
		group.add(btnRight);
		group.add(btnTwo);
		btnLeft.addActionListener(this);
		btnRight.addActionListener(this);
		btnTwo.addActionListener(this);
		btnTwo.setSelected(true);

		cbAltHyp = new JComboBox();
		cbAltHyp.addActionListener(this);

		lblNull = new JLabel();
		lblHypParameter = new JLabel();
		lblTailType = new JLabel();

		fldNullHyp = new MyTextFieldD(app);
		int fieldWidth = 6;
		fldNullHyp.setColumns(fieldWidth);
		fldNullHyp.setText("" + 0);
		fldNullHyp.addActionListener(this);
		fldNullHyp.addFocusListener(this);

		lblConfLevel = new JLabel();
		fldConfLevel = new MyTextFieldD(app);
		fldConfLevel.setColumns(fieldWidth);
		fldConfLevel.addActionListener(this);
		fldConfLevel.addFocusListener(this);

		lblSigma = new JLabel();
		fldSigma = new MyTextFieldD(app);
		fldSigma.setColumns(fieldWidth);
		fldSigma.addActionListener(this);
		fldSigma.addFocusListener(this);

		btnCalculate = new JButton();
		lblResultHeader = new JLabel();

		sigmaPanel = hBox(lblSigma, fldSigma);

		GridBagConstraints c = new GridBagConstraints();
		c.gridx = 0;
		c.weightx = 1;
		c.insets = new Insets(4, 0, 0, 0);
		c.anchor = GridBagConstraints.WEST;

		GridBagConstraints tab = new GridBagConstraints();
		tab.gridx = 0;
		tab.gridy = c.gridy;
		tab.weightx = 1;
		tab.insets = new Insets(4, 20, 0, 0);
		tab.anchor = GridBagConstraints.WEST;

		// test panel
		testPanel = new JPanel(new GridBagLayout());
		c.gridy = GridBagConstraints.RELATIVE;
		testPanel.add(lblNull, c);
		testPanel.add(flowPanel(lblHypParameter, fldNullHyp), tab);
		testPanel.add(lblTailType, c);
		testPanel.add(cbAltHyp, tab);

		// CI panel
		intPanel = new JPanel(new GridBagLayout());
		c.gridy = GridBagConstraints.RELATIVE;
		intPanel.add(lblConfLevel, c);
		intPanel.add(fldConfLevel, tab);

		// result panel
		resultTable = new StatTable(app);
		setResultTable();

		resultPanel = new JPanel(new BorderLayout());
		c.gridy = GridBagConstraints.RELATIVE;
		resultPanel.add(lblResultHeader, BorderLayout.NORTH);
		resultPanel.add(resultTable, BorderLayout.CENTER);
		c.weightx = 0;
		c.fill = GridBagConstraints.HORIZONTAL;
		// resultPanel.add(resultTable, c);

		// main panel
		mainPanel = new JPanel(new GridBagLayout());
		this.add(mainPanel, BorderLayout.NORTH);
		// this.add(resultPanel, BorderLayout.CENTER);
	}

	private void updateMainPanel() {

		mainPanel.removeAll();
		GridBagConstraints c = new GridBagConstraints();
		c.gridx = 0;
		c.weightx = 1;
		c.insets = new Insets(4, 0, 0, 0);
		c.anchor = GridBagConstraints.WEST;

		GridBagConstraints tab = new GridBagConstraints();
		tab.gridx = 0;
		tab.gridy = c.gridy;
		tab.weightx = 1;
		tab.insets = new Insets(4, 20, 0, 0);
		tab.anchor = GridBagConstraints.WEST;

		c.gridy = GridBagConstraints.RELATIVE;
		if (isZProcedure) {
			mainPanel.add(sigmaPanel, tab);
		}

		if (isTest) {
			mainPanel.add(testPanel, c);
		} else {
			mainPanel.add(intPanel, c);
		}

		c.weightx = 0;
		c.fill = GridBagConstraints.HORIZONTAL;
		mainPanel.add(resultPanel, c);
	}

	private void setResultTable() {
		ArrayList<String> nameList = model.getNameList(loc);

		String[] rowNames = new String[nameList.size()];
		nameList.toArray(rowNames);
		resultTable.setStatTable(rowNames.length, rowNames, 1, null);
	}

	private void updateResultTable() {

		DefaultTableModel model1 = resultTable.getModel();

		evaluate();
		String cInt = statDialog.format(model.getMean()) + " \u00B1 "
				+ statDialog.format(model.getMe());

		switch (model.selectedPlot) {
		default:
			// do nothing
			break;
		case StatisticsModel.INFER_ZTEST:
			model1.setValueAt(statDialog.format(model.getP()), 0, 0);
			model1.setValueAt(statDialog.format(model.getTestStat()), 1, 0);
			model1.setValueAt("", 2, 0);
			model1.setValueAt(statDialog.format(model.getN()), 3, 0);
			model1.setValueAt(statDialog.format(model.getMean()), 4, 0);
			break;

		case StatisticsModel.INFER_TTEST:
			model1.setValueAt(statDialog.format(model.getP()), 0, 0);
			model1.setValueAt(statDialog.format(model.getTestStat()), 1, 0);
			model1.setValueAt(statDialog.format(model.getDf()), 2, 0);
			model1.setValueAt(statDialog.format(model.getSe()), 3, 0);
			model1.setValueAt("", 4, 0);
			model1.setValueAt(statDialog.format(model.getN()), 5, 0);
			model1.setValueAt(statDialog.format(model.getMean()), 6, 0);
			break;

		case StatisticsModel.INFER_ZINT:
			model1.setValueAt(cInt, 0, 0);
			model1.setValueAt(statDialog.format(model.getLower()), 1, 0);
			model1.setValueAt(statDialog.format(model.getUpper()), 2, 0);
			model1.setValueAt(statDialog.format(model.getMe()), 3, 0);
			model1.setValueAt("", 4, 0);
			model1.setValueAt(statDialog.format(model.getN()), 5, 0);
			model1.setValueAt(statDialog.format(model.getMean()), 6, 0);
			break;

		case StatisticsModel.INFER_TINT:
			model1.setValueAt(cInt, 0, 0);
			model1.setValueAt(statDialog.format(model.getLower()), 1, 0);
			model1.setValueAt(statDialog.format(model.getUpper()), 2, 0);
			model1.setValueAt(statDialog.format(model.getMe()), 3, 0);
			model1.setValueAt(statDialog.format(model.getDf()), 4, 0);
			model1.setValueAt(statDialog.format(model.getSe()), 5, 0);
			model1.setValueAt("", 6, 0);
			model1.setValueAt(statDialog.format(model.getN()), 7, 0);
			model1.setValueAt(statDialog.format(model.getMean()), 8, 0);
			break;
		}

	}

	// ============================================================
	// Updates and Event Handlers
	// ============================================================

	@Override
	public void updateFonts(Font font) {
		// not needed
		// ... font updates handled by recursive call in StatDialog
	}

	@Override
	public void setLabels() {

		lblHypParameter.setText(loc.getMenu("HypothesizedMean.short") + " = ");
		lblNull.setText(loc.getMenu("NullHypothesis") + ": ");
		lblTailType.setText(loc.getMenu("AlternativeHypothesis") + ": ");
		lblConfLevel.setText(loc.getMenu("ConfidenceLevel") + ": ");
		lblResultHeader.setText(loc.getMenu("Result") + ": ");
		lblSigma.setText(loc.getMenu("StandardDeviation.short") + " = ");
		btnCalculate.setText(loc.getMenu("Calculate"));
		repaint();
	}

	/** Helper method for updateGUI() */
	private void updateNumberField(JTextField fld, double n) {

		fld.removeActionListener(this);
		fld.setText(statDialog.format(n));
		// fld.setCaretPosition(0);
		fld.addActionListener(this);

	}

	private void updateGUI() {

		isTest = (model.selectedPlot == StatisticsModel.INFER_ZTEST
				|| model.selectedPlot == StatisticsModel.INFER_TTEST);

		isZProcedure = model.selectedPlot == StatisticsModel.INFER_ZTEST
				|| model.selectedPlot == StatisticsModel.INFER_ZINT;

		updateNumberField(fldNullHyp, model.hypMean);
		updateNumberField(fldConfLevel, model.confLevel);
		updateNumberField(fldSigma, model.sigma);
		updateCBAlternativeHyp();
		setResultTable();
		updateResultTable();
		updateMainPanel();
	}

	@SuppressWarnings("unchecked")
	private void updateCBAlternativeHyp() {

		cbAltHyp.removeActionListener(this);
		cbAltHyp.removeAllItems();
		cbAltHyp.addItem(loc.getMenu("HypothesizedMean.short") + " "
				+ OneVarModel.tail_right + " "
				+ statDialog.format(model.hypMean));
		cbAltHyp.addItem(loc.getMenu("HypothesizedMean.short") + " "
				+ OneVarModel.tail_left
				+ " " + statDialog.format(model.hypMean));
		cbAltHyp.addItem(loc.getMenu("HypothesizedMean.short") + " "
				+ OneVarModel.tail_two
				+ " " + statDialog.format(model.hypMean));

		if (model.tail == OneVarModel.tail_right) {
			cbAltHyp.setSelectedIndex(0);
		} else if (model.tail == OneVarModel.tail_left) {
			cbAltHyp.setSelectedIndex(1);
		} else {
			cbAltHyp.setSelectedIndex(2);
		}

		cbAltHyp.addActionListener(this);

	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (isIniting) {
			return;
		}
		Object source = e.getSource();

		if (source instanceof JTextField) {
			doTextFieldActionPerformed((JTextField) source);
		}

		else if (source == cbAltHyp) {

			if (cbAltHyp.getSelectedIndex() == 0) {
				model.tail = OneVarModel.tail_right;
			} else if (cbAltHyp.getSelectedIndex() == 1) {
				model.tail = OneVarModel.tail_left;
			} else {
				model.tail = OneVarModel.tail_two;
			}

			evaluate();
			updateResultTable();
		}

	}

	private void doTextFieldActionPerformed(JTextField source) {
		if (isIniting) {
			return;
		}

		double value = model.evaluateExpression(kernel,
				source.getText().trim());

		if (source == fldConfLevel) {
			model.confLevel = value;
			evaluate();
			updateGUI();
		}

		else if (source == fldNullHyp) {
			model.hypMean = value;
			evaluate();
			updateGUI();
		}

		else if (source == fldSigma) {
			model.sigma = value;
			evaluate();
			updateGUI();
		}

	}

	@Override
	public void focusGained(FocusEvent e) {
		// nothing to do
	}

	@Override
	public void focusLost(FocusEvent e) {
		doTextFieldActionPerformed((JTextField) (e.getSource()));
	}

	/**
	 * @param selectedPlot selected plot
	 */
	public void setSelectedPlot(int selectedPlot) {
		model.selectedPlot = selectedPlot;
		updateGUI();
	}

	@Override
	public void updatePanel() {
		// evaluate();
		updateGUI();
		// updateResultTable();
	}

	// ============================================================
	// Computation
	// ============================================================

	private void evaluate() {

		GeoList dataList = statDialog.getController().getDataSelected();
		double[] sample = statDialog.getController().getValueArray(dataList);

		model.evaluate(sample);

	}

	// ============================================================
	// GUI Utilities
	// ============================================================

	private static JPanel flowPanel(Component... comp) {
		JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
		for (int i = 0; i < comp.length; i++) {
			p.add(comp[i]);
		}
		// p.setBackground(Color.white);
		return p;
	}

	private static Box hBox(Component... comp) {
		Box b = Box.createHorizontalBox();
		for (int i = 0; i < comp.length; i++) {
			b.add(comp[i]);
		}
		return b;
	}

}
