/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Stuart E. Middleton
//      Created date:           2004/04/30
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.infrastructure;

import javax.swing.JTabbedPane;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.JComponent;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JProgressBar;
import javax.swing.JFileChooser;

import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Rectangle;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.Dimension;

import java.awt.event.KeyEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;

import java.util.Vector;
import java.io.File;
import eu.gemss.components.proxies.GEMSSNegotiator;

/*
* Swing JPanel that supports the interface controls for the sample client
*/
public class SampleGUIPanel extends JPanel implements ActionListener {

	// interface components
	private WorkflowDiagram mdiagram;
	private JTextArea mtextAreaConfiguration;
	private JTextArea mtextAreaJobStatus;
	private JTextArea mtextAreaNegotiatorStatus;
	private JButton mbuttonGo;
	private JButton mbuttonSave;
	private JButton mbuttonLoad;
	private JButton mbuttonDownload;
	private JButton mbuttonKill;

	// other
	private boolean mbHalted;
      private boolean mbGo;
	private SampleGUI mparent;
	private GEMSSNegotiator mnegotiator;

	/*
	* Swing JPanel that supports the interface controls for the sample client. For reference the node indexes are
      * Discover (0), Inform (1), Call(2), Bid(3), Accept(4), Job prep (5), Upload (6), Start (7), Run (8), Download (9), Acknwl (10), Finish (11)
	* @param strLogoFilename name of the file used for the GEMSS logo (GIF or JPG)
	* @param parent reference to the base class for callbacks on the save and download buttons
	*/
	public SampleGUIPanel( String strLogoFilename, SampleGUI parent )
	{
		// call constructor for a Grid ayout
		super( new BorderLayout(),true );

		JPanel panel, panelButtons;
		JTabbedPane tabbedPane;
		JLabel logo;
		ImageIcon image;
		java.net.URL imgURI;

		try {
			// note parent for callbacks
			mparent = parent;
			mbHalted = false;
			mnegotiator = null;

			// make this panel opaque for a nicer refresh
			setOpaque( true );

			// initially paused
			mbGo = false;

			// add diagram pane and the progress bar
			panel = new JPanel( new GridLayout(1,1),true );
			mdiagram = new WorkflowDiagram();
			mdiagram.setBackground( getBackground() );
			panel.add( mdiagram );
			add( panel,BorderLayout.NORTH );

			// add diagram nodes
			mdiagram.addNode( "Discover",1,0 );
			mdiagram.addNode( "Inform",2,0 );
			mdiagram.addNode( "Call",2,0 );
			mdiagram.addNode( "Bid",2,0 );
			mdiagram.addNode( "Accept",2,0 );
			mdiagram.addNode( "Job prep",2,0 );
			mdiagram.addNode( "Upload",2,0 );
			mdiagram.addNode( "Start",2,0 );
			mdiagram.addNode( "Run",2,0 );
			mdiagram.addNode( "Download",2,0 );
			mdiagram.addNode( "Acknwl",2,0 );
			mdiagram.addNode( "Finish",2,0 );
			mdiagram.addFeedbackLoop(3); // feedback from propose -> call

			// add tab pane and it's three panels
			tabbedPane = new JTabbedPane();

			// one - configuration (fixed font to allow nice tabbing)
			mtextAreaConfiguration = new JTextArea("Loading configuration ...",20,20);
			mtextAreaConfiguration.setFont( new Font( "Courier",Font.PLAIN,12 ) );
			mtextAreaConfiguration.setWrapStyleWord( true );
			panel = new JPanel( new GridLayout(1,1),true );
			panel.add( mtextAreaConfiguration );
			tabbedPane.addTab( "Configuration",panel );
			tabbedPane.setMnemonicAt( 0, KeyEvent.VK_1 );

			// two - job status
			mtextAreaJobStatus = new JTextArea("",20,20);
			mtextAreaJobStatus.setFont( new Font( "Courier",Font.PLAIN,12 ) );
			mtextAreaJobStatus.setWrapStyleWord( true );
			panel = new JPanel( new GridLayout(1,1),true );
			panel.add( mtextAreaJobStatus );
			tabbedPane.addTab( "Job Status",panel );
			tabbedPane.setMnemonicAt( 0, KeyEvent.VK_2 );

			// three - proxy status
			mtextAreaNegotiatorStatus = new JTextArea("",20,20);
			mtextAreaNegotiatorStatus.setFont( new Font( "Courier",Font.PLAIN,12 ) );
			mtextAreaNegotiatorStatus.setWrapStyleWord( true );
			panel = new JPanel( new GridLayout(1,1),true );
			panel.add( mtextAreaNegotiatorStatus );
			tabbedPane.addTab( "Negotiator Status",panel );
			tabbedPane.setMnemonicAt( 0, KeyEvent.VK_3 );

			// add tab panel to main panel
			add( tabbedPane,BorderLayout.CENTER );

			// add button grid
			panel = new JPanel( new BorderLayout(),true );
			panelButtons = new JPanel( new FlowLayout( FlowLayout.RIGHT ),true );

			// make logo
			image = new ImageIcon( strLogoFilename,"GEMSS logo" );
			logo = new JLabel( image );
			panel.add( logo,BorderLayout.WEST );

			// make buttons and register actions
			mbuttonGo = new JButton( "Start" );
			mbuttonGo.setActionCommand("Go");
			mbuttonGo.addActionListener(this);
			mbuttonGo.setEnabled( true );

			mbuttonSave = new JButton( "Save & exit ..." );
			mbuttonSave.setActionCommand("Save");
			mbuttonSave.addActionListener(this);
			mbuttonSave.setEnabled( false );

			mbuttonLoad = new JButton( "Load ..." );
			mbuttonLoad.setActionCommand("Load");
			mbuttonLoad.addActionListener(this);
			mbuttonLoad.setEnabled( true );

			mbuttonDownload = new JButton( "Download files ..." );
			mbuttonDownload.setActionCommand("DownloadIntermediate");
			mbuttonDownload.addActionListener(this);
			mbuttonDownload.setEnabled( false );

			mbuttonKill = new JButton( "Kill" );
			mbuttonKill.setActionCommand("Kill");
			mbuttonKill.addActionListener(this);
			mbuttonKill.setEnabled( false );

			// add them to the panel
			panelButtons.add( mbuttonGo );
			panelButtons.add( mbuttonLoad );
			panelButtons.add( mbuttonSave );
			panelButtons.add( mbuttonDownload );
			panelButtons.add( mbuttonKill );
			panel.add( panelButtons, BorderLayout.EAST );
			add( panel,BorderLayout.SOUTH );
		}
		catch( Exception ex ) {
			System.out.println("Failed to create interface panel : "+ex.getMessage());
			ex.printStackTrace( System.out );
		}
	} // end constructor


	/*
	* Set the text of the configuration window
	* @param strText String to display, which can include newline characters
	*/
	public void setConfigurationText( String strText )
	{
		try {
			// too early?
			if( mtextAreaConfiguration == null ) return;

			mtextAreaConfiguration.setText( strText );
		}
		catch( Exception ex ) {
			System.out.println("Cannot set configuration text : "+ex.getMessage());
		}
	} // end setConfigurationText

	/*
	* Set the text of the job status window
	* @param strText String to display, which can include newline characters
	*/
	public void setJobStatusText( String strText )
	{
		try {
			// too early?
			if( mtextAreaJobStatus == null ) return;

			mtextAreaJobStatus.setText( strText );
		}
		catch( Exception ex ) {
			System.out.println("Cannot set job status text : "+ex.getMessage());
		}
	} // end setJobStatusText

	/*
	* Set the text of the negotiator status window
	* @param strText String to display, which can include newline characters
	*/
	public void setNegotiatorStatusText( String strText )
	{
		try {
			// too early?
			if( mtextAreaNegotiatorStatus == null ) return;

			mtextAreaNegotiatorStatus.setText( strText );
		}
		catch( Exception ex ) {
			System.out.println("Cannot set negotiator status text : "+ex.getMessage());
		}
	} // end setNegotiatorStatusText

	/*
	* Set the activity value of a workflow node. Nodes are in order of
      * Discover (0), Inform (1), Call(2), Bid(3), Accept(4), Job prep (5), Upload (6), Start (7), Run (8), Download (9), Acknwl (10), Finish (11)
	* @param nIndex index value of the node, starting from 0 to 6
	* @param nValue value of the node, 1 = processing, 2 = not started, others = finished
      * @param nOrdinal value to be displayed within node (0 = no value if displayed)
	*/
	public void setWorkflowNodeValue( int nIndex, int nValue, int nOrdinal )
	{
		try {
			mdiagram.changeNodeValue( nIndex,nValue,nOrdinal );
		}
		catch( Exception ex ) {
			System.out.println("Cannot set workflow node value : "+ex.getMessage());
		}
	} // end setWorkflowNodeValue

	/*
	* Return if the go button has been hit
	* @return true if the go button has not been clicked
	*/
	public boolean isPaused()
	{
		// return the flag state
		return !mbGo;
	}

	/*
	* Set negotiator handle so it can be used for download and kill operations
      * @param negotiator neg object
	*/
	public void setNegotiator( GEMSSNegotiator negotiator )
	{
		mnegotiator = negotiator;
		mbuttonKill.setEnabled( true ); // allow kill
		mbuttonDownload.setEnabled( true ); // allow download too
	}

	/*
	* Enable or disable load
      * @param bOk true if load is to be enabled
	*/
	public void setEnableLoad( boolean bOk )
	{
		mbuttonLoad.setEnabled( bOk );
	}

	/*
	* Tell the interface to disable the kill button
	*/
	public void jobFinished()
	{
		mbuttonKill.setEnabled( false ); // allow kill
	}

	/*
	* Halt the interface permanently (e.g. on finding an error)
	* @param bFailed true if the workflow failed
	*/
	public void haltInterface( boolean bFailed )
	{
		mbHalted = bFailed;
		mbuttonGo.setEnabled( false );
		mbuttonSave.setEnabled( false );
		mbuttonKill.setEnabled( false );
		// disable download if acknowledge has been sent, since there will be no files server side
		if( bFailed == false ) {
			mbuttonDownload.setEnabled( false );
		}
		// still allow the download of files (if enableFileDownload has already been called)
		repaint();
	}

	/*
	* Start the download files dialogue box. The dialogue box will not block, even though the
	* download call is blocking, thus preventing downloads from halting interface updates
	* etc.
	* @param negotiator negotiator object
	*/
	public void downloadFiles( GEMSSNegotiator negotiator )
	{
		FileDownloadDialogue dialogue;

		try {
			FileDownloadDialogue.setDefaultLookAndFeelDecorated( true );
			dialogue = new FileDownloadDialogue( negotiator );
			dialogue.setLocationRelativeTo( this ); // start just off top left
			dialogue.show(); // modeless
			return;
		}
		catch( Exception ex ) {
			System.out.println("Failed to make file download dialogue :"+ex.getMessage());
			ex.printStackTrace( System.out );
		}
	}

	/*
	* Action listener for the buttons. Actions Go, Pause, Save and DownloadIntermediate
	* are handled presently.
	* @param e action event
	*/
	public void actionPerformed(ActionEvent e)
	{
		FileDownloadDialogue dialogue;
		KillThread thread;

		try {
			if( "Go".equals(e.getActionCommand()) ) {
				// go
				mbuttonGo.setEnabled( false );
				mbuttonSave.setEnabled( true );
                        mbGo = true;
			}
			else if( "Save".equals(e.getActionCommand()) ) {
				// Save
				mparent.SaveCallbackHandler();
			}
			else if( "Load".equals(e.getActionCommand()) ) {
				// Load (null will make method ask for filename of session)
				if( mparent.LoadSession( null ) ) {
                              mbuttonGo.setEnabled( false );
                              mbuttonSave.setEnabled( true );
                              mbGo = true;
				}
			}
			else if( "DownloadIntermediate".equals(e.getActionCommand()) ) {
				// Download intermediate files
				FileDownloadDialogue.setDefaultLookAndFeelDecorated( true );
				dialogue = new FileDownloadDialogue( mnegotiator );
				dialogue.setLocationRelativeTo( this ); // start just off top left
				dialogue.show(); // modeless
			}
			else if( "Kill".equals(e.getActionCommand()) ) {
				// disable kill button
				mbuttonKill.setEnabled( false );

				// start kill thread
				thread = new KillThread( mnegotiator );
				thread.start();
			}
		}
		catch( Exception ex ) {
			System.out.println("Action handler error : "+ex.getMessage());
		}
	}

	/*
	* Custom Canvas class to draw the workflow diagram
	*/
	class WorkflowDiagram extends JPanel
	{
		private Vector mnodeNames;
		private Vector mnodeValues;
		private Vector mnodeOrdinals;
		private Vector mfeedbackLoops;
		private static final int mstaticNodeWidth = 20;
		private static final int mstaticXGap = 40;
		private static final int mstaticXOffset = 80;
		private static final int mstaticYOffset = 20;
		private static final int mstaticNodeHeight = 20;

		/*
		* Default constructor
		*/
		public WorkflowDiagram()
		{
			super();

			mnodeNames = new Vector();
			mnodeValues = new Vector();
			mnodeOrdinals = new Vector();
			mfeedbackLoops = new Vector();
			setOpaque(false);
		}

		/*
		* add node to the diagram.
		* @param strName name of the node
		* @param nValue Value of node, 1 = green, 2 = red, others = gray
            * @param nOrdinal value to be displayed within node (0 = no value if displayed)
		*/
		public void addNode( String strName, int nValue, int nOrdinal )
		{
			mnodeNames.addElement( strName );
			mnodeValues.addElement( new Integer(nValue) );
			mnodeOrdinals.addElement( new Integer(nOrdinal) );
			// force a repaint to show the change
			repaint();
		}

		/*
		* add feedbacl loop to the diagram.
		* @param nIndex index of the feedback loop (will connect to previous node)
		*/
		public void addFeedbackLoop( int nIndex )
		{
			mfeedbackLoops.addElement( new Integer(nIndex) );
			// force a repaint to show the change
			repaint();
		}

		/*
		* changes a nodes value
		* @param nIndex index value of node
		* @param nValue Value of node, 1 = green, 2 = red, others = gray
            * @param nOrdinal value to be displayed within node (0 = no value if displayed)
		*/
		public void changeNodeValue( int nIndex, int nValue, int nOrdinal )
		{
			mnodeValues.setElementAt( new Integer(nValue),nIndex );
			mnodeOrdinals.setElementAt( new Integer(nOrdinal),nIndex );
			// force a repaint to show the change
			repaint();
		}

		/*
		* return the preferred size
		* @return preferred size
		*/
		public Dimension getPreferredSize()
		{
			// return estimate of the ideal size
			return new Dimension( mstaticXOffset + (mnodeNames.size()+1) * (mstaticXGap + mstaticNodeWidth),
						    mstaticYOffset + mstaticNodeHeight + 25 );
		}

		/*
		* paint the workflow diagram
		* @param g Graphics context
		*/
		 protected void paintComponent(Graphics g)
		 {
			Rectangle rect;
			Font font;
			int i;
			Integer nIndex;

			// clear canvas
			rect = g.getClipBounds();
			g.setColor( getBackground() );
			g.fillRect( rect.x,rect.y,rect.width,rect.height );

			// draw conecting line
			g.setColor( Color.BLACK );
			i = mnodeNames.size()-1;
			if( i<0 ) i = 0;
			g.drawLine( mstaticXOffset + mstaticNodeWidth/2, mstaticYOffset + mstaticNodeHeight/2,
				      mstaticXOffset + mstaticNodeWidth/2 + i * (mstaticNodeWidth + mstaticXGap), mstaticYOffset + mstaticNodeHeight/2 );

			// draw feedback loops
			for( i=0;i<mfeedbackLoops.size();i++ ) {
				// get node for feedback (must be > 0)
				nIndex = (Integer) mfeedbackLoops.elementAt(i);

				if( nIndex.intValue() > 0 ) {
					// draw feedback arrow from nIndex to nIndex - 1
					g.drawLine( mstaticXOffset + mstaticNodeWidth/2 + nIndex.intValue() * (mstaticNodeWidth + mstaticXGap), mstaticYOffset + mstaticNodeHeight/2,
					      mstaticXOffset + mstaticNodeWidth/2 + nIndex.intValue() * (mstaticNodeWidth + mstaticXGap), 10 );
					g.drawLine( mstaticXOffset + mstaticNodeWidth/2 + nIndex.intValue() * (mstaticNodeWidth + mstaticXGap), 10,
					      mstaticXOffset + mstaticNodeWidth/2 + (nIndex.intValue()-1) * (mstaticNodeWidth + mstaticXGap), 10 );
					g.drawLine( mstaticXOffset + mstaticNodeWidth/2 + (nIndex.intValue()-1) * (mstaticNodeWidth + mstaticXGap), 10,
					      mstaticXOffset + mstaticNodeWidth/2 + (nIndex.intValue()-1) * (mstaticNodeWidth + mstaticXGap), mstaticYOffset + mstaticNodeHeight/2 );
				}
			} // next feedback loop


			// draw each node
			for( i=0;i<mnodeNames.size();i++ ) {
				drawNode( g,i,(Integer) mnodeValues.elementAt(i), (String) mnodeNames.elementAt(i), (Integer) mnodeOrdinals.elementAt(i) );
			}

			// draw halted signal (if needed)
			if( mbHalted ) {
				g.setColor( Color.RED );
				font = new Font( null,Font.BOLD,18 );
				g.setFont( font );
				g.drawString( "Halted", 5, mstaticYOffset + mstaticNodeHeight );
			}
			else {
				// draw a "workflow" label
				g.setColor( Color.GRAY );
				font = new Font( null,Font.BOLD | Font.ITALIC,14 );
				g.setFont( font );
				g.drawString( "Workflow", 5, mstaticYOffset + mstaticNodeHeight );
			}
		} // end paint

		/*
		* draw a node with a colour
		* @param g Graphics context
		* @param nIndex index of node, starting from 0
		* @param nValue Value of node, 1 = green, 2 = gray, others = red
		* @param strLabel Label for the node, to be drawn under it
            * @param nOrdinal value to be displayed within node (0 = no value if displayed)
		*/
		public void drawNode( Graphics g, int nIndex, Integer nValue, String strLabel, Integer nOrdinal )
		{
			Font font;

			// draw node outline
			g.setColor( Color.BLACK );
			g.drawRect( mstaticXOffset + nIndex * (mstaticNodeWidth +mstaticXGap), mstaticYOffset, mstaticNodeWidth, mstaticNodeHeight );

			// draw node contents
			switch( nValue.intValue() ) {
				case 1 : g.setColor( Color.GREEN ); break;
				case 2 : g.setColor( Color.GRAY ); break;
				default : g.setColor( Color.BLUE );
			}
			g.fillRect( mstaticXOffset + nIndex * (mstaticNodeWidth +mstaticXGap), mstaticYOffset, mstaticNodeWidth, mstaticNodeHeight );

			// draw node label
			g.setColor( Color.BLACK );
			font = new Font( null,Font.PLAIN,12 );
			g.setFont( font );
			g.drawString( strLabel, mstaticXOffset + nIndex * (mstaticNodeWidth +mstaticXGap), mstaticYOffset + mstaticNodeHeight + 15 );

			// draw ordinal value if > 0
			if( nOrdinal.intValue() > 0 ) {
				g.setColor( Color.BLACK );
				font = new Font( null,Font.PLAIN,12 );
				g.setFont( font );
				g.drawString( nOrdinal.toString(), mstaticXOffset + nIndex * (mstaticNodeWidth +mstaticXGap) + 5, mstaticYOffset + mstaticNodeHeight - 5 );
			}

		} // end drawNode
	} // end WorkflowDiagram class

	/*
	* Dialogue to handle intermediate file downloads
	*/
	class FileDownloadDialogue extends JFrame implements ActionListener, DocumentListener
	{
		// interface objects
		private JProgressBar mprogressBar;
		private JTextField mtextFileList;
		private JTextField mtextOutputFile;
		private JButton mbuttonDownload;
		private JButton mbuttonOutput;
		private GEMSSNegotiator mnegotiator;

		// other objects
		boolean mbDownloadStarted;

		/*
		* constructor
		* @param proxy GEMSS proxy
		*/
		public FileDownloadDialogue( GEMSSNegotiator negotiator )
		{
			super( "Intermediate result file download" );

			JPanel panel, subpanel, subpanel2;

			// set vars
			mnegotiator = negotiator;

			// make interface objects
			mtextFileList = new JTextField( 25 );
			mtextOutputFile = new JTextField( 25 );
			mprogressBar = new JProgressBar( 0,100 );
			mprogressBar.setBorderPainted( true );
			mbuttonDownload = new JButton( "Start Download" );
			mbuttonOutput = new JButton( "..." );

			// make actions
			mbuttonDownload.setActionCommand("Download");
			mbuttonDownload.addActionListener( this );
			mbuttonDownload.setEnabled( false );
			mbuttonDownload.setDefaultCapable( false );
			mbuttonOutput.setActionCommand("Output");
			mbuttonOutput.addActionListener( this );
			mbuttonOutput.setEnabled( true );
			mbuttonOutput.setDefaultCapable( false );

			mtextFileList.setActionCommand("FileList");
			mtextFileList.getDocument().addDocumentListener( this );
			mtextFileList.setEnabled( true );

			mtextOutputFile.setActionCommand("OutputFile");
			mtextOutputFile.getDocument().addDocumentListener( this );
			mtextOutputFile.setEnabled( true );

			// make panel
			panel = new JPanel( new GridLayout(3,1,8,8),true );

			// line one - filename
			subpanel = new JPanel( new BorderLayout(),true );
			subpanel.add( new JLabel( "Remote filename to download : " ),BorderLayout.WEST );
			subpanel.add( mtextFileList,BorderLayout.CENTER );
			panel.add( subpanel );

			// line two - output
			subpanel = new JPanel( new BorderLayout(),true );
			subpanel.add( new JLabel( "Local filename to copy to : " ),BorderLayout.WEST );
			subpanel.add( mtextOutputFile,BorderLayout.CENTER );
			subpanel.add( mbuttonOutput,BorderLayout.EAST );
			panel.add( subpanel );

			// line three - progress
			subpanel = new JPanel( new BorderLayout(),true );
			subpanel2 = new JPanel( new FlowLayout( FlowLayout.LEFT ),true );
			subpanel2.add( new JLabel( "Download Progress" ) );
			subpanel2.add( mprogressBar );
			subpanel.add( subpanel2,BorderLayout.CENTER );
			subpanel.add( mbuttonDownload,BorderLayout.EAST );
			panel.add( subpanel );

			// add panel to dialogue
			getContentPane().setLayout( new BorderLayout(5,5) );
			getContentPane().add( panel,BorderLayout.CENTER );
			getContentPane().add( new JPanel(),BorderLayout.NORTH );
			getContentPane().add( new JPanel(),BorderLayout.EAST );
			getContentPane().add( new JPanel(),BorderLayout.SOUTH );
			getContentPane().add( new JPanel(),BorderLayout.WEST );

			// set title
			setResizable( false );
			pack();

			// set flags etc
			mprogressBar.setValue( 0 );
			mprogressBar.setStringPainted( true );
			mprogressBar.setString( "Not started" );
			mbDownloadStarted = false;
		}

		/*
		* Action listener for the button.
		* @param e action event
		*/
		public void actionPerformed(ActionEvent e)
		{
			DownloadThread thread;
			JFileChooser fileDialogue;
			int nReturnVal;

			try {
				if( "Download".equals(e.getActionCommand()) ) {
					// disable button and text fields
					mbuttonDownload.setEnabled( false );
					mtextFileList.setEnabled( false );
					mtextOutputFile.setEnabled( false );

					// start download
					mbDownloadStarted = true;

					// start the download thread
					thread = new DownloadThread( mnegotiator, mtextFileList.getText(), mtextOutputFile.getText() );
					thread.start();

					// return now to avoid locking up the interface refreshes
				}
				else if( "Output".equals(e.getActionCommand()) ) {
					// popup a file dialogue to choose a file
					fileDialogue = new JFileChooser();
					fileDialogue.setDialogTitle( "Choose filename" );
					fileDialogue.setCurrentDirectory( new File(".") );
					nReturnVal = fileDialogue.showDialog( mbuttonOutput,"Accept" );
					// ignore if aborted
					if( nReturnVal != JFileChooser.APPROVE_OPTION ) return;

					// set the filename into the mtextOutputFile text
					mtextOutputFile.setText( fileDialogue.getSelectedFile().getAbsolutePath() );

					// enable download if appropriate
					if( mtextFileList.getText() != null && mtextFileList.getText().length() > 0 &&
					    mtextOutputFile.getText() != null && mtextOutputFile.getText().length() > 0 ) {
						// enable button
						mbuttonDownload.setEnabled( true );
					}
					else {
						// disable button
						mbuttonDownload.setEnabled( false );
					}
				}
			}
			catch( Exception ex ) {
				System.out.println("Action handler error : "+ex.getMessage());
				ex.printStackTrace( System.out );
			}
		}

		/*
		* Document listener for the text fields.
		* @param e document event
		*/
		public void insertUpdate(DocumentEvent e)	{
			try {
				// ignore if download ongoing
				if( mbDownloadStarted ) return;

				// enable button IF text set for both fields
				if( mtextFileList.getText() != null && mtextFileList.getText().length() > 0 &&
				   mtextOutputFile.getText() != null && mtextOutputFile.getText().length() > 0) {
					// enable button
					mbuttonDownload.setEnabled( true );
				}
				else {
					// disable button
					mbuttonDownload.setEnabled( false );
				}
			}
			catch( Exception ex ) {
				System.out.println("Action handler error : "+ex.getMessage());
				ex.printStackTrace( System.out );
			}
		}
		/*
		* Document listener for the text fields.
		* @param e document event
		*/
		public void removeUpdate(DocumentEvent e)	{
			try {
				// ignore if download ongoing
				if( mbDownloadStarted ) return;

				// enable button IF text set for both fields
				if( mtextFileList.getText() != null && mtextFileList.getText().length() > 0 &&
				   mtextOutputFile.getText() != null && mtextOutputFile.getText().length() > 0) {
					// enable button
					mbuttonDownload.setEnabled( true );
				}
				else {
					// disable button
					mbuttonDownload.setEnabled( false );
				}
			}
			catch( Exception ex ) {
				System.out.println("Action handler error : "+ex.getMessage());
				ex.printStackTrace( System.out );
			}
		}
		/*
		* Document listener for the text fields (does nothing)
		* @param e document event
		*/
		public void changedUpdate(DocumentEvent e) { ; }

		/*
		* Helper in-line class to avoid locking up the interface whilst downloading files
		*/
		class DownloadThread extends Thread {
			// data
			private GEMSSNegotiator mnegotiator;
			private String mstrFile;
			private String mstrOutput;

			/*
			* constructor
			* @param negotiator GEMSSNegotiator
			* @param strFile filename to download
			* @param strOutput filename to save downloaded file
			*/
			public DownloadThread( GEMSSNegotiator negotiator, String strFile, String strOutput ) {
				mnegotiator = negotiator;
				mstrFile = strFile;
				mstrOutput = strOutput;
			}

			/*
			* download the file using the GEMSS proxy
			*/
			public void run() {
				try {
					// show start of download
					mprogressBar.setValue( 50 );
					mprogressBar.setString( "Downloading ..." );

					// do the download
					// would be nice to get some feedback when we use large file transfer
					// since this could take hours.
					mnegotiator.downloadData( mstrFile,mstrOutput );

					// success
					mprogressBar.setValue( 100 );
					mprogressBar.setString( "Complete" );
				}
				catch( Exception ex ) {
					// failed
					mprogressBar.setValue( 100 );
					mprogressBar.setString( "Failed" );

					// text dump too
					System.out.println("Failed to download "+mstrFile+" to "+mstrOutput);
					ex.printStackTrace( System.out );
				}
			}
		} // end of DownloadThread
	} // end FileDownloadDialogue

	/*
	* Helper in-line class to avoid locking up the interface whilst killing job
	*/
	class KillThread extends Thread {
		// data
		private GEMSSNegotiator mnegotiator;

		/*
		* constructor
		* @param negotiator gemss negotiator
		*/
		public KillThread( GEMSSNegotiator negotiator ) {
			mnegotiator = negotiator;
		}

		/*
		* kill job using the GEMSS negotiator
		*/
		public void run() {
			try {
				System.out.println("Killing job");

				// do the kill
				mnegotiator.kill();

				// success
				mbHalted = true;
			}
			catch( Exception ex ) {
				System.out.println("Failed to kill "+ex.getMessage());
				ex.printStackTrace( System.out );
			}
		}
	} // end of KillThread
} // end SampleClientPhaseTwoPanel
