// ParametersTable.java
//
// Created: February 2006
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard.data;

import org.tulsoft.shared.UUtils;

import org.biomoby.shared.MobySecondaryData;
import org.biomoby.shared.parser.MobyParameter;
import org.biomoby.shared.parser.MobyTags;
import org.biomoby.service.dashboard.chooser.DateTimeChooserEditor;

import com.toedter.components.JSpinField;

import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JComboBox;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.event.CellEditorListener;

import java.awt.Component;
import java.awt.event.MouseEvent;

import org.jdom.Element;

import java.util.EventObject;
import java.util.Vector;

/**
 * A table for entering one or more BioMoby secondary inputs
 * (parameters, in the Biomoby speak). It also knows how to convert
 * resulting values into Biomoby compliant XML. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: ParametersTable.java,v 1.3 2006/04/28 00:13:41 senger Exp $
 */

public class ParametersTable
    extends JTable {

    // table model
    protected ParametersTableModel tableModel;

    // table data definitions
    MobySecondaryData[] paramDefs;

    /*********************************************************************
     * Default constructor.
     ********************************************************************/
    public ParametersTable (MobySecondaryData[] paramDefs) {
	super();
	tableModel = new ParametersTableModel (paramDefs);
	this.paramDefs = paramDefs;
	setModel (tableModel);
	getTableHeader().setReorderingAllowed (false);

	getColumnModel().getColumn (ParametersTableModel.COL_VALUE)
	    .setCellEditor (new MultiEditor());
	getColumnModel().getColumn (ParametersTableModel.COL_VALUE)
	    .setCellRenderer (new MultiRenderer());
    }

    /*********************************************************************
     * Put this table in a scrollable pane.
     ********************************************************************/
    public JScrollPane scrollable() {
 	return new JScrollPane (this);
    }

    /*********************************************************************
     * Return each parameter (which corresponds to a row in this
     * table) in its XML representation.
     ********************************************************************/
    public Element[] toXML() {
	Object[] values = tableModel.getData();
	Vector v = new Vector();
	for (int i = 0; i < values.length; i++) {
	    if (! "".equals (values[i])) {
		v.addElement (MobyParameter.toXML (paramDefs[i].getName(),
						   values[i].toString()));
	    }
	}
	Element[] result = new Element [v.size()];
	v.copyInto (result);
	return result;
    }

    /*********************************************************************
     * Implement table header tool tips
     ********************************************************************/
    protected JTableHeader createDefaultTableHeader() {
	return new JTableHeader (columnModel) {
		public String getToolTipText (MouseEvent e) {
		    java.awt.Point p = e.getPoint();
		    int index = columnModel.getColumnIndexAtX (p.x);
		    int realIndex = 
			columnModel.getColumn (index).getModelIndex();
		    return tableModel.columnToolTips [realIndex];
		}
	    };
    }

    /**************************************************************************
     *
     **************************************************************************/
    class SpinnerEditor
 	extends AbstractCellEditor
	implements TableCellEditor {
	int currentRow = -1;
	JSpinField spinner;
	public SpinnerEditor (int min, int max) {
	    super();
	    spinner = new JSpinField (min, max);
	    spinner.addPropertyChangeListener (new java.beans.PropertyChangeListener() {
		    public void propertyChange (java.beans.PropertyChangeEvent evt) {
			try {
			    if (evt.getPropertyName().equals("value")) {
				// spinner.setValue() does not work here... why?
				// (that's why I set value directly to the tbale model)
//  				spinner.setValue (new Integer (evt.getNewValue().toString()).intValue());
				if (currentRow > -1) {
				    tableModel.setValueAt (evt.getNewValue().toString(),
							   currentRow, ParametersTableModel.COL_VALUE);
				}
			    }
			} catch (Exception e) {
			}
		    }
		});
	}

	public Component getTableCellEditorComponent(JTable table,
						     Object value,
						     boolean isSelected,
						     int row, int column) {
	    try {
		spinner.setValue (new Integer (value.toString()).intValue());
		currentRow = row;
	    } catch (NumberFormatException e2) {
	    }
	    return spinner;
	}

	public Object getCellEditorValue() {
	    return new Integer (spinner.getValue());
	}

    }

    /**************************************************************************
     *
     **************************************************************************/
    class MultiRenderer
	extends DefaultTableCellRenderer {

	TableCellRenderer[] cellRenderers;

	public MultiRenderer() {
	    setOpaque (true);
	    cellRenderers = new TableCellRenderer [paramDefs.length];
	    for (int i = 0; i < paramDefs.length; i++) {
		MobySecondaryData def = paramDefs[i];
		String type = def.getDataType();

		if (type.equals (MobyTags.MOBYBOOLEAN)) {
		    cellRenderers[i] = getDefaultRenderer (Boolean.class);
		} else {
		    cellRenderers[i] = getDefaultRenderer (String.class);
		}
	    }
	}

	public Component getTableCellRendererComponent (JTable table,
							Object value,
							boolean isSelected,
							boolean hasFocus,
							int row,
							int column) {

	    if (paramDefs [row].getDataType().equals (MobyTags.MOBYBOOLEAN))
		value = (value instanceof Boolean ?
			 value :
			 new Boolean (value.toString()));

	    return cellRenderers [row]
 		.getTableCellRendererComponent (table,
						value,
						isSelected,
 						hasFocus, row, column);
	}
    }

    /**************************************************************************
     *
     **************************************************************************/
    class MultiEditor
 	extends AbstractCellEditor
	implements TableCellEditor {

	TableCellEditor[] cellEditors;
 	protected TableCellEditor editor;

	public MultiEditor() {
	    cellEditors = new TableCellEditor [paramDefs.length];
	    for (int i = 0; i < paramDefs.length; i++) {
		MobySecondaryData def = paramDefs[i];
		String[] allowed = def.getAllowedValues();
		String type = def.getDataType();
		int min = def.getMinimumValue();
		int max = def.getMaximumValue();

		if (allowed.length > 0) {
		    cellEditors[i] = new DefaultCellEditor (new JComboBox (allowed));
		} else if (type.equals (MobyTags.MOBYFLOAT)) {
		    cellEditors[i] = ParametersTable.this.getDefaultEditor (Double.class);
		} else if (type.equals (MobyTags.MOBYINTEGER)) {
		    cellEditors[i] = new SpinnerEditor (min, max);
		} else if (type.equals (MobyTags.MOBYBOOLEAN)) {
		    cellEditors[i] = ParametersTable.this.getDefaultEditor (Boolean.class);
		} else if (type.equals (MobyTags.MOBYDATETIME)) {
		    cellEditors[i] = new DateTimeChooserEditor();
		} else {
		    cellEditors[i] = getDefaultEditor (String.class);
		}
	    }
	}

	public Component getTableCellEditorComponent(JTable table,
						     Object value,
						     boolean isSelected,
						     int row, int column) {
	    editor = cellEditors [row];

	    // delegate to new real editor
	    return editor.getTableCellEditorComponent (table, value, isSelected,
						       row, column);
	}

	public Object getCellEditorValue() {
	    return editor.getCellEditorValue();
	}
	public boolean stopCellEditing() {
	    return editor.stopCellEditing();
	}
	public void cancelCellEditing() {
	    editor.cancelCellEditing();
	}
	public void addCellEditorListener (CellEditorListener l) {
	    editor.addCellEditorListener (l);
	}
	public void removeCellEditorListener (CellEditorListener l) {
	    editor.removeCellEditorListener (l);
	}
	public boolean shouldSelectCell (EventObject anEvent) {
	    return editor.shouldSelectCell (anEvent);
	}

    }

    /**************************************************************************
     *
     * Data model of this table.
     *
     **************************************************************************/
    protected class ParametersTableModel
	extends AbstractTableModel {

	protected final static int COL_NAME  = 0;
	protected final static int COL_VALUE = 1;

	String[] columnToolTips =
	    new String[] { "Parameter (article) name",
			   "Parameter value" };
	String[] columnNames = new String[] { "Name",
					      "Value" };
	Class[] columnClasses = new Class[] { String.class,
					      String.class };
	MobySecondaryData[] paramDefs;
	Object[] values;

	public ParametersTableModel (MobySecondaryData[] paramDefs) {
	    super();
	    this.paramDefs = paramDefs;
	    values = new Object [paramDefs.length];
	    for (int i = 0; i < paramDefs.length; i++) {
		MobySecondaryData def = paramDefs[i];
		String defValue = def.getDefaultValue();
		values[i] = (UUtils.isEmpty (defValue) ? "" : defValue);
	    }
	}

	public Object[] getData() {
	    return values;
	}

	public Object getValueAt (int row, int col) {
	    if (col == COL_NAME)
		return paramDefs [row].getName();
	    else if (col == COL_VALUE)
		return values [row];
	    else return null;
	}

	public void setValueAt (Object aValue, int row, int col) {
	    if (col == COL_VALUE)
		values [row] = aValue;
	}

	public boolean isCellEditable (int row,
				       int col) {
	    return (col == COL_VALUE);
	}	

	public int getColumnCount() {
	    return columnNames.length;
	}

	public int getRowCount() {
	    return paramDefs.length;
	}

	public String getColumnName (int col) {
	    return columnNames [col];
	}

	public Class getColumnClass (int col) {
	    return columnClasses [col];
	}

    }


}
