// StatisticsPanel.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;

import org.biomoby.shared.MobyException;
import org.biomoby.shared.MobyService;
import org.biomoby.shared.MobyData;
import org.biomoby.shared.event.NotificationEvent;
import org.biomoby.shared.event.NotificationListener;
import org.biomoby.shared.event.Notifier;

import org.tulsoft.tools.gui.SwingUtils;

import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JPanel;
import javax.swing.JComponent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

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

import java.util.HashSet;

import org.biomoby.shared.MobyPrimaryDataSet;
import org.biomoby.shared.MobySecondaryData;

/**
 * A panel showing various statistics. At the beginning, it was meant
 * to be only a panel used in the Dashboard turorial (showing how to
 * develop and to add a new panel) but it became useful on its
 * own. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: StatisticsPanel.java,v 1.2 2006/02/20 05:51:10 senger Exp $
 */

public class StatisticsPanel
    extends AbstractPanel
    implements NotificationListener {

    private static org.apache.commons.logging.Log log =
       org.apache.commons.logging.LogFactory.getLog (StatisticsPanel.class);

    protected final static String SERVICES_ACCESS_ERROR =
	"An error happened when accessing a list of available services.\n\n";

    // numbers and indeces of individual statistics tables:

    // ...for services
    protected static final int S_NUMBER_OF_COLS = 8;
    protected static final int S_COL_AUTHORITY       = 0;
    protected static final int S_COL_SERVICE_NAME    = 1;
    protected static final int S_COL_COUNT_PRIM_INP  = 2;
    protected static final int S_COL_COUNT_SEC_INP   = 3;
    protected static final int S_COL_PARAM_TYPES     = 4;
    protected static final int S_COL_COUNT_OUT       = 5;
    protected static final int S_COL_IS_INP_COLL     = 6;
    protected static final int S_COL_IS_OUT_COLL     = 7;


    // associated model working behind the scene
    RegistryModel registryModel;

    // components that are used from more methods
    JTable table;
    ServiceStatisticsModel ssModel;

    /*********************************************************************
     * Default constructor.
     ********************************************************************/
    public StatisticsPanel() {
	super();
	panelIconFileName = "images/smallStat.gif";
    }

    /**************************************************************************
     *
     **************************************************************************/
    public JComponent getComponent (PropertyChannel aPropertyChannel) {
 	setPropertyChannel (aPropertyChannel);
	registryModel = createRegistryModel();
   	registryModel.addNotificationListener (this);

	if (pComponent != null) return pComponent;
	pComponent = new JPanel (new GridBagLayout(), true);

	ssModel = new ServiceStatisticsModel();
	TableSorter sorter = new TableSorter (ssModel);
	table = new JTable (sorter) {
		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 modelIndex = 
				    columnModel.getColumn (index).getModelIndex();


				return
				    ( (ServiceStatisticsModel)
				      ((TableSorter)table.getModel()).getTableModel() )
				    .getColumnToolTip (modelIndex);
			    }
			};
		}
	    };
	sorter.setTableHeader (table.getTableHeader());
	table.setShowHorizontalLines (false);
	updateServiceStatistics (null);
	JScrollPane scroll = new JScrollPane (table);

    	SwingUtils.addComponent (pComponent, scroll,      0, 0, 1, 1, BOTH, NWEST,  1.0, 1.0);

	return pComponent;
    }

    /**************************************************************************
     *
     **************************************************************************/
    public String getName() {
	return "Registry Statistics";
    }

    /**************************************************************************
     *
     **************************************************************************/
    public String getDescription() {
	return
	    "A panel showing some basic statistics " +
	    "about the contents of a Biomoby registry.";
    }

    /**************************************************************************
     *
     **************************************************************************/
    public boolean loadOnlyOnDemand() {
	return true;
    }

    /*********************************************************************
     * Implementation of a NotificationListener intereface
     ********************************************************************/
    public void notified (NotificationEvent event) {
	switch (event.getType()) {
	case Notifier.AUTHORITIES_RESET:
	case Notifier.AUTHORITIES_START:
	    if (log.isDebugEnabled())
		log.debug (event.toString());
	    ssModel.setData (new Object[][]
		{ { "Loading...", "", new Integer(0), new Integer(0), "", new Integer(0), "", ""} });
	    ssModel.fireTableDataChanged();
	    break;
 	case Notifier.AUTHORITIES_UPDATED:
	    if (log.isDebugEnabled())
		log.debug (event.toString());
	    MobyService[] updatedServices = (MobyService[])event.getDetails();
 	    updateServiceStatistics (updatedServices);
	    break;
	}
    }

    /**************************************************************************
     * This method picks good column sizes.
     **************************************************************************/
    private void initColumnSizes (JTable table) {
        ServiceStatisticsModel model =
	    (ServiceStatisticsModel)((TableSorter)table.getModel()).getTableModel();
        TableColumn column = null;
        Component comp = null;
        int headerWidth = 0;
        int cellWidth = 0;
        TableCellRenderer headerRenderer =
            table.getTableHeader().getDefaultRenderer();

        for (int i = 0; i < model.getColumnCount(); i++) {
            column = table.getColumnModel().getColumn(i);
	    comp = headerRenderer.getTableCellRendererComponent(
				 table, column.getHeaderValue(),
                                 false, false, 0, 0);
            headerWidth = comp.getPreferredSize().width;
            comp = table.getDefaultRenderer(model.getColumnClass(i)).
                             getTableCellRendererComponent(
                                 table, model.getLongValue (i),
                                 false, false, 0, i);
            cellWidth = comp.getPreferredSize().width;
	    column.setPreferredWidth (Math.max (headerWidth, cellWidth));
        }
    }

    /**************************************************************************
     *
     **************************************************************************/
    protected synchronized void updateServiceStatistics (final MobyService[] newServices) {
// 	java.io.StringWriter sw = new java.io.StringWriter (500);
// 	new Exception().printStackTrace (new java.io.PrintWriter (sw));
// 	log.debug ("USS STARTED: " + sw.toString());

	SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		Object[][] rows = null;
		public Object construct() {
		    try {
			MobyService[] services;
			if (newServices == null)
			    services = registryModel.getServices (StatisticsPanel.this);
			else
			    services = newServices;
			rows = new Object[services.length][S_NUMBER_OF_COLS];
			for (int i = 0; i < services.length; i++) {
			    MobyService service = services[i];
			    rows [i] [S_COL_AUTHORITY] = service.getAuthority();
			    rows [i] [S_COL_SERVICE_NAME] = service.getName();
			    
			    MobyData[] pInputs = service.getPrimaryInputs();
			    MobyData[] sInputs = service.getSecondaryInputs();
			    MobyData[] outputs = service.getPrimaryOutputs();
			    rows [i][S_COL_COUNT_PRIM_INP] = new Integer (pInputs.length);
			    rows [i][S_COL_COUNT_SEC_INP] = new Integer (sInputs.length);
			    
			    StringBuffer buf = new StringBuffer();
			    HashSet set = new HashSet();
			    for (int j = 0; j < sInputs.length; j++) {
				String abbrev =
				    ( ((MobySecondaryData)sInputs[j]).getDataType()
				      .substring (0, 1));
				if (! set.contains (abbrev)) {
				    buf.append (abbrev);
				    set.add (abbrev);
				}
			    }
			    rows [i] [S_COL_PARAM_TYPES] = buf.toString();
			    rows [i] [S_COL_COUNT_OUT] = new Integer (outputs.length);
			    
			    boolean found = false;
			    for (int j = 0; j < pInputs.length; j++) {
				if (pInputs[j] instanceof MobyPrimaryDataSet) {
				    found = true;
				    break;
				}
			    }
			    rows [i] [S_COL_IS_INP_COLL] = (found ? " yes " : "");
			    found = false;
			    for (int j = 0; j < outputs.length; j++) {
				if (outputs[j] instanceof MobyPrimaryDataSet) {
				    found = true;
				    break;
				}
			    }
			    rows [i] [S_COL_IS_OUT_COLL] = (found ? " yes " : "");
			}    
			
		    } catch (MobyException e) {
			exception = e;
		    }
		    return rows;  // not used here
		}
		
		// runs on the event-dispatching thread.
		public void finished() {
		    if (exception != null) {
			error (SERVICES_ACCESS_ERROR, exception);
			ssModel.setData (new Object[][] {});
		    } else {
			ssModel.setData (rows);
		    }
		    initColumnSizes (table);
		    ssModel.fireTableDataChanged();
		}
	    };
	worker.start(); 
    }

    /**************************************************************************
     *
     * Model for filling a table of service statistics
     *
     **************************************************************************/
    class ServiceStatisticsModel
	extends AbstractTableModel {

	private String[] columnNames = new String[] {
	    "Authority",
	    "Service name",
	    "# Prims",
	    "# Secs",
	    "Params",
	    "# Outs",
	    "Inp Coll",
	    "Out Coll",
	};

	// tool-tips for column headers
	private String[] cToolTips = new String[] {
	    "Service authority",
	    "Service name",
	    "Number of primary inputs",
	    "Maximum number of secondary inputs (parameters)",
	    "Types of parameters (S=String, I=Integer, F=Float, B=Boolean, D=DateTime)",
	    "Number of outputs",
	    "Is any of the primary inputs of this service a collection?",
	    "Is any of the outputs of this service a collection?",
	};

	private Object[] longValues = {
	    "xxxxxxxxxxxxxxxxxxxxx",
	    "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
	    new Integer (1),
	    new Integer (1),
	    "XXXXX",
	    new Integer (1),
	    "  xxx  ",
	    "  xxx  ",
	};

	private Object[][] data = new Object[][] {};

	public int getColumnCount() {
	    return columnNames.length;
	}
	
	public int getRowCount() {
	    return data.length;
	}
	
	public String getColumnName (int col) {
	    return columnNames [col];
	}
	
	public Object getValueAt (int row, int col) {
	    try {
		return data[row][col];
	    } catch (ArrayIndexOutOfBoundsException e) {
		return null;
	    }
	}
	
	public Class getColumnClass (int c) {
	    return getValueAt (0, c).getClass();
	}
	
	public String getColumnToolTip (int column) {
	    return cToolTips [column];
	}

	public Object getLongValue (int column) {
	    return longValues [column];
	}

// 	/*
// 	 * Don't need to implement this method unless your table's
// 	 * data can change.
// 	 */
// 	public void setValueAt(Object value, int row, int col) {
// 	    data[row][col] = value;
// 	    fireTableCellUpdated(row, col);
// 	}

	protected void setData (Object[][] data) {
	    this.data = data;
//  	    fireTableDataChanged();
	}



    }

}
