// MobyUnitTestingPanel.java
//
// Created: October 2008
//
// This file is a component of the BioMoby project.
// Copyright Edward Kawas (edward.kawas@gmail.com).
//

package org.biomoby.service.dashboard;

import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagLayout;
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.awt.event.KeyListener;
import java.io.File;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.tree.DefaultMutableTreeNode;

import org.biomoby.client.rdf.builder.ServiceInstanceRDF;
import org.biomoby.registry.meta.Registry;
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.biomoby.shared.MobyUnitTest;
import org.biomoby.shared.Utils;
import org.biomoby.shared.extended.ServiceInstanceParser;
import org.tulsoft.shared.PrefsUtils;
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;

import com.hp.hpl.jena.rdf.model.Model;

/**
 * This Panel uses the SimpleClient as a template panel and adds the
 * functionality required to provide/perform unit tests on selected Moby
 * services.
 * <p>
 * This panel has 2 parts to it.
 * <p>
 * The first part allows you (the user) to provide sample input and any or all
 * of the following: an xpath xpression that when applied to the service outputs
 * returns nodes, a regular expression that when applied to the service returns
 * at least one match, or expected service output that when compared to the
 * actual output contains exact DOM trees.
 * <p>
 * The second part allows you to test a service with either user entered test
 * data or unit test data (if available) obtained from the service RDF provided
 * by the service provider fetched from the signature URL.
 * 
 * @author Eddie Kawas
 * @author Martin Senger
 * 
 * @version $Id: MobyUnitTestingPanel.java,v 1.1 2009/01/30 14:51:11 kawas Exp $
 */

public class MobyUnitTestingPanel extends AbstractPanel {

    private static final long serialVersionUID = -370045034538998854L;

    private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
	    .getLog(MobyUnitTestingPanel.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";

    // associated models working behind the scene
    RegistryModel registryModel;

    UnitTestingServiceCallermodel callerModel;

    // components that are used from more methods
    JLabel selService;

    JFileChooserWithHistory inFile, rdfFile;

    JButton runButton, stopButton, testXpathButton, saveRDFButton, downloadTestButton, testRegexButton, testOutputButton, copyServiceResultsButton;

    JCheckBox iFromFile;

    JFileChooser saveChooser;
    
    JTextFieldWithHistory endpoint, regularExpression, xpathExpression;

    JPanel dataTablesPanel;

    ServiceInputPanel inputDataTables;

    MySwingWorker runWorker;

    UnitTestingResultPanel results;

    JComponent resultsComp;

    JSplitPane resSplit;

    JTextArea regularExpressionMatchingText;

    // shared icons
    protected static Icon runIcon, runIconDis;

    protected static Icon saveIcon, saveIconDis;
    
    protected static Icon loadIcon, loadIconDis;
    
    protected static Icon testOutputIcon, testOutputIconDis;
    
    protected static Icon copyIcon,copyIconDis;
    
    protected static Icon stopIcon, stopIconDis;

    protected static Icon addDataIcon, addDataIconDis;
    
    private Color default_button_color;
    
    private String serviceOutput = "";
    
    private static EditableConsole serviceOutputConsole;
    
    // Dashboard properties for unit testing
    static final String UT_REGULAR_EXPRESSION = "ut-regular-expression";
    static final String UT_XPATH_EXPRESSION = "ut-xpath-expression";
    static final String UT_SC_SERVICE = "ut-sc-service"; // replaces DP_SC_SERVICE
    static final String UT_SC_SERVICE_NAME = "ut-sc-service-name"; // replaces DP_SC_SERVICE_NAME
    static final String UT_SC_INPUT_FILE = "ut-service-input-file";
    static final String UT_SC_IN_FILE = "ut-service-in-file";
    static final String UT_INPUT_FILE = "ut-input-file";
    static final String UT_SC_RDF_OUTPUT_FILE = "ut-service-output-file";
    static final String UT_RDF_OUTPUT_FILE = "ut-rdf-output-file-name";
    
    /***************************************************************************
     * Default constructor.
     **************************************************************************/
    public MobyUnitTestingPanel() {
	super();
	panelIconFileName = "images/unitTest.gif";
	saveChooser = new JFileChooser();
	saveChooser.setDialogTitle ("Save Biomoby Service Input");
    }

    /***************************************************************************
     * 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");
	if (saveIcon == null) saveIcon = loadIcon ("images/smallSave.gif");
	if (saveIconDis == null) saveIconDis = loadIcon ("images/smallSave_dis.gif");
	if (loadIcon == null) loadIcon = loadIcon ("images/smallDeploy.gif");
	if (loadIconDis == null) loadIconDis = loadIcon ("images/smallDeploy_dis.gif");
	if (copyIcon == null) copyIcon = loadIcon ("images/smallClone.gif");
	if (copyIconDis == null) copyIconDis = loadIcon ("images/smallClone_dis.gif");
	if (testOutputIcon == null) testOutputIcon = loadIcon ("images/smallAgent.gif");
	if (testOutputIconDis == null) testOutputIconDis = loadIcon ("images/smallAgent_dis.gif");
    }

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

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

	// result panel
	results = new UnitTestingResultPanel();
	resultsComp = results.getComponent(aPropertyChannel);

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

	// panel with input data
	JPanel inputData = getInputPanel();
	updateInputDataPanel(INIT_SELECT);
	
	// tabbed panel with XPATH and REGEX creators/testors
	JPanel expressionPanel = new JPanel(new GridBagLayout());
	expressionPanel.setBorder(createFatBorder("Moby Unit Test Cases", GraphColours
		.getColour("cadetblue", Color.blue)));
	JTabbedPane tabbedPane = new JTabbedPane();
	tabbedPane.addTab ("XPath Tester", null, getXpathSubpanel());
	tabbedPane.addTab ("Regex Tester", null, getRegexSubpanel());
	tabbedPane.addTab ("Service Output XML Tester", null, getValidServiceOutputSubpanel());
	SwingUtils.addComponent(expressionPanel, tabbedPane, 0, 0, 2, 1, BOTH,NWEST, 1.0, 1.0);	
	
	// service ontology tree
	JPanel sBoard = new JPanel(new GridBagLayout());
	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, 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), vSplit(inputData, expressionPanel, 0.9), 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(UT_SC_SERVICE_NAME, "");
	if (!"".equals(lastServiceName))
	    selectService(lastServiceName);

	return pComponent;
    }

    /*
     * create the valid service output subpanel that goes in the Unit Test main
     * panel
     */
    private Component getValidServiceOutputSubpanel() {
	JPanel panel = createTitledPanel("Expected service output XML");
	serviceOutputConsole = new EditableConsole ();
	SwingUtils.addComponent(panel, serviceOutputConsole, 0, 0, 1, 1, BOTH, NWEST, 1.0, 1.0);
	return panel;
    }
    
    /*
     * A console for our expected service output
     */
    class EditableConsole
	extends CommonConsole {	
	private static final long serialVersionUID = 1060691168904057586L;
	public EditableConsole() {
	super();
	
	textArea.setEditable (true);
	textArea.setBackground
	    (GraphColours.getColour (DashboardConfig.getString (P_S_EDIT_BGCOLOR, null),
				     textArea.getBackground()));
	copyServiceResultsButton = AbstractPanel.createButton
	    ("Copy",
		     "Copy over service invocation results",
		     -1,
		     new ActionListener() {
			 public void actionPerformed (ActionEvent e) {
			     textArea.setText(serviceOutput);
			 }
		     });
	copyServiceResultsButton.setIcon(copyIcon);
	copyServiceResultsButton.setDisabledIcon(copyIconDis);
	copyServiceResultsButton.setEnabled(false);
	SwingUtils.compact (copyServiceResultsButton);
	SwingUtils.addComponent (this, copyServiceResultsButton, 1, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NWEST, 0.0, 0.0);
	
	testOutputButton = AbstractPanel.createButton("Test",
		    "Test your services real ouput against this XML", -1,
		    new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    MobyUnitTest unitTest = new MobyUnitTest();
			    unitTest.setValidOutputXML(textArea.getText());
			    boolean success = false;
			    try {
				success = unitTest.compareOutputXML(serviceOutput);
			    } catch (MobyException me) {
				error("Differences found!\n", me);
				return;
			    }
			    if (success) {
				SwingUtils.msg(null, "Success", "Service output XML is as expected!", confirmIcon);
			    } else {
				error("Differences found!\n", new MobyException(unitTest
					.getXMLDifferences(serviceOutput)));
			    }
			}
		    });
	testOutputButton.setIcon(testOutputIcon);
	testOutputButton.setDisabledIcon(testOutputIconDis);
	testOutputButton.setEnabled(false);
	SwingUtils.compact (testOutputButton);
	SwingUtils.addComponent (this, testOutputButton, 2, 1, 1, 1, AbstractPanel.NONE, AbstractPanel.NWEST, 0.0, 0.0);
	
	}
	/*
	 * (non-Javadoc)
	 * @see org.biomoby.service.dashboard.CommonConsole#hasAppendModeSwitcher()
	 */
	public boolean hasAppendModeSwitcher() {return false; }
	/*
	 * (non-Javadoc)
	 * @see org.biomoby.service.dashboard.CommonConsole#hasVerboseModeSwitcher()
	 */
	public boolean hasVerboseModeSwitcher() {return false; }
	/*
	 * (non-Javadoc)
	 * @see org.biomoby.service.dashboard.CommonConsole#hasSaveButton()
	 */
	public boolean hasSaveButton() {return false; }
	/*
	 * (non-Javadoc)
	 * @see org.biomoby.service.dashboard.CommonConsole#hasCleanButton()
	 */
	public boolean hasCleanButton() {return false; }
    }
    /*
     * A sub panel for creating and testing regular expressions on real output
     * for a service
     */
    private Component getRegexSubpanel() {
	JPanel panel = createTitledPanel("Regular Expression for Unit Testing");
	
	regularExpression = createText(null, UT_REGULAR_EXPRESSION, UT_REGULAR_EXPRESSION);
	regularExpressionMatchingText = new JTextArea("",5,5);
	JScrollPane scroll = new JScrollPane(regularExpressionMatchingText);
	scroll.setMinimumSize(regularExpressionMatchingText.getPreferredScrollableViewportSize());
	
	// Button for testing regular expressions
	testRegexButton = new JButton("Test Regular Expression");
	testRegexButton.addActionListener(new ActionListener(){

	    public void actionPerformed(ActionEvent e) {
		MobyUnitTest unitTest = new MobyUnitTest();
		unitTest.setValidREGEX(regularExpression.getText());
		if (unitTest.compareXmlWithREGEX(serviceOutput, true)) {
		    // print the matches
		    Pattern p = Pattern.compile(unitTest.getValidREGEX(), Pattern.MULTILINE);
		    Matcher m = p.matcher(serviceOutput);
		    regularExpressionMatchingText.setText("");
		    StringBuilder sb = new StringBuilder();
	            while (m.find()) {
	        	int start=0, stop=0;
	        	if (m.start()==0)
	        	    start = 0;
	        	else if (m.start() - 10 >=0 )
	        	    start = m.start() - 10;
	        	if (m.end() + 10 >= serviceOutput.length()-1)
	        	    stop = serviceOutput.length()-1;
	        	else
	        	    stop = m.end() + 10;
	        	sb.append("Match: " + m.group() + " in text:\n\t" + serviceOutput.substring(start, stop ).replaceAll("\n", "")+ " @ (" + m.start() + "," +  m.end() + ")\n");
	            }
	            regularExpressionMatchingText.setText(sb.toString());
		} else {
		    regularExpressionMatchingText.setText("No matches found");
		}
	    }
	    
	});
	// disable the regex button until service is run
	testRegexButton.setEnabled(false);
	JPanel statusPanel = createButtonPanel(new JButton[]{testRegexButton});
	SwingUtils.addComponent(panel, new JLabel("RegEx: "),          0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(panel, regularExpression,              0, 1, 1, 1, HORI, NWEST, 1.0, 0.0);
	SwingUtils.addComponent(panel, new JLabel("Matching Text: "),  0, 2, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(panel, scroll,                         0, 3, 1, 2, HORI, NWEST, 1.0, 0.0);
	SwingUtils.addComponent(panel, statusPanel,                    0, 5, 1, 1, HORI, CENTER, 1.0, 0.0);
	return panel;
    }

    /*
     * A sub panel for testing xpath expressions on real data
     */
    private Component getXpathSubpanel() {
	JPanel panel = createTitledPanel("XPath Expression for Unit Testing");
	xpathExpression = createText(null, UT_XPATH_EXPRESSION, UT_XPATH_EXPRESSION);
	xpathExpression.addKeyListener(new KeyListener(){

	    public void keyPressed(KeyEvent e) {
		testXpathButton.setBackground(default_button_color);
	    }

	    public void keyReleased(KeyEvent e) {
		testXpathButton.setBackground(default_button_color);
	    }

	    public void keyTyped(KeyEvent e) {
		testXpathButton.setBackground(default_button_color);
	    }

	    });
	// Button for testing regular expressions
	testXpathButton = new JButton("Test XPATH Expression");
	default_button_color = testXpathButton.getBackground();
	testXpathButton.addActionListener(new ActionListener(){
	    public void actionPerformed(ActionEvent e) {
		MobyUnitTest unitTest = new MobyUnitTest();
		unitTest.setValidXPath(xpathExpression.getText());
		boolean success = false;
		try {
		    success = unitTest.compareXmlWithXpath(serviceOutput);
		}catch (MobyException me) {
		    error("XPath Error", me);
		    return;
		}
		// here we change the color of the button depending on whether we have success
		if (success) {
		    ((JButton) e.getSource()).setBackground(Color.GREEN);
		} else {
		    ((JButton) e.getSource()).setBackground(Color.RED);
		}
	    }
	});
	// disable the xpath button until service is run
	testXpathButton.setEnabled(false);
	JPanel statusPanel = createButtonPanel(new JButton[]{testXpathButton});
	SwingUtils.addComponent(panel, new JLabel("XPath: "), 0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(panel, xpathExpression,       0, 1, 1, 1, HORI, NWEST, 1.0, 0.0);
	SwingUtils.addComponent(panel, statusPanel,           0, 2, 1, 1, HORI, CENTER, 1.0, 0.0);
	return panel;
    }

    /***************************************************************************
     * 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);

	ButtonGroup group = new ButtonGroup();
	JRadioButton htRegistry, htNewURL;
	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));
	// run the service
	// determine button text from preferences
	boolean usingAsBytes = getPrefValue(DP_INP_ASBYTES, false);
	String runLabel = " Call Service ";

	runButton = createButton(runLabel, "Invoke selected service",
		KeyEvent.VK_C, new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			MobyService srv = (MobyService) propertyChannel
				.get(UT_SC_SERVICE);
			if (srv != null) {
			    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 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");

	// the save rdf unit test button
	JPanel saveUnitTest = createTitledPanel(" Moby Unit Tests ");
	saveRDFButton = createButton(" Save Your Unit Test ",
		"Use this button to create service RDF that contains this unit test", KeyEvent.VK_U,
		new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			saveRDFButton.setEnabled(false);
			saveUnitTest();
		    }
		});
	saveRDFButton.setEnabled(false);
	saveRDFButton.setIcon(saveIcon);
	saveRDFButton.setDisabledIcon(saveIconDis);
	// download unit test button
	downloadTestButton = createButton(" Download Unit Test ",
		"Use this button to load Unit test information from the service provider", KeyEvent.VK_D,
		new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			// here we go to a url and download the unit test
			String serviceOfInterest = getPrefValue(
				UT_SC_SERVICE_NAME, null);
			if (serviceOfInterest == null) {
			    // should never be here
			    error("You shouldn't be able to load unit tests without first selecting a service!");
			    return;
			}
			MobyService selectedService = 
			    (MobyService) propertyChannel.get(UT_SC_SERVICE);
			if (selectedService == null) {
			    // get service info from registry
			    selectService(serviceOfInterest);
			}
			// get unit test information from the selected service
			loadUnitTest(serviceOfInterest, selectedService);
			    
		    }
		});
	downloadTestButton.setDisabledIcon(loadIconDis);
	downloadTestButton.setIcon(loadIcon);
	downloadTestButton.setEnabled(false);
	
	// set up the button pannel
	JPanel saveButtonPanel = createButtonPanel(new JButton[] { downloadTestButton, saveRDFButton });
	SwingUtils.addComponent(saveUnitTest, saveButtonPanel, 0, 0, 1, 1, NONE, CENTER, 0.0, 0.0);
	// set up control panel
	SwingUtils.addComponent(howTo, htNewURL,               0, 0, 1, 1, NONE, NWEST, 0.0,0.0);
	SwingUtils.addComponent(howTo, endpoint,               1, 0, 1, 1, HORI, NWEST, 1.0,0.0);
	SwingUtils.addComponent(howTo, htRegistry,             0, 1, 2, 1, NONE, NWEST,0.0, 0.0);
	SwingUtils.addComponent(howTo, asBytes,                0, 2, 2, 1, NONE, NWEST, 0.0,0.0);
	SwingUtils.addComponent(howTo, buttonPanel,            0, 3, 2, 1, NONE, CENTER, 0.0,0.0);
	// set up the main control panel
	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, saveUnitTest,               0, 2, 1, 2, HORI, CENTER, 0.0, 0.0, BREATH_TOP);
	SwingUtils.addComponent(p, Box.createVerticalGlue(),   0, 4, 1, 1, VERT, NWEST, 0.0, 1.0);
	
	return p;
    }

    protected void removeUnitTestData() {
	// clear the unit test fields
	if (xpathExpression != null) {
	    xpathExpression.setText("");
	    // reset the color of the button back to default
	    testXpathButton.setBackground(default_button_color);
	}
	// set the regex
	if (regularExpression != null)
	    regularExpression.setText("");
	// set the service output
	if (serviceOutputConsole != null)
	    serviceOutputConsole.getArea().setText("");
	
    }

    /***************************************************************************
     * Select/unselect using 'send as bytes'
     **************************************************************************/
    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_REGISTRY);
	// since unit testing only supports either registry or url, we need to ensure that one of them was selected
	if (!initHowTo.trim().equals(DP_CS_REGISTRY) || !initHowTo.trim().equals(DP_CS_URL)) {
	    initHowTo = DP_CS_REGISTRY;
	}
	if (howTo.equals(initHowTo)) {
	    radio.setSelected(true);
	    radio.setEnabled(true);
	    propertyChannel.put(DP_CALL_SERVICE, howTo);
	}
	endpoint.setEnabled(initHowTo.equals(DP_CS_NEWURL));
	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));
	}
    };

    /***************************************************************************
     * 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",
		UT_SC_IN_FILE, false);
	inFile = createFileSelector("Select input XML for calling a service",
		"Select", null, UT_SC_INPUT_FILE, UT_SC_INPUT_FILE);
	
	inFile.setEnabled(iFromFile.isSelected());

	JButton saveButton = AbstractPanel.createButton
	    ("Save Input to File",
	     "Save Service Input to File",
	     -1,
	     new ActionListener() {
		 public void actionPerformed (ActionEvent e) {
		     onSave();
		 }
	     });
	saveButton.setIcon (saveIcon);
	SwingUtils.compact (saveButton);
	
	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, createButtonPanel(new JButton[]{saveButton}), 0, 2, 1, 1, NONE, NWEST, 0.0,0.0);
	SwingUtils.addComponent(inputData, upper, 0, 0, 1, 1, BOTH, NWEST, 1.0,1.0);
	
	return inputData;
    }

    /*********************************************************************
     * Save contents...
     ********************************************************************/
    protected void onSave() {

	final String fileName = showSaveDialog();
	if (UUtils.isEmpty (fileName ))
	    return;

	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			Utils.createFile (new File (fileName), inputDataTables.toXML());
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			AbstractPanel.error ("Sorry, an error happened when saving...\n\n", exception);
		}
	    };
	worker.start(); 
    }
    
    /*********************************************************************
     * Show...
     ********************************************************************/
    protected String showSaveDialog() {
	// set an init value (if any)
	Preferences node = PrefsUtils.getNode (this.getClass());
	String initValue = node.get (UT_INPUT_FILE,System.getProperty ("user.dir"));
	if (UUtils.notEmpty (initValue)) {
	    File file = new File (initValue);
	    saveChooser.setSelectedFile (file);
	}
	// show chooser
	if (saveChooser.showSaveDialog (this) != JFileChooser.APPROVE_OPTION)
	    return null;
	// store the chosen value
	String fileName = saveChooser.getSelectedFile().getAbsolutePath();
	node.put (UT_INPUT_FILE,
		  fileName);
	return fileName;
    }
    
    /***************************************************************************
     * 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(UT_SC_IN_FILE, false)) {
		    // ...either from a file
		    String inputFile = propertyChannel
			    .getString(UT_SC_INPUT_FILE);
		    if (UUtils.isEmpty(inputFile))
			throw new MobyException("No input XML file given.");
		    data.setDataFromFile(new File(inputFile));
		    // try filling in the Input widget with the actual input
		    try {
			inputDataTables.setValues(data.getData().toString());
		    } catch (MobyException e) {
			// ignore this warning because this was purely for show
		    }
		} else {
		    data.setData(inputDataTables.toXML());
		}
		

		// 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) {
		return;
	    }

	    if (exception == null) {

		propertyChannel.fire(DP_STATUS_MSG,
			"Service invocation finished.");

		if (!DP_CS_NONE.equals(propertyChannel
			.getString(DP_CALL_SERVICE))) {
		    results.updateComponent(data);
		    serviceOutput = data.getData().toString();
		}

	    } 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);
	// enable the unit test buttons once we have run the service so that we
	// can test the output
	testRegexButton.setEnabled(true);
	testXpathButton.setEnabled(true);
	testOutputButton.setEnabled(true);
	copyServiceResultsButton.setEnabled(true);
    }

    /***************************************************************************
     * 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());
		    propertyChannel.put(UT_SC_SERVICE, service);
		    runButton.setEnabled(true);
		    downloadTestButton.setEnabled(true);
		    saveRDFButton.setEnabled(true);

		}
	    }
	};
	worker.start();
    }
    
    /***************************************************************************
     * Called when a user clicks on the load unit test button
     * 
     * Obtains the selected services' signature url and parses it for the unit
     * test information and fills out our ServiceInputPanel. 
     **************************************************************************/
    protected void loadUnitTest(final String serviceName, final MobyService service) {
	final SwingWorker worker = new SwingWorker() {
	    boolean found = false;
	    public Object construct() {
		downloadTestButton.setEnabled(false);
		saveRDFButton.setEnabled(false);
		 // String url = "file:////home/ubuntu/moby-live/getDragonAlleleLocus.rdf";
		    String url = service.getSignatureURL();
		    if (url == null || url.trim().equals("")) {
			// nothing to tell you ... so remove test data
			removeUnitTestData();
		    }
		    try {
		    // instantiate a parser with our sigUrl
		    ServiceInstanceParser sip = new ServiceInstanceParser(url);
		    MobyService[] services = sip.getMobyServicesFromRDF();
		    // look for the service that we are interested in
		    for (MobyService service : services) {
			if (service.getUniqueName().equals(serviceName)){
			    found = true;
			    // fill in the unit test information
			    if (service.getUnitTests() != null && service.getUnitTests().length > 0) {
				// take the first test
				MobyUnitTest ut = service.getUnitTests()[0];
				// set the xpath statement
				xpathExpression.setText(ut.getValidXPath());
				// reset the color of the button back to default
				testXpathButton.setBackground(default_button_color);
				// set the regex
				regularExpression.setText(ut.getValidREGEX());
				// set the service output
				serviceOutputConsole.getArea().setText(ut.getValidOutputXML());
				// load up the example input
				inputDataTables.setValues(ut.getExampleInput());
			    }
			    break;
			}
		    }
		    } catch (MobyException me) {
			downloadTestButton.setEnabled(true);
			saveRDFButton.setEnabled(true);
			error("There was an error while getting unit test information!", me);
		    }
		return service; // not used here
	    }

	    // runs on the event-dispatching thread.
	    public void finished() {
		// nothing found, clear the fields
		if (!found) {
		    removeUnitTestData();
		    return;
		}
		// a service was found so we should uncheck the
		// box saying to load inputs from a file
		if (iFromFile.isSelected()) {
		    iFromFile.setSelected(false);
		}
		downloadTestButton.setEnabled(true);
		saveRDFButton.setEnabled(true);
	    }
	};
	worker.start();
    }

    /***************************************************************************
     * Called when a user clicks on the save unit test button
     * 
     * Obtains the selected services' signature url and parses it for the unit
     * test information and fills out our ServiceInputPanel. 
     **************************************************************************/
    public void saveUnitTest() {
	final SwingWorker worker = new SwingWorker() {
	    private boolean success = false;
	    private String filename = "";
	    public Object construct() {
		// get the service from the property channel
		MobyService service = (MobyService)propertyChannel.get(UT_SC_SERVICE);
		// if null, tell error
		if (service == null) {
		    success = false;
		    error("Problem saving unit test. Did you select a service?");
		    return null;
		}
		// fill in the MobyUnitTest object
		MobyUnitTest unitTest = new MobyUnitTest();
		// set the xpath expression
		if (!xpathExpression.getText().trim().equals("")) {
		    unitTest.setValidXPath(xpathExpression.getText().trim());
		}
		// set the regex
		if (!regularExpression.getText().trim().equals("")) {
		    unitTest.setValidREGEX(regularExpression.getText().trim());
		}
		// set the outputxml
		if (!serviceOutputConsole.getArea().getText().trim().equals("")) {
		    unitTest.setValidOutputXML(serviceOutputConsole.getArea()
			    .getText().trim());
		}
		// set the input
		try {
		    DataContainer data = new DataContainer();
		    if (propertyChannel.getBoolean(UT_SC_IN_FILE, false)) {
			// ...either from a file
			String inputFile = propertyChannel
				.getString(UT_SC_INPUT_FILE);
			// handle empty XML
			if (UUtils.isEmpty(inputFile)) {
			    error("Empty File!", new MobyException(
				    "No input XML file given."));
			    success = false;
			    return null;
			}
			data.setDataFromFile(new File(inputFile));
		    } else {
			data.setData(inputDataTables.toXML());
		    }
		    unitTest.setExampleInput((String) data.getData());
		} catch (MobyException me) {
		    error("Unknown error ...", me);
		    success = false;
		    return false;
		}
		service.addUnitTest(unitTest);
		ServiceInstanceRDF siRdf;
		try {
		    siRdf = new ServiceInstanceRDF(
			    	new Registry(
			    		propertyChannel.getString(DP_REGISTRY_ENDPOINT),
			    		propertyChannel.getString(DP_REGISTRY_ENDPOINT),
			    		propertyChannel.getString(DP_REGISTRY_NAMESPACE))
		    );
		    Model m = siRdf.createRDFModel(null,
			    new MobyService[] { service }, true);
		    String rdf = siRdf.serializeModel(m);
		    // create the chooser if its null
		    if (rdfFile == null) {
			rdfFile = createFileSelector("Save your RDF to",
				"Save", service.getName(), UT_SC_RDF_OUTPUT_FILE,
				UT_SC_RDF_OUTPUT_FILE);
			rdfFile.setEnabled(true);
		    }
		    // set an init value (if any)
		    Preferences node = PrefsUtils.getNode(this.getClass());
		    String initValue = node.get(UT_RDF_OUTPUT_FILE, System
			    .getProperty("user.dir"));
		    if (UUtils.notEmpty(initValue)) {
			File file = new File(initValue);
			rdfFile.setSelectedFile(file);
		    }
		    // show chooser
		    if (rdfFile.getFileChooser().showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
			// store the chosen value
			filename = rdfFile.getFileChooser()
				.getSelectedFile().getAbsolutePath();
			node.put(UT_RDF_OUTPUT_FILE, filename);
			Utils.createFile(new File(filename), rdf);
			success = true;
		    }
		} catch (MobyException me) {
		    error("Problem saving file ...", me);
		    success = false;
		    saveRDFButton.setEnabled(true);
		    return null;
		}
		return null;
	    }
	    public void finished() {
		if (success)
		    SwingUtils
			    .msg(
				    null,
				    "Success",
				    "Your unit test has been successfully saved to\n\t'"
					    + filename
					    + "'!\nDon't forget to put that document at the location specified by your signature URL!\n",
				    confirmIcon);
		saveRDFButton.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(UT_SC_SERVICE);
	runButton.setEnabled(false);
	downloadTestButton.setEnabled(false);
	saveRDFButton.setEnabled(false);
    }

    /***************************************************************************
     * 
     **************************************************************************/
    public String getName() {
	return "Moby Unit Testing";
    }

    /***************************************************************************
     * 
     **************************************************************************/
    public String getDescription() {
	return "A panel for creating and unit testing your moby services.";
    }

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

	private static final long serialVersionUID = 5884117301201885425L;

	/***********************************************************************
	 * 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(UT_SC_SERVICE_NAME, currentServiceName);
	}
    }

}
