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

package org.biomoby.service.dashboard;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Date;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JRadioButton;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.tree.DefaultMutableTreeNode;

import org.biomoby.service.dashboard.data.DataContainer;
import org.biomoby.service.dashboard.data.ServiceInputPanel;
import org.biomoby.shared.MobyDataType;
import org.biomoby.shared.MobyException;
import org.biomoby.shared.MobyService;
import org.tulsoft.shared.UUtils;
import org.tulsoft.tools.gui.AwtUtils;
import org.tulsoft.tools.gui.JFileChooserWithHistory;
import org.tulsoft.tools.gui.JTextFieldWithHistory;
import org.tulsoft.tools.gui.SwingUtils;

/**
 * A panel allowing to formulate input data and to call an arbitrary
 * Biomoby service. Its interface is however not that user-friendly,
 * and it is supposed to be used more or less for testing purposes in
 * time when a new service is being created and developed. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: SimpleClientPanel.java,v 1.14 2008/12/03 15:21:12 groscurt Exp $
 */

public class SimpleClientPanel
    extends AbstractPanel {

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

    /** A property name. Its value contains a name of color that will
     *  be used to display service name.
     */
    protected static final String P_S_TITLE_COLOR = "simpleclient.service.title.color";

    /** A property name. Its value contains a name of color that will
     *  be used as background for editable console with XML input.
     */
    protected static final String P_S_EDIT_BGCOLOR = "simpleclient.service.edit.bgcolor";

    // some texts
    protected static final String INIT_SELECT =
	"<html><center><font color='red'>Start by selecting a service<br>" +
	"that you wish to call.</font></center>";

    protected static final String CALLER_ERROR =
	"Sorry, an error happened when calling a service.\n\n";

    protected static final Insets BREATH_UP = new Insets (6,6,0,6);
    protected static final Insets BREATH_DOWN = new Insets (0,6,6,6);

    // associated models working behind the scene
    RegistryModel registryModel;
    ServiceCallerModel callerModel;

    // components that are used from more methods
    JLabel selService;
    CommonConsole input;
    JFileChooserWithHistory outFile, inFile;
    JButton runButton, stopButton;
    JCheckBox iShowXML, iFromFile;
    JTextFieldWithHistory className, endpoint;
    JPanel dataTablesPanel;
    ServiceInputPanel inputDataTables;
    MySwingWorker runWorker;
    ResultsPanel results;
    JComponent resultsComp;
    JSplitPane resSplit;
//     JTextFieldWithHistory recentServices;

    boolean selectionAllowed = false;

    // shared icons
    protected static Icon runIcon, runIconDis;
    protected static Icon stopIcon, stopIconDis;
    protected static Icon addDataIcon, addDataIconDis;

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

    /*********************************************************************
     * Load shared icons.
     ********************************************************************/
    protected void loadIcons() {
	super.loadIcons();

	if (runIcon == null) runIcon = loadIcon ("images/smallRun.gif");
	if (runIconDis == null) runIconDis = loadIcon ("images/smallRun_dis.gif");

	if (stopIcon == null) stopIcon = loadIcon ("images/smallCancel.gif");
	if (stopIconDis == null) stopIconDis = loadIcon ("images/smallCancel_dis.gif");

	if (addDataIcon == null) addDataIcon = loadIcon ("images/smallAnnotate.gif");
	if (addDataIconDis == null) addDataIconDis = loadIcon ("images/smallAnnonate_dis.gif");

    }
	
    /**************************************************************************
     *
     **************************************************************************/
    public JComponent getComponent (PropertyChannel aPropertyChannel) {
 	setPropertyChannel (aPropertyChannel);
	registryModel = createRegistryModel();
	callerModel = new ServiceCallerModel();
	callerModel.setPropertyChannel (aPropertyChannel);

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

	// result panel
	results = new ResultsPanel();
	resultsComp = results.getComponent (aPropertyChannel);
	propertyChannel.addPropertyChangeListener (new PropertyChangeListener() {
		public void propertyChange (PropertyChangeEvent e) {
		    if (DP_DETACH_VIEW.equals (e.getPropertyName())) {

			MobyService srv =
			    (MobyService)propertyChannel.get (DP_SC_SERVICE);
			String title = (srv == null ? "Service results" :
					srv.getName() + " at " + new Date());
			JComponent current = results.getComponent (propertyChannel);
			results.adjustForDetachement();
			JFrame newFrame =
			    SwingUtils.createSoftMainFrame (current,
							    title);
			Dimension dim = current.getPreferredSize();
			SwingUtils.showMainFrameRelativeTo
			    (pComponent, newFrame, (int)dim.getWidth(), (int)dim.getHeight());
  			results = new ResultsPanel();
 			resultsComp = results.getComponent (propertyChannel);
			resSplit.setBottomComponent (resultsComp);
		    }
		}});


	// panel with all the fields (except for building inputs)
	JPanel controls = getControlPanel();

	// panel with input data
	JPanel inputData = getInputPanel();
	updateInputDataPanel (INIT_SELECT);

	// service ontology tree
	JPanel sBoard = new JPanel (new GridBagLayout());
// 	JLabel lRecent = new JLabel ("Recently called services");
// 	recentServices = createText (null,
// 				     DP_SC_SERVICES,
// 				     DP_SC_SERVICES);
// 	recentServices.addActionListener (new ActionListener() {
// 		public void actionPerformed (ActionEvent e) {
// 		    String contents =  ((JTextFieldWithHistory)e.getSource()).getText();
// 		    if (! "".equals (contents) && selectionAllowed) {
// 			System.out.println ("SEL: " + contents);
// 			selectService (contents);
// 		    }
// 		}
// 	    });
	
	ServicesBoard servicesBoard =
	    new ServicesBoard (registryModel,
			       null,
			       propertyChannel,
			       new CustomServicesTree (registryModel,
						       null));
	log.debug ("Services tree update started");
	servicesBoard.updateTree (CommonTree.SORTED_BY_AUTHORITY);
//    	SwingUtils.addComponent (sBoard, lRecent,        0, 0, 1, 1, NONE, NWEST,  0.0, 0.0, BREATH_UP);
//    	SwingUtils.addComponent (sBoard, recentServices, 0, 1, 1, 1, HORI, NWEST,  1.0, 0.0, BREATH_DOWN);
   	SwingUtils.addComponent (sBoard, servicesBoard,  0, 2, 1, 1, BOTH, NWEST,  1.0, 1.0);

 	// split it into moving panels
 	JSplitPane split = hSplit (resSplit = vSplit (hSplit (controls,
							      sBoard, 0.5),
						      resultsComp, 0.1),
				   inputData,
				   0.5);

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

	// initialize by a last used service name from preferences
	String lastServiceName = getPrefValue (DP_SC_SERVICE_NAME, "");
	if (! "".equals (lastServiceName))
	    selectService (lastServiceName);

	return pComponent;
    }

    /**************************************************************************
     * Create a sub-panel for all the controls...
     **************************************************************************/
    protected JPanel getControlPanel() {
	JPanel p = new JPanel (new GridBagLayout());

	// (selected) service name
	selService = new JLabel();
	selService.setFont (FAT_BORDER_FONT);
	selService.setForeground
	    (GraphColours.getColour (DashboardConfig.getString (P_S_TITLE_COLOR, null),
				     Color.blue));

	// how to invoke the service
	JPanel howTo = createTitledPanel ("Service invocation");
	endpoint = createText (null, DP_ENDPOINT, DP_ENDPOINT);
	className = createText (null, DP_IMPL_CLASS, DP_IMPL_CLASS);

	ButtonGroup group = new ButtonGroup();
	JRadioButton htNone, htRegistry, htEndpoint, htLocal, htNewURL;
	group.add (htEndpoint = createHowToButton ("Use service's usual endpoint", DP_CS_URL));
	htNone = createHowToButton ("No real call, just show/echo input", DP_CS_NONE);
	htNone.addItemListener(
		new ItemListener() {
			public void itemStateChanged (ItemEvent e) {
			    boolean enabled = (e.getStateChange() == ItemEvent.SELECTED);		     
			    if (enabled) runButton.setText(" Show Input ");
			    else {
			    	if (propertyChannel.getBoolean(DP_INP_PING, false)){
				    	runButton.setText(" Ping Service "); 
			    	}
			    	else {
			    		runButton.setText(" Call Service ");
			    	}
			    }
			}
	     });	
	group.add (htNone);
	group.add (htRegistry = createHowToButton ("Ask registry where service is, and call it", DP_CS_REGISTRY));
	group.add (htNewURL   = createHowToButton ("Use this endpoint", DP_CS_NEWURL));
	group.add (htLocal    = createHowToButton ("Use this local class", DP_CS_CLASS));

	// run the service
	// determine button text from preferences
	boolean usingPing = getPrefValue (DP_INP_PING, false);
	boolean usingAsBytes = getPrefValue (DP_INP_ASBYTES, false);
	String runLabel = " Call Service ";
	if (propertyChannel.getString(DP_CALL_SERVICE).equals(DP_CS_NONE))
		runLabel = " Show Input ";
	else if (usingPing) runLabel = " Ping Service ";
	
	runButton =
	    createButton (runLabel,
			  "Invoke selected service",
			  KeyEvent.VK_C,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  MobyService srv = (MobyService)propertyChannel.get (DP_SC_SERVICE);
				  if (srv != null) {
				      // if the service requires authentication this is asked
				      if(propertyChannel.getBoolean(DP_AUTHENTICATION, false)) {
					  askForAuthentication();
				      }
				      // remove possible remnants
				      else {
					  propertyChannel.remove(DP_USER_AUTHENTICATION);
					  propertyChannel.remove(DP_PASSWORD_AUTHENTICATION);
				      }
				      runWorker = new MySwingWorker (srv);
				      runWorker.start(); 
				  }
			      }
			  });
	runButton.setIcon (runIcon);
	runButton.setDisabledIcon (runIconDis);
 	runButton.setEnabled (false);  // will enable when a service is selected

	stopButton =
	    createButton (" Stop service ",
			  "Cancel connection to a running service",
			  KeyEvent.VK_S,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (runWorker != null) {
// 				      runWorker.interrupt();  // no sense with the current Biomoby API
				      runWorker.cancel();
				  }
				  serviceFinished();
			      }
			  });
	stopButton.setIcon (stopIcon);
	stopButton.setDisabledIcon (stopIconDis);
 	stopButton.setEnabled (false);  // will enable when a service is called

	JPanel buttonPanel = createButtonPanel (new JButton[] { runButton,
								stopButton });
	
	JCheckBox asPing = createCheckBox
	    ("'Ping' this service",
	     usingPing, -1,
	     new ItemListener() {
		 public void itemStateChanged (ItemEvent e) {
		     boolean enabled = (e.getStateChange() == ItemEvent.SELECTED);
		     setPropertySelect (enabled, DP_INP_PING);
		     setPrefValue (DP_INP_PING, enabled);
		     propertyChannel.put (DP_INP_PING,
					  new Boolean (enabled).toString());		     
		     if (enabled) {
		    	 if (propertyChannel.getString(DP_CALL_SERVICE).equals(DP_CS_NONE))
		    		 runButton.setText(" Show Input ");
		    	 else
		    		 runButton.setText(" Ping Service ");
		     }
		     else {
		    	 if (propertyChannel.getString(DP_CALL_SERVICE).equals(DP_CS_NONE))
		    		 runButton.setText(" Show Input ");
		    	 else
		    		 runButton.setText(" Call Service ");
		     }
		 }
	     });
    setPropertySelect (usingPing, DP_INP_PING);
    asPing.setToolTipText ("A Moby 'Ping' is used to determine if the service is responsive " +
    		"and if it responds in an API-compliant manner.");
	Font font = asPing.getFont();
	asPing.setFont (font.deriveFont (Math.max (1, font.getSize2D() - 1)));	

	JCheckBox asBytes = createCheckBox
	    ("Send data to service as a byte array",
	     usingAsBytes, -1,
	     new ItemListener() {
		 public void itemStateChanged (ItemEvent e) {
		     boolean enabled = (e.getStateChange() == ItemEvent.SELECTED);
		     setPropertySelect (enabled, DP_INP_ASBYTES);
		     setPrefValue (DP_INP_ASBYTES, enabled);
		     propertyChannel.put (DP_INP_ASBYTES,
					  new Boolean (enabled).toString());		     
		 }
	     });
    setPropertySelect (usingAsBytes, DP_INP_ASBYTES);
        asBytes.setToolTipText ("It should not have any effect on result; it is for debugging");
	font = asBytes.getFont();
	asBytes.setFont (font.deriveFont (Math.max (1, font.getSize2D() - 1)));

	JCheckBox authenticationBox = createCheckBox("Use a user/password for service authentication", false, -1,
						     new ItemListener() {
							 public void itemStateChanged(ItemEvent e) {
							     propertyChannel.put(DP_AUTHENTICATION, e.getStateChange() == ItemEvent.SELECTED);
							 }
						     });
	authenticationBox.setToolTipText("If the service requires authentication." +
					 "A dialog will open if you call the service to enter the authentication.");
	font = authenticationBox.getFont();
	authenticationBox.setFont(font.deriveFont(Math.max(1, font.getSize2D() -1)));

 	SwingUtils.addComponent (howTo, htEndpoint, 0, 0, 2, 1, HORI, NWEST,  1.0, 0.0);
 	SwingUtils.addComponent (howTo, htNewURL,   0, 1, 1, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (howTo, endpoint,   1, 1, 1, 1, HORI, NWEST,  1.0, 0.0);
 	SwingUtils.addComponent (howTo, htRegistry, 0, 2, 2, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (howTo, htLocal,    0, 3, 1, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (howTo, className,  1, 3, 1, 1, HORI, NWEST,  1.0, 0.0);
 	SwingUtils.addComponent (howTo, htNone,     0, 4, 2, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (howTo, asPing,		0, 5, 2, 1, NONE, NWEST,  0.0, 0.0, BREATH_TOP);	
 	SwingUtils.addComponent (howTo, asBytes,    0, 6, 2, 1, NONE, NWEST,  0.0, 0.0);
	SwingUtils.addComponent (howTo, authenticationBox,0,7,2,1,NONE, NWEST, 0,0);

	Component glue = Box.createVerticalGlue();
 	SwingUtils.addComponent (p, selService,  0, 0, 1, 1, NONE, CENTER, 0.0, 0.0);
 	SwingUtils.addComponent (p, howTo,       0, 1, 1, 1, HORI, NWEST,  1.0, 0.0);
 	SwingUtils.addComponent (p, buttonPanel, 0, 2, 1, 1, NONE, CENTER, 0.0, 0.0, BREATH_TOP);
 	SwingUtils.addComponent (p, glue,        0, 3, 1, 1, VERT, NWEST,  0.0, 1.0);
	return p;
    }

    /**
     * Opens a dialog to enter the authentication information
     */
    private void askForAuthentication() {
        final JDialog dialog = new JDialog( new JFrame(), "Authentication", true );
        JPanel panel = new JPanel( new GridBagLayout() );
        panel.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );

        Insets insets = new Insets( 4, 4, 4, 4 );

        JLabel label = new JLabel( "Please enter your username and password !" );
        SwingUtils.addComponent( panel, label, 0, 0, 2, 1, HORI, CENTER, 1, 0, insets );

        label = new JLabel( "User: " );
        SwingUtils.addComponent( panel, label, 0, 1, 1, 1, HORI, NWEST, 0, 0, insets );

        final JTextField userField = new JTextField( propertyChannel.getString( DP_USER_AUTHENTICATION ), 20 );
        SwingUtils.addComponent( panel, userField, 1, 1, 1, 1, HORI, NWEST, 0, 0, insets );

        label = new JLabel( "Password: " );
        SwingUtils.addComponent( panel, label, 0, 2, 1, 1, HORI, NWEST, 0, 0, insets );

        final JPasswordField passwordField = new JPasswordField(
                propertyChannel.getString( DP_PASSWORD_AUTHENTICATION ), 20 );
        SwingUtils.addComponent( panel, passwordField, 1, 2, 1, 1, HORI, NWEST, 0, 0, insets );

        JButton button = new JButton( "Enter..." );
        button.addActionListener( new ActionListener() {
            public void actionPerformed( ActionEvent e ) {
                // sets the user and password in the propertychannel 
                propertyChannel.put( DP_USER_AUTHENTICATION, userField.getText() );
                propertyChannel.put( DP_PASSWORD_AUTHENTICATION, new String(passwordField.getPassword() ));

                dialog.setVisible( false );
            }
        } );
        SwingUtils.addComponent( panel, button, 0, 3, 2, 1, HORI, CENTER, 0, 0, insets );

        dialog.setContentPane( panel );
        dialog.pack();
        dialog.setLocationRelativeTo( this );
        dialog.setVisible( true );
    }

    /**************************************************************************
     * Select/unselect using 'send as bytes' or 'ping'...
     **************************************************************************/
    protected void setPropertySelect (boolean enabled, String property) {
    	setPrefValue (property, enabled);
    	propertyChannel.put (property, new Boolean (enabled).toString());
    }

    /**************************************************************************
     * Create a specialized radio button - for various ways how to
     * call a service.
     **************************************************************************/
    private JRadioButton createHowToButton (String title, String howTo) {
	JRadioButton radio = new JRadioButton (title);
	radio.setActionCommand (howTo);
	radio.addActionListener (howToListener);
	String initHowTo = getPrefValue (DP_CALL_SERVICE, DP_CS_NONE);
	if (howTo.equals (initHowTo)) {
	    radio.setSelected (true);
	    radio.setEnabled (true);
	    propertyChannel.put (DP_CALL_SERVICE, howTo);
	}
	endpoint.setEnabled (initHowTo.equals (DP_CS_NEWURL));
	className.setEnabled (initHowTo.equals (DP_CS_CLASS));
	return radio;
    }

    //
    private ActionListener howToListener = new ActionListener() {
	    public void actionPerformed (ActionEvent e) {
		String howTo = e.getActionCommand();
		setPrefValue (DP_CALL_SERVICE, howTo);
		propertyChannel.put (DP_CALL_SERVICE, howTo);
		endpoint.setEnabled (howTo.equals (DP_CS_NEWURL));
		className.setEnabled (howTo.equals (DP_CS_CLASS));
	    }
	};

    /**************************************************************************
     * Create a specialized check box for "what to do with input/output"...
     **************************************************************************/
    private JCheckBox createDataBox (String title,
				     final String preferenceKey,
				     boolean defaultValue) {
	boolean initValue = getPrefValue (preferenceKey, defaultValue);
	propertyChannel.put (preferenceKey,
			     new Boolean (initValue).toString());
	return createCheckBox
	    (title, initValue, -1,
	     new ItemListener() {
		 public void itemStateChanged (ItemEvent e) {
		     boolean enabled = (e.getStateChange() == ItemEvent.SELECTED);
		     setPrefValue (preferenceKey, enabled);
		     propertyChannel.put (preferenceKey,
					  new Boolean (enabled).toString());
		     inFile.setEnabled (iFromFile.isSelected());
		 }
	     });
    }

    /**************************************************************************
     * Create a sub-panel for service inputs...
     **************************************************************************/
    protected JPanel getInputPanel() {
	JPanel inputData = new JPanel (new GridBagLayout());
	inputData.setBorder (createFatBorder
			     ("Service Input Data",
			      GraphColours.getColour ("cadetblue", Color.blue)));

	// upper part (tables, input file, show-xml check-box)
	JPanel upper = new JPanel (new GridBagLayout());

	dataTablesPanel = new JPanel (new GridBagLayout());

	iFromFile  = createDataBox ("Take an input from this XML file", DP_SC_IN_FILE, false);
	inFile = createFileSelector ("Select input XML for calling a service",
				      "Select",
				      null,
				      DP_SC_INPUT_FILE,
				      DP_SC_INPUT_FILE);
	inFile.setEnabled (iFromFile.isSelected());

	iShowXML = createDataBox ("Show input as XML",        DP_INP_SHOWXML, true);

   	SwingUtils.addComponent (upper, dataTablesPanel, 0, 0, 2, 1, BOTH, NWEST,  1.0, 1.0);
 	SwingUtils.addComponent (upper, iFromFile,       0, 1, 1, 1, NONE, NWEST,  0.0, 0.0);
 	SwingUtils.addComponent (upper, inFile,          1, 1, 1, 1, HORI, NWEST,  1.0, 0.0);
 	SwingUtils.addComponent (upper, iShowXML,        0, 2, 1, 1, NONE, NWEST,  0.0, 0.0);

	// lower part (console showing/editing input XML)
	input = new EditableConsole();
	input.setAppendMode (false);
	input.setVerboseMode (false);

 	// split upper and lower parts into moving panels
 	JSplitPane split = vSplit (upper, input, 0.5);
   	SwingUtils.addComponent (inputData, split, 0, 0, 1, 1, BOTH, NWEST,  1.0, 1.0);
	return inputData;
    }

    class EditableConsole
	extends CommonConsole {
		
	public EditableConsole() {
	super();
	textArea.setEditable (true);
	textArea.setBackground
 	    (GraphColours.getColour (DashboardConfig.getString (P_S_EDIT_BGCOLOR, null),
 				     textArea.getBackground()));
	}
    }

    /**************************************************************************
     * Replace the global 'dataTablesPanel' with an information text.
     **************************************************************************/
    protected void updateInputDataPanel (String info) {
	dataTablesPanel.removeAll();
	AwtUtils.redisplay (dataTablesPanel);

	JLabel text = new JLabel (info);
	SwingUtils.addComponent (dataTablesPanel, text,  0, 0, 1, 1, NONE, CENTER, 0.0, 0.0);
	dataTablesPanel.validate();
	return;
    }

    /**************************************************************************
     * Replace the global 'dataTablesPanel' with fields defining the
     * input data for the given service.
     **************************************************************************/
    protected void updateInputDataPanel (MobyService service,
					 MobyDataType[] dataTypes) {
	dataTablesPanel.removeAll();

	inputDataTables = new ServiceInputPanel (service, dataTypes);
	SwingUtils.addComponent (dataTablesPanel, inputDataTables, 0, 0, 1, 1, BOTH, NWEST, 1.0, 1.0);
	dataTablesPanel.validate();
    }

    /**************************************************************************
     * Find 'dataTypeToBeFound in 'dataTypes' and return it. The same
     * could be achieved by calling registryModel.getDataType() but
     * here I do not want to create yet another swing worker for it.
     **************************************************************************/
    protected MobyDataType findDataType (String dataTypeToBeFound,
					 MobyDataType[] dataTypes) {
	for (int i = 0; i < dataTypes.length; i++) {
	    if (dataTypeToBeFound.equals (dataTypes[i].getName()))
		return dataTypes[i];
	}
	log.error ("Strange, data type '" + dataTypeToBeFound +
		   "' was not found in " + dataTypes.length + " data types.");
	return null;
    }

    /**************************************************************************
     *
     * A worker that calls the service...
     *
     **************************************************************************/
    class MySwingWorker
	extends SwingWorker {

	MobyException exception = null;
	DataContainer data = new DataContainer();
	boolean wasCancelled = false;
	MobyService service;

	public MySwingWorker (MobyService service) {
	    super();
	    this.service = service;
	    data.setMetadata (propertyChannel);
	}

	public void cancel() {
	    wasCancelled = true;
	    propertyChannel.fire (DP_STATUS_MSG, "Service invocation cancelled.");
	}

	public Object construct() {
	    try {
		runButton.setEnabled (false);
		propertyChannel.fire (DP_STATUS_MSG,
				      "Calling service " + service.getName() + "...");

		// create a data container with input data...
		if (propertyChannel.getBoolean (DP_SC_IN_FILE, false)) {

		    // ...either from a file
		    String inputFile = propertyChannel.getString (DP_SC_INPUT_FILE);
		    if (UUtils.isEmpty (inputFile))
			throw new MobyException ("No input XML file given.");
		    data.setDataFromFile (new File (inputFile));
		    
		} else {
		    data.setData (inputDataTables.toXML());
		}

		// optionally, show XML data: we want to show this XML
		// input only (a) if it was explicitly asked for (property
		// DP_INP_SHOWXML is true), or (b) if "no real service
		// call" was selected (property DP_CALL_SERVICE has value
		// DP_CS_NONE)
		if ( DP_CS_NONE.equals (propertyChannel.getString (DP_CALL_SERVICE)) ||
		     (propertyChannel.getBoolean (DP_INP_SHOWXML, false)) ) {
		    input.setText ((String)data.getData());
		} 
		
		// If we are only pinging the service, set the data object to an empty message
		if (propertyChannel.getString(DP_INP_PING).toLowerCase().equals("true")) {
			String emptyMsg = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
			"<moby:MOBY xmlns:moby=\"http://www.biomoby.org/moby\">\n" +
			"  <moby:mobyContent />\n" +
			"</moby:MOBY>";
			data.setData(emptyMsg);
			input.setText(emptyMsg);
		}

		// finally, call the service
		stopButton.setEnabled (true);
		callerModel.runIt (data);

	    } catch (MobyException e) {
		exception = e;
		
	    } catch (Error e) {
		exception = new MobyException (e.toString());
	    }
	    return null;  // not used here
	}
		    
	// runs on the event-dispatching thread
	public void finished() {
	    if (wasCancelled) {
		// service was interrupted by the Stop button - do
		// nothing (the whole GUI should be already in a good
		// state - the button 'Stop' took care about it)
		return;
	    }

	    if (exception == null) {
	    	
			// handle results here (using renderers...)
			if (propertyChannel.getString(DP_INP_PING).toLowerCase().equals("true")) {
				propertyChannel.fire (DP_STATUS_MSG, service.getName()+" isAlive.");
			} else {
				propertyChannel.fire (DP_STATUS_MSG, "Service invocation finished.");
			}
			
			if (! DP_CS_NONE.equals (propertyChannel.getString (DP_CALL_SERVICE))) {
			    results.updateComponent (data);			
			}

	    } else {
			if (propertyChannel.getString(DP_INP_PING).toLowerCase().equals("true")) {
		    	propertyChannel.fire (DP_STATUS_MSG, service.getName()+" is dead.");
				results.removeResults();
			} else {
		    	propertyChannel.fire (DP_STATUS_MSG, "Service invocation failed.");
				error (CALLER_ERROR, exception);
			}
			exception.printStackTrace();
	    }
	    serviceFinished();
	}
    }

    /**************************************************************************
     * Called when a call to a service finished.
     **************************************************************************/
    protected void serviceFinished() {
	runButton.setEnabled (true);
	stopButton.setEnabled (false);
    }

    /**************************************************************************
     * Called when a service is selected in a service tree (or in a
     * 'recently used services' combo box), and also at the beginning
     * when a service name is retrieved from user preferences.
     *
     * Get selected service from a registry model and generate a new
     * 'input data panel' to reflect input data of the selected
     * service.
     **************************************************************************/
    protected void selectService (final String serviceName) {
	final Object source = this;
	final SwingWorker worker = new SwingWorker() {
		MobyService service;
		MobyDataType[] dataTypes;
		public Object construct() {
		    try {
			service = registryModel.getService (serviceName);
			dataTypes = registryModel.getDataTypes (source);
		    } catch (MobyException e) {
			error (ServicesTree.SERVICES_ACCESS_ERROR, e);
		    }
		    return service;  // not used here
		}

		// runs on the event-dispatching thread.
		public void finished() {
		    if (service == null) {
			deselectService();
		    } else {
			updateInputDataPanel (service, dataTypes);
			selService.setText (service.getName());
// 			selectionAllowed = false;
// 			recentServices.setText (service.getName());
// 			selectionAllowed = true;
			propertyChannel.put (DP_SC_SERVICE, service);
			runButton.setEnabled (true);

		    }
		}
	    };
	worker.start(); 
    }

    /**************************************************************************
     * Called when no service is selected in order to display this
     * fact on various places in the panel.
     **************************************************************************/
    protected void deselectService() {
	selService.setText ("");
	updateInputDataPanel (INIT_SELECT);
	propertyChannel.remove (DP_SC_SERVICE);
	runButton.setEnabled (false);
    }

    /**************************************************************************
     *
     **************************************************************************/
    public String getName() {
	return "Simple Client";
    }

    /**************************************************************************
     *
     **************************************************************************/
    public String getDescription() {
	return
	    "A panel allowing to create input data and to call an arbitrary Biomoby service. " +
	    "Its purpose is mostly for the service developers to test their new services.";
    }

    /**************************************************************************
     *
     * Customized tree of services...
     *
     **************************************************************************/
    protected class CustomServicesTree
	extends ServicesTree {

	/*********************************************************************
	 * Construtor
	 ********************************************************************/
	public CustomServicesTree (RegistryModel model,
				   CommonConsole console) {
	    super (model, console);
	}

	/*********************************************************************
	 *
	 ********************************************************************/
	protected void createPopups (String title) {
 	    super.createPopups (title);
 	    removeFromPopups (AC_RELOAD);
 	    removeSeparatorAfter (AC_COLLAPSE);
	}

	/*********************************************************************
	 * Service selected in a service tree...
	 ********************************************************************/
	protected void selected (DefaultMutableTreeNode node) {
	    if (node == null) {
		// nothing selected
		deselectService();
		return;
	    }
	    updateInputDataPanel ("Loading...");
	    selService.setText ("");
	    final CommonNode nodeObject = (CommonNode)node.getUserObject();
	    String currentServiceName = nodeObject.getValue();
	    selectService (currentServiceName);
	    setPrefValue (DP_SC_SERVICE_NAME, currentServiceName);
	}
    }

}
