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

package org.biomoby.service.dashboard;

import org.biomoby.shared.MobyException;

import org.tulsoft.tools.gui.SwingUtils;

import javax.swing.JPanel;
import javax.swing.Icon;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JComponent;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JButton;

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

import java.io.IOException;
import java.io.File;
import java.io.PrintStream;
import java.io.PipedOutputStream;
import java.io.PipedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;

/**
 * A panel allowing to register various entities in a Biomoby
 * registry. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: RegistrationPanel.java,v 1.21 2006/10/02 21:12:45 senger Exp $
 */

public class RegistrationPanel
    extends AbstractPanel {

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

    // associated model working behind the scenes
    protected RegistryModel registryModel;

    // components that are used from more methods
    protected CommonConsole console;
    protected JButton registerButton, showXMLButton, fromXMLButton;
    protected JCheckBox copyBySelect;

    // shared icons
    protected static Icon menuAddISAIcon, menuAddISAIconDis;
    protected static Icon menuAddHASAIcon, menuAddHASAIconDis;
    protected static Icon menuAddHASIcon, menuAddHASIconDis;
    protected static Icon unregisterIcon, unregisterIconDis;
    protected static Icon agentIcon, agentIconDis;
    protected static Icon registerIcon, registerIconDis, register2Icon;
    protected static Icon showXMLIcon, showXMLIconDis;
    protected static Icon fromXMLIcon, fromXMLIconDis;
    protected static Icon addDataIcon, addDataIconDis;
    protected static Icon editDataIcon, editDataIconDis;
    protected static Icon deleteDataIcon, trashIcon;

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

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

	if (menuAddISAIcon == null) menuAddISAIcon = loadIcon ("images/smallAddISA.gif");
	if (menuAddISAIconDis == null) menuAddISAIconDis = loadIcon ("images/smallAddISA_dis.gif");
	
	if (menuAddHASAIcon == null) menuAddHASAIcon = loadIcon ("images/smallAddHASA.gif");
	if (menuAddHASAIconDis == null) menuAddHASAIconDis = loadIcon ("images/smallAddHASA_dis.gif");
	
	if (menuAddHASIcon == null) menuAddHASIcon = loadIcon ("images/smallAddHAS.gif");
	if (menuAddHASIconDis == null) menuAddHASIconDis = loadIcon ("images/smallAddHAS_dis.gif");
	
	if (unregisterIcon == null) unregisterIcon = loadIcon ("images/smallTrash.gif");
	if (unregisterIconDis == null) unregisterIconDis = loadIcon ("images/smallTrash_dis.gif");

	if (agentIcon == null) agentIcon = loadIcon ("images/smallAgent.gif");
	if (agentIconDis == null) agentIconDis = loadIcon ("images/smallAgent_dis.gif");

	if (registerIcon == null) registerIcon = loadIcon ("images/smallRegister.gif");
	if (registerIconDis == null) registerIconDis = loadIcon ("images/smallRegister_dis.gif");
	if (register2Icon == null) register2Icon = loadIcon ("images/register2.gif");

 	if (showXMLIcon == null) showXMLIcon = loadIcon ("images/smallToDoc.gif");
 	if (showXMLIconDis == null) showXMLIconDis = loadIcon ("images/smallToDoc_dis.gif");

 	if (fromXMLIcon == null) fromXMLIcon = loadIcon ("images/smallFromDoc.gif");
 	if (fromXMLIconDis == null) fromXMLIconDis = loadIcon ("images/smallFromDoc_dis.gif");

	if (addDataIcon == null) addDataIcon = loadIcon ("images/smallAddData.gif");
	if (addDataIconDis == null) addDataIconDis = loadIcon ("images/smallAddData_dis.gif");

	if (editDataIcon == null) editDataIcon = loadIcon ("images/smallEdit.gif");
	if (editDataIconDis == null) editDataIconDis = loadIcon ("images/smallEdit_dis.gif");

	if (deleteDataIcon == null) deleteDataIcon = loadIcon ("images/smallRemove.gif");
	if (trashIcon == null) trashIcon = loadIcon ("images/smallTrash.gif");
    }

    /**************************************************************************
     *
     **************************************************************************/
    public JComponent getComponent (PropertyChannel propertyChannel) {
 	setPropertyChannel (propertyChannel);
	registryModel = createRegistryModel();
	if (pComponent != null)  return pComponent;

	// console panel
	console = new CommonConsole();
	console.setAppendMode (true);
	console.setPropertyChannel (propertyChannel);

	pComponent = new JPanel (new GridBagLayout(), true);

	// sub-panels for registering various entities
	JTabbedPane tabbedPane = new JTabbedPane();
	tabbedPane.addTab ("Data Type Registration", register2Icon,
			   new RegistrationDataTypeSubPanel()
			   .getComponent (propertyChannel, console));
	tabbedPane.addTab ("Service Registration", register2Icon,
			   new RegistrationServiceSubPanel()
			   .getComponent (propertyChannel, console));
	tabbedPane.addTab ("Namespace Registration", register2Icon,
			   new RegistrationNamespaceSubPanel()
			   .getComponent (propertyChannel, console));
	tabbedPane.addTab ("Service Type Registration", register2Icon,
			   new RegistrationServiceTypeSubPanel()
			   .getComponent (propertyChannel, console));

// 	tabbedPane.setBackgroundAt (0, GraphColours.getColour ("darkseagreen2",
//  							       tabbedPane.getBackgroundAt (0)));
// 	tabbedPane.setBackground (GraphColours.getColour ("darkseagreen2", tabbedPane.getBackground()));

	// split the screen
	JSplitPane split = vSplit (tabbedPane, console, 0.60);

	// put all together
 	SwingUtils.addComponent (pComponent, split, 0, 0, 1, 1, BOTH, NWEST, 1.0, 1.0);

	return pComponent;
    }

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

    /**************************************************************************
     *
     **************************************************************************/
    public String getDescription() {
	return
	    "A panel allowing to register and unregister any Biomoby entity.";
    }

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

    /*********************************************************************
     * Create a file chooser for selecting an XML file (for
     * registration purposes). Use 'preferenceKey' to find the last
     * used value.
     ********************************************************************/
    protected JFileChooser createXMLChooser (String preferenceKey) {
	JFileChooser chooser = new JFileChooser();
	File file = new File (getPrefValue (preferenceKey,
					    System.getProperty ("user.dir")));
	chooser.setSelectedFile (file);
	chooser.setDialogTitle ("Registration from raw XML");
	chooser.setApproveButtonText ("Select XML file");
	chooser.addChoosableFileFilter (getXMLFilter());
	return chooser;
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected JButton createFromXMLButton (String toolTip,
					   String methodName,
					   int mnemonic,
					   String preferenceKey,
					   JFileChooser chooser) {
	final String myMethodName = methodName;
	final String myPreferenceKey = preferenceKey;
	final JFileChooser myChooser = chooser;
	JButton button =
	    createButton (" Register from XML ",
			  toolTip,
			  mnemonic,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (myChooser.showDialog (null, null) == JFileChooser.APPROVE_OPTION) {
				      File selFile = myChooser.getSelectedFile();
				      setPrefValue (myPreferenceKey, selFile.getParent());
				      onRegisterFromXML (selFile, myMethodName);
				  }
			      }
			  });
	button.setIcon (fromXMLIcon);
	button.setDisabledIcon (fromXMLIconDis);
	return button;
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected JButton createRegisterButton (String label,
					    String toolTip,
					    int mnemonic) {
	JButton button =
	    createButton (label,
			  toolTip,
			  mnemonic,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  onRegister (true);
			      }
			  });
	button.setIcon (registerIcon);
	button.setDisabledIcon (registerIconDis);
	return button;
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected JButton createShowXMLButton (String toolTip,
					   int mnemonic) {
	JButton button =
	    createButton (" Show raw XML ",
			  toolTip,
			  mnemonic,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  onRegister (false);
			      }
			  });
	button.setIcon (showXMLIcon);
	button.setDisabledIcon (showXMLIconDis);
	return button;
    }

    /*********************************************************************
     * This is just a common wrapper around a real registration method
     * checkAndRegister (implemented by sub-classes).
     ********************************************************************/
    protected void onRegister (final boolean realRegistration) {
	if (realRegistration)
	    registerButton.setEnabled (false);
	else
	    fromXMLButton.setEnabled (false);
	final SwingWorker worker = new SwingWorker() {
		boolean oldAppendMode;
		StatusBag bag;
		MobyException exception = null;
		public Object construct() {
		    try {
			checkAndRegister (realRegistration);
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}

		// runs on the event-dispatching thread.
		public void finished() {
		    if (exception != null)
			error ("An error occured when trying to register a new entity.\n\n",
			       exception);
		    registerButton.setEnabled (true);
		    fromXMLButton.setEnabled (true);
		    maybeDisableVerbose (bag);
		    console.setAppendMode (oldAppendMode);
		}
	    };
	worker.start(); 
    }

    /*********************************************************************
     * Should be overwritten by sub-panels.
     ********************************************************************/
    public void checkAndRegister (boolean realRegistration)
	throws MobyException {
    }

    /**************************************************************************
     * Should be overwritten by sub-panels.
     **************************************************************************/
    protected void updateCache()
	throws MobyException {
    }

    /*********************************************************************
     *
     ********************************************************************/
    protected void onRegisterFromXML (File fromFile,
				      String methodName) {
	final File xmlFile = fromFile;
	final String myMethodName = methodName;
	registerButton.setEnabled (false);
	fromXMLButton.setEnabled (false);
	final SwingWorker worker = new SwingWorker() {
		boolean oldAppendMode;
		StatusBag bag;
		MobyException exception = null;
		public Object construct() {
		    try {
			bag = maybeEnableVerbose();
			oldAppendMode = console.setAppendMode (true);
			console.setText ("Registering entity from raw XML:\n" +
					 "--------------------------------\n");
			console.setText (registryModel.callRegistry (myMethodName,
								     xmlFile));
			console.setText ("\n");
			updateCache();

		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}

		// runs on the event-dispatching thread.
		public void finished() {
		    if (exception != null)
			error ("An error occured when trying to register using file:\n" +
			       xmlFile.getAbsolutePath() + "\n\n", exception);
		    registerButton.setEnabled (true);
		    fromXMLButton.setEnabled (true);
		    maybeDisableVerbose (bag);
		    console.setAppendMode (oldAppendMode);
		}
	    };
	worker.start(); 
    }

    /*********************************************************************
     * If the registration console is in verbose mode, redirect STDERR
     * to a pipe (that will feed the console) and set registry model
     * to a debug mode (in which it produces verbose messages).
     *
     * Return a bag that will be used later in 'maybeDisableVerbose';
     ********************************************************************/
    protected StatusBag maybeEnableVerbose() {
	if (console == null || !console.isVerboseMode()) return null;
	console.setVerboseEventName (DP_REG_VERBOSE);

	StatusBag bag = new StatusBag();

	// save current stderr stream
	bag.err = System.err;

	// redirect stderr to a pipe
	PipedInputStream pipeIn = null;
	try {
	    PipedOutputStream pipeOut = new PipedOutputStream();
	    pipeIn = new PipedInputStream (pipeOut);
	    System.err.flush();
	    System.setErr (new PrintStream (pipeOut));
	} catch (IOException e) {
	    log.error ("Cannot create pipe for verbose messages: " + e.toString());
	    return null;
	}

	// save current debug status and set in on
	bag.verbose = registryModel.setVerbose (true);

	// start a thread to read from the created pine (and to fire
	// events with verbose messages)
	FireVerboseThread firingThread =
	    new FireVerboseThread (pipeIn, DP_REG_VERBOSE, bag);
	bag.firingThread = firingThread;
	firingThread.start();
	return bag;
    }

    /*********************************************************************
     * Restore what was done by 'maybeDisableVerbose' (if given bag is
     * not null).
     ********************************************************************/
    protected void maybeDisableVerbose (StatusBag bag) {
	if (bag == null) return;

	// stop the firing thread
	// TBD: unfortunately this does not have the effect I have
	// expected: to avoid "write dead end" exception in the thred.
 	Thread thread = bag.firingThread;
 	bag.firingThread = null;
 	thread.interrupt();

	// set debug mode back (but perhaps nothing was stored - so
	// the initialValue() of the localThread is called, which is
	// fine)
	registryModel.setVerbose (bag.verbose);

	// set stderr stream back (again it may use the
	// initialValue())
	System.err.flush();
	System.setErr (bag.err);

    }

    /*********************************************************************
     * A thread that reads verbose messages from a pipe and fires them
     * to property channel.
     *
     * TBD: There is something wrong... It produces 'write dead end'
     * io exception; probably loosing the last line of a
     * message... Not sure how to solve it... (fortunately it's not a
     * big deal)
     ********************************************************************/
    class FireVerboseThread extends Thread {
	private BufferedReader reader;
	private String verboseEventName;
	private StatusBag bag;

	public FireVerboseThread (InputStream in,
				  String verboseEventName,
				  StatusBag bag) {
	    reader = new BufferedReader (new InputStreamReader (in));
	    this.verboseEventName = verboseEventName;
	    this.bag = bag;
	}

	public void run() {
	    String input = null;
	    try {
		Thread thisThread = Thread.currentThread();
		while ((input = reader.readLine()) != null) {
		    propertyChannel.fire (verboseEventName, input + "\n");

		    // this is to stop this thread...
		    if (thisThread != bag.firingThread)
			break;
		}
	    } catch (IOException e) {
//   		log.error ("Firing verbose event failed: " + e.toString());
	    }
	}
    }

    /*********************************************************************
     * Keep here the current state of few things - in order to
     * reproduce them back later.
     ********************************************************************/
    protected class StatusBag {
	public PrintStream err;
	public boolean verbose;
	public Thread firingThread;
    }

}
