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

package org.biomoby.service.dashboard;

import org.biomoby.shared.MobyDataType;
import org.biomoby.shared.MobyNamespace;
import org.biomoby.shared.MobyPrimaryData;
import org.biomoby.shared.MobyPrimaryDataSimple;
import org.biomoby.shared.MobyPrimaryDataSet;

import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JList;
import javax.swing.JComboBox;
import javax.swing.ListCellRenderer;
import javax.swing.DefaultComboBoxModel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.AbstractCellEditor;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.PopupMenuEvent;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.util.Vector;

/**
 * A swing JTable that collects definitions of primary data (input or
 * ouput) for a service. Used in the service registration. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: PrimaryDataTable.java,v 1.7 2006/02/12 18:47:39 senger Exp $
 */

public class PrimaryDataTable
    extends CommonDataTable {

    // indeces of the used columns
    public final static int COL_ARTICLE   = PrimaryDataTableModel.COL_ARTICLE;
    public final static int COL_DATATYPE  = PrimaryDataTableModel.COL_DATATYPE;
    public final static int COL_IN_SET    = PrimaryDataTableModel.COL_IN_SET;
    public final static int COL_NAMESPACE = PrimaryDataTableModel.COL_NAMESPACE;

    /*********************************************************************
     * Default constructor.
     ********************************************************************/
    public PrimaryDataTable() {
	super();
	tableModel = new PrimaryDataTableModel();
	setModel (tableModel);
	createItself();
    }

    /*********************************************************************
     *
     ********************************************************************/
    public MobyPrimaryData[] getData() {
	Vector v = tableModel.getData();
	MobyPrimaryData[] result = new MobyPrimaryData [v.size()];
	v.copyInto (result);
	return result;
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected void createItself() {
	super.createItself();

	// set renderers for namespaces column
	TableColumn nsColumn =
	    getColumnModel().getColumn (COL_NAMESPACE);
	nsColumn.setCellRenderer (new NamespaceRenderer());
 	nsColumn.setCellEditor (new NamespaceEditor());
    }

    /**************************************************************************
     *
     **************************************************************************/
    class NamespaceRenderer extends DefaultTableCellRenderer {

	public NamespaceRenderer() {
	    setOpaque (true);
	}	
	protected void setValue (Object value) {
	    if (value == null) {
		setText ("");
	    } else {
		MobyNamespace[] nss = ((MobyPrimaryData)value).getNamespaces();
		if (nss.length == 0)
		    setText ("");
		else if (nss.length == 1)
		    setText (nss[0].getName());
		else
		    setText (nss.length + " chosen");
	    }
	}
    }

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

	// this is what is diplay when some namespaces are present
	DefaultComboBoxModel comboModel = new DefaultComboBoxModel();
	JComboBox comboBox;

	// used when there are no namespaces (so a combo box is useless)
	DefaultTableCellRenderer defaultRenderer = new DefaultTableCellRenderer();

	// remember where this combo box is located in (we will need
	// it to fire 'table update' when we remove some namespaces)
	JTable table;
	int row, col;

	// remember where the displayed namespaces come from
	MobyPrimaryDataSimple lastSimple = null;

	public NamespaceEditor() {
	    defaultRenderer.setText ("");
	    comboBox = new JComboBox (comboModel);
	    comboBox.setOpaque (true);
	    comboBox.setRenderer (new ComboBoxRenderer());
	    comboBox.addActionListener (new ActionListener() {
		    public void actionPerformed (ActionEvent e) {
			if ( (e.getModifiers() & ActionEvent.CTRL_MASK) > 0) {
			    JComboBox cb = (JComboBox)e.getSource();
			    Object selectedItem = cb.getSelectedItem();
			    if (selectedItem != null) {
				String namespace = selectedItem.toString();
				if (lastSimple != null) {
				    lastSimple.removeNamespace (namespace);
				    ((AbstractTableModel)table.getModel()).fireTableCellUpdated (row, col);

				}
			    }
			}
			 // make the renderer reappear
			fireEditingStopped();
		    }
		});
	    comboBox.addPopupMenuListener (new PopupMenuListener() {
		    public void popupMenuCanceled (PopupMenuEvent e) {
			// make the renderer reappear
			fireEditingStopped();
		    }
		    public void popupMenuWillBecomeVisible (PopupMenuEvent e) {}
		    public void popupMenuWillBecomeInvisible (PopupMenuEvent e) {}
		});
	}

	public Object getCellEditorValue() {
	    return null;
	}

	public Component getTableCellEditorComponent (JTable aTable,
						      Object value,
						      boolean isSelected,
						      int aRow, int aCol) {

	    // remember (used when a namespace is being deleted)
	    this.table = aTable;
	    this.row = aRow;
	    this.col = aCol;

	    // fill combo box with the current namespaces
	    if (value == null) {
// 		cancelCellEditing();
		return defaultRenderer;
	    }
	    lastSimple = null;
	    if (value instanceof MobyPrimaryDataSimple) {
		lastSimple = (MobyPrimaryDataSimple)value;
	    } else if (value instanceof MobyPrimaryDataSet) {
		MobyPrimaryDataSimple[] simples = ((MobyPrimaryDataSet)value).getElements();
		if (simples.length > 0)
		    lastSimple = simples[0];
	    }
	    if (lastSimple != null) {
		MobyNamespace[] nss = lastSimple.getNamespaces();
		if (nss.length > 0) {
		    comboModel.removeAllElements();
		    for (int i = 0; i < nss.length; i++)
			comboModel.addElement (nss[i].getName());
		    return comboBox;
		}
	    }
// 	    cancelCellEditing();
	    return defaultRenderer;
	}
    }

    /**************************************************************************
     *
     **************************************************************************/
    class ComboBoxRenderer extends JLabel
	implements ListCellRenderer {

	public ComboBoxRenderer() {
	    setOpaque (true);
	    setIcon (trashIcon);
	}

	public Component getListCellRendererComponent (JList list,
						       Object value,
						       int index,
						       boolean isSelected,
						       boolean cellHasFocus) {
	    if (isSelected) {
		setBackground (list.getSelectionBackground());
		setForeground (list.getSelectionForeground());
		if (index > -1)
		    list.setToolTipText ("To remove a namespace, hold CTRL and select it");

	    } else {
		setBackground (list.getBackground());
		setForeground (list.getForeground());
	    }
	    setText (value == null ? "" : value.toString());
	    return this;
	}
    }

    /**************************************************************************
     *
     **************************************************************************/
    protected class PrimaryDataTableModel
	extends CommonDataTableModel {

	public final static int COL_ARTICLE   = 1;
	public final static int COL_DATATYPE  = 2;
	public final static int COL_IN_SET    = 3;
	public final static int COL_NAMESPACE = 4;

	public PrimaryDataTableModel() {
	    columnToolTips = new String[] {
		"Click in this column to remove data from this service",
		"Click to edit article name",
		"Select a row, then select a data type in the data types tree",
		"Check the box if this data should be a collection",
		"Select a row, then select one or more namespaces in the namespaces tree"
	    };
	    columnNames = new String[] {
		"Remove",
		"Article name",
		"Data Type",
		"Collection",
		"Namespaces"
	    };
	    columnClasses = new Class[] {
		Integer.class,
		String.class,
		String.class,
		Boolean.class,
		MobyPrimaryData.class
	    };
	}

	public void addEmptyData() {
	    data.addElement (new MobyPrimaryDataSimple (""));
	    int rowCount = getRowCount();
	    fireTableRowsInserted (rowCount-1, rowCount-1);
	}

	public Object getValueAt (int row, int col) {
	    try {
		MobyPrimaryData d = (MobyPrimaryData)data.elementAt (row);
		switch (col) {
		case COL_BUTTON:    return new Integer (row);
		case COL_ARTICLE:   return d.getName();
		case COL_DATATYPE:  return getDataTypeName (d);
		case COL_IN_SET:    return new Boolean (d instanceof MobyPrimaryDataSet);
		case COL_NAMESPACE: return d;
		}
	    } catch (Exception e) { }
	    return "";
	}

	private String getDataTypeName (MobyPrimaryData theData) {
	    if (theData == null) return "";
	    MobyDataType dataType = theData.getDataType();
	    if (dataType == null) return "";
	    String name = dataType.getName();
	    return (name == null ? "" : name);
	}

	public void setValueAt (Object value, int row, int col) {
	    // TBD: I do not know why this method is called after a
	    // last row has been deleted - so the 'row' is actually
	    // out of bounds; but if other than last row is deleted
	    // things work fine... (that's why i put here try{} but I
	    // am not happy about it
	    if (value == null) return;
	    try {
		MobyPrimaryData d = (MobyPrimaryData)data.elementAt (row);
		switch (col) {
		case COL_ARTICLE:
		    d.setName (value.toString());
		    break;
		case COL_DATATYPE:
		    d.setDataType (new MobyDataType (value.toString()));
		    break;
		case COL_IN_SET:
		    if ( ((Boolean)value).booleanValue() ) {
			// change data to a collection (if necessary)
			if (d instanceof MobyPrimaryDataSimple) {
			    MobyPrimaryDataSet set = new MobyPrimaryDataSet (d.getName());
			    ((MobyPrimaryDataSimple)d).setName ("");  // article name in collection is nameless
			    set.addElement ((MobyPrimaryDataSimple)d);
			    synchronized (data) {
				data.removeElementAt (row);
				data.insertElementAt (set, row);
			    }
			}
		    } else {
			// change data to a simple (if necessary)
			if (d instanceof MobyPrimaryDataSet) {
			    MobyPrimaryDataSimple[] simples =
				((MobyPrimaryDataSet)d).getElements();
			    if (simples.length > 0) {
				simples[0].setName (d.getName());
				synchronized (data) {
				    data.removeElementAt (row);
				    data.insertElementAt (simples[0], row);
				}
			    }
			}
		    }
		    break;
		}
		fireTableCellUpdated (row, col);
	    } catch (Exception e) { }
	}

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

}
