/*
 * org.openmicroscopy.shoola.env.ui.ActivityComponent
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2013 University of Dundee. All rights reserved.
 *
 *
 * 	This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */
package org.openmicroscopy.shoola.env.ui;



//Java imports
import info.clearthought.layout.TableLayout;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToolBar;

import omero.model.OriginalFile;

import org.apache.commons.lang.StringUtils;
import org.jdesktop.swingx.JXBusyLabel;
import org.openmicroscopy.shoola.env.Environment;
import org.openmicroscopy.shoola.env.LookupNames;
import org.openmicroscopy.shoola.env.config.Registry;
import org.openmicroscopy.shoola.env.data.ProcessException;
import org.openmicroscopy.shoola.env.data.model.ApplicationData;
import org.openmicroscopy.shoola.env.data.model.DownloadActivityParam;
import org.openmicroscopy.shoola.env.data.model.DownloadAndLaunchActivityParam;
import org.openmicroscopy.shoola.env.data.util.SecurityContext;
import org.openmicroscopy.shoola.env.event.EventBus;
import org.openmicroscopy.shoola.util.filter.file.CSVFilter;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.filechooser.FileChooser;
import pojos.FileAnnotationData;

/**
 * Top class that each action should extend.
 *
 * @author Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 *         <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 *         <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since 3.0-Beta4
 */
public abstract class ActivityComponent 
	extends JPanel
	implements ActionListener
{

	/** Bound property indicating to remove the entry from the display. */
	static final String 	REMOVE_ACTIVITY_PROPERTY = "removeActivity";
	
	/** Bound property indicating to unregister the activity. */
	static final String 	UNREGISTER_ACTIVITY_PROPERTY = "unregisterActivity";

	/** The default dimension of the status. */
	private static final Dimension SIZE = new Dimension(22, 22);
	
	/** ID to remove the entry from the display. */
	private static final int 	REMOVE = 0;
	
	/** ID to cancel the activity. */
	private static final int 	CANCEL = 1;
	
	/** ID to display the standard error. */
	private static final int	EXCEPTION = 2;

	/** ID to display all the output. */
	private static final int	ALL_RESULT = 3;
	
	/** ID to display the error. */
	private static final int	ERROR = 4;
	
	/** ID to display the info. */
	private static final int	INFO = 5;
	
	/** The key to look for to display the output message. */
	private static final String MESSAGE = "Message";
	
	/** The key to look for to display the error message if any. */
	static final String STD_ERR = "stderr";
	
	/** The key to look for to display the output message if any. */
	static final String STD_OUT = "stdout";
	
	/** Indicate the status of the activity. */
	private JXBusyLabel 				status;
	
	/** Button to remove the activity from the display. */
	private JButton						removeButton;
	
	/** Button to cancel the activity. */
	private JButton						cancelButton;

	/** The label hosting the icon. */
	protected JLabel					iconLabel;
	
	/** The component displaying the status. */
	private JComponent					 statusPane;
	
	/** The index of the {@link #cancelButton} or {@link #removeButton}. */
	private int							buttonIndex;
	
	/** The tool bar displaying controls. *. */
	private JToolBar					toolBar;
	
	/** Button to shows the exception. */
	private JButton						exceptionButton;
	
	/** Menu displaying the option to view the standard error. */
	private ActivityResultPopupMenu		errorMenu;
	
	/** Menu displaying the option to view the standard output. */
	private ActivityResultPopupMenu		infoMenu;
	
	/** The exception thrown while running the script. */
	private Throwable					exception;
	
	/** The label displaying the type of activity. */
	protected JLabel					type;

	/** The label displaying message if any. */
	protected JLabel					messageLabel;
	
	/** Convenience reference for subclasses. */
    protected final Registry			registry;
    
    /** Convenience reference for subclasses. */
    protected final SecurityContext ctx;
    
    /** Convenience reference for subclasses. */
    protected final UserNotifier		viewer;
   
    /** The result of the activity. */
    protected Object 					result;
    
    /** Loader associated to the activity. */
    protected UserNotifierLoader 		loader;
    
    /** The object hosting the error.*/
    protected Object					errorObject;
    
    /** The object hosting the info.*/
    protected Object					infoObject;
    
    /** The component displaying the results.*/
    private JComponent					resultPane;
    
	/** Button to show the general result. */
	protected List<ActivityResultRow>	resultButtons;
	
	private PropertyChangeListener		listener;
	
	/** The index where the output goes.*/
	private String paneIndex;
	
    /**
	 * Opens the passed object. Downloads it first.
	 * 
	 * @param object The object to open.
	 * @param parameters Either Analysis parameters or Application data.
	 * @param source The source triggering the operation.
	 */
	private void open(Object object, Object parameters, JComponent source)
	{
		if (!(object instanceof FileAnnotationData || 
				object instanceof OriginalFile)) return;
		Environment env = (Environment) registry.lookup(LookupNames.ENV);
		int index = -1;
		long id = -1;
		String name = "";
		OriginalFile of = null;
		if (object instanceof FileAnnotationData) {
			FileAnnotationData data = (FileAnnotationData) object;
			if (data.isLoaded()) {
				of = (OriginalFile) data.getContent();
				name = data.getFileName();
			} else {
				id = data.getId();
				index = DownloadActivityParam.FILE_ANNOTATION;
				name = "Annotation_"+id;
			}
		} else {
			of = (OriginalFile) object;
			id = of.getId().getValue();
			if (!of.isLoaded()) {
				index = DownloadActivityParam.ORIGINAL_FILE;
				name = "File_"+id;
			} else {
				if (of.getName() != null)
					name = of.getName().getValue();
				else name = "File_"+id;
			}
		}
		String path = env.getOmeroFilesHome();
		File f;
		if (index != -1) {
			path += File.separator+name;
			f = new File(path);
			//Delete the file if it already exists
			if (f.exists()) {
				f.delete();
				f = new File(path);
			}
			f.deleteOnExit();
		} else {
			String v = path + File.separator+name;
			File ff = new File(v);
			if (ff.exists()) 
				ff.delete();
			f = new File(path);
		}
		
		DownloadAndLaunchActivityParam activity;
		
		if (index != -1) 
			activity = new DownloadAndLaunchActivityParam(id, index, f, null);
		else 
			activity = new DownloadAndLaunchActivityParam(of, f, null);
		
		if (parameters instanceof ApplicationData) {
			activity.setApplicationData((ApplicationData) parameters);
		}
		activity.setSource(source);
		viewer.notifyActivity(ctx, activity);
	}
	
	/** 
	 * Initializes the components. 
	 * 
	 * @param text The type of activity.
	 * @param icon The icon to display when done.
	 */
	private void initComponents(String text, Icon icon)
	{
		exceptionButton = createButton("Failure", EXCEPTION, this);
		exceptionButton.setVisible(false);
		//removeButton = createButton("Remove", REMOVE, this);
		IconManager icons = IconManager.getInstance(registry);
		removeButton = new JButton(icons.getIcon(IconManager.REMOVE));
		UIUtilities.unifiedButtonLookAndFeel(removeButton);
		removeButton.setActionCommand(""+REMOVE);
		removeButton.addActionListener(this);
		cancelButton = createButton("Cancel", CANCEL, this);
		//if (index == ADVANCED)
		resultButtons = new ArrayList<ActivityResultRow>();
		status = new JXBusyLabel(SIZE);
		type = UIUtilities.setTextFont(text);
		iconLabel = new JLabel();
		messageLabel = UIUtilities.setTextFont("", 
				iconLabel.getFont().getStyle(), 10);
		iconLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
		if (icon != null) iconLabel.setIcon(icon);
		statusPane = status;
		resultPane = new JPanel();
		listener = new PropertyChangeListener() {
			
			public void propertyChange(PropertyChangeEvent evt) {
				//do something
				
			}
		};
	}
	
	/** Builds and lays out the UI. */
	private void buildGUI()
	{
		JPanel barPane = new JPanel();
		barPane.setOpaque(false);
		barPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		double[][] size = {{TableLayout.FILL}, 
						{TableLayout.PREFERRED, TableLayout.PREFERRED}};
		barPane.setLayout(new TableLayout(size));
		barPane.add(type, "0, 0, LEFT, CENTER");
		barPane.add(messageLabel, "0, 1, CENTER, CENTER");
	
		//icon, message, content, toolbar
		double[][] tl = {{TableLayout.PREFERRED, TableLayout.FILL,
			TableLayout.PREFERRED, TableLayout.PREFERRED},
			{TableLayout.PREFERRED}};
		setLayout(new TableLayout(tl));
		add(statusPane, "0, 0");
		JPanel p = UIUtilities.buildComponentPanel(barPane);
		p.setOpaque(false);
		p.setBackground(barPane.getBackground());
		add(p, "1, 0");
		paneIndex = "2, 0";
		add(resultPane, paneIndex);
		add(createToolBar(), "3, 0");
	}
	
	/**
	 * Returns the tool bar.
	 * 
	 * @return See above.
	 */
	private JComponent createToolBar()
	{
		toolBar = new JToolBar();
		toolBar.setOpaque(false);
		toolBar.setFloatable(false);
		toolBar.setBorder(null);
		buttonIndex = 0;
		toolBar.add(exceptionButton);
		toolBar.add(Box.createHorizontalStrut(5));
		buttonIndex = 2;
		toolBar.add(cancelButton);
		JLabel l = new JLabel();
		Font f = l.getFont();
		l.setForeground(UIUtilities.LIGHT_GREY.darker());
		l.setFont(f.deriveFont(f.getStyle(), f.getSize()-2));
		String s = UIUtilities.formatShortDateTime(null);
		String[] values = s.split(" ");
		if (values.length > 1) {
			String v = values[1];
			if (values.length > 2) v +=" "+values[2];
			l.setText(v);
			toolBar.add(Box.createHorizontalStrut(5));
			toolBar.add(l);
			toolBar.add(Box.createHorizontalStrut(5));
		}
		return toolBar;
	}
	
	/** Resets the controls. */
	private void reset()
	{
		toolBar.remove(buttonIndex);
		toolBar.add(removeButton, buttonIndex);
		removeButton.setEnabled(true);
		exceptionButton.setVisible(false);
		status.setBusy(false);
		status.setVisible(false);
		statusPane = iconLabel;
		remove(statusPane);
		add(statusPane, "0, 0, CENTER, CENTER");
		repaint();
	}
	
	/**
	 * Converts the passed mapped.
	 * 
	 * @param m The map to handle.
	 * @return See above.
	 */
	private Map<String, Object> convertResult(Map<String, Object> m)
	{
		Map<String, Object> objects = new HashMap<String, Object>();
		if (m == null) return objects;
		messageLabel.setText("");
		Object v = m.get(MESSAGE);
		if (v != null) {
			if (v instanceof String)
				messageLabel.setText((String) v);
		}
		m.remove(MESSAGE);
		if (m.containsKey(STD_ERR)) {
			errorObject = m.get(STD_ERR);
			m.remove(STD_ERR);
		}
		if (m.containsKey(STD_OUT)) {
			infoObject = m.get(STD_OUT);
			m.remove(STD_OUT);
		}
		return m;
	}
	
	/** Shows the exception. */
	private void showException()
	{
		if (exception == null) return;
		viewer.notifyError(type.getText(), messageLabel.getText(), exception);
	}
	
    /**
     * Returns the identifier of the plugin to run.
     * 
     * @return See above.
     */
    private int runAsPlugin()
    {
    	Environment env = (Environment) registry.lookup(LookupNames.ENV);
    	if (env == null) return -1;
    	return env.runAsPlugin();
    }
    
	/**
     * Creates a new instance.
     * 
     * @param viewer The viewer this data loader is for.
     *               Mustn't be <code>null</code>.
     * @param registry Convenience reference for subclasses.
     * @param ctx The security context.
     */
	ActivityComponent(UserNotifier viewer, Registry registry,
			SecurityContext ctx)
	{
		if (viewer == null) throw new NullPointerException("No viewer.");
    	if (registry == null) throw new NullPointerException("No registry.");
    	this.viewer = viewer;
    	this.registry = registry;
    	this.ctx = ctx;
	}
	
	/**
	 * Initializes the components.
	 * 
	 * @param text		The text of the activity.
     * @param icon		The icon to display then done.
	 */
	void initialize(String text, Icon icon)
	{
		initComponents(text, icon);
		buildGUI();
	}

    /**
     * Creates a button.
     * 
     * @param text The text of the button.
     * @param actionID The action command id.
     * @param l The action listener.
     * @return See above.
     */
    JButton createButton(String text, int actionID, ActionListener l)
    {
    	JButton b = UIUtilities.createHyperLinkButton(text);
		b.setActionCommand(""+actionID);
		b.addActionListener(l);
		return b;
    }

    /**
	 * Returns the name to give to the file.
	 * 
	 * @param files Collection of files in the currently selected directory.
	 * @param fileName The name of the original file.
	 * @param original The name of the file. 
	 * @param dirPath Path to the directory.
	 * @param index The index of the file.
	 * @param extension The extension to check or <code>null</code>.
	 * @return See above.
	 */
	String getFileName(File[] files, String fileName, String original, 
								String dirPath, int index, String extension)
	{
		String path = dirPath+original;
		boolean exist = false;
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
	        	 if ((files[i].getAbsolutePath()).equals(path)) {
	                 exist = true;
	                 break;
	             }
			}
		}
        if (!exist) return original;
        if (StringUtils.isEmpty(fileName)) return original;
    	
    	if (!StringUtils.isEmpty(extension)) {
    		int n = fileName.lastIndexOf(extension);
    		String v = fileName.substring(0, n)+"_("+index+")"+extension;
    		index++;
    		return getFileName(files, fileName, v, dirPath, index, extension);
    	} else {
    		int lastDot = fileName.lastIndexOf(".");
    		if (lastDot != -1) {
        		extension = fileName.substring(lastDot, fileName.length());
        		String v = fileName.substring(0, lastDot)+"_("+index+")"+
        		extension;
        		index++;
        		return getFileName(files, fileName, v, dirPath, index, null);
        	} 
    	}
    	
    	return original;
	}
	
	/** Invokes when the activity has been cancelled. */
	public void onActivityCancelled()
	{
		reset();
		firePropertyChange(UNREGISTER_ACTIVITY_PROPERTY, null, this);
		notifyActivityCancelled();
		EventBus bus = registry.getEventBus();
		bus.post(new ActivityProcessEvent(this, false));
	}
	
	/** Invokes when the call-back has been set. */
	public void onCallBackSet()
	{
		cancelButton.setEnabled(true);
	}
	
	/** Invokes when the activity starts. */ 
	public void startActivity()
	{
		status.setBusy(true);
	}
	
	/**
	 * Returns <code>true</code> if the result can be displayed, 
	 * <code>false</code> otherwise.
	 * 
	 * @param object The object to handle.
	 * @return See above.
	 */
	boolean canPlotResult(Object object)
	{
		if (object instanceof FileAnnotationData) {
			FileAnnotationData fa = (FileAnnotationData) object;
			if (fa.isLoaded()) {
				return fa.getFileName().endsWith("."+CSVFilter.CSV);
			}
		}
		return false;
	}
	
	/** 
	 * Downloads the passed object is supported.
	 * 
	 * @param text   The text used if the object is not loaded.
	 * @param object The object to handle.
	 * @param folder Indicates where to download the file or <code>null</code>.
	 */
	void download(String text, Object object, File folder)
	{
		if (!(object instanceof FileAnnotationData || 
				object instanceof OriginalFile)) return;
		int index = -1;
		if (text == null) text = "";
		String name = "";
		String description = "";
		long dataID = -1;
		OriginalFile of = null;
		if (object instanceof FileAnnotationData) {
			FileAnnotationData data = (FileAnnotationData) object;
			if (data.isLoaded()) {
				name = data.getFileName();
				description = data.getDescription();
				of = (OriginalFile) data.getContent();
			} else {
				of = null;
				dataID = data.getId();
				index = DownloadActivityParam.FILE_ANNOTATION;
				if (text.length() == 0) text = "Annotation";
				name = text+"_"+dataID;
			}
		} else {
			of = (OriginalFile) object;
			if (!of.isLoaded()) {
				dataID = of.getId().getValue();
				index = DownloadActivityParam.ORIGINAL_FILE;
				if (text.length() == 0) text = "File";
				name = text+"_"+dataID;
				of = null;
			}
		}
		final OriginalFile original = of;
		final int type = index;
		final String desc = description;
		final long id = dataID;
		if (folder != null) {
			if (original == null && type == -1) return;
			DownloadActivityParam activity;
			IconManager icons = IconManager.getInstance(registry);
			if (original != null) {
				activity = new DownloadActivityParam(original,
						folder, icons.getIcon(IconManager.DOWNLOAD_22));
				
			} else {
				activity = new DownloadActivityParam(id, type,
						folder, icons.getIcon(IconManager.DOWNLOAD_22));
			}
			activity.setLegend(desc);
			activity.setUIRegister(false);
			viewer.notifyActivity(ctx, activity);
			return;
		}
		JFrame f = registry.getTaskBar().getFrame();
		FileChooser chooser = new FileChooser(f, FileChooser.SAVE, 
				"Download", "Select where to download the file.", null, 
				true, true);
		IconManager icons = IconManager.getInstance(registry);
		chooser.setTitleIcon(icons.getIcon(IconManager.DOWNLOAD_48));
		chooser.setSelectedFileFull(name);
		chooser.setApproveButtonText("Download");
		chooser.addPropertyChangeListener(new PropertyChangeListener() {
		
			public void propertyChange(PropertyChangeEvent evt) {
				String name = evt.getPropertyName();
				if (FileChooser.APPROVE_SELECTION_PROPERTY.equals(name)) {
					File[] files = (File[]) evt.getNewValue();
					File folder = files[0];
					if (original == null && type == -1) return;
					IconManager icons = IconManager.getInstance(registry);
					DownloadActivityParam activity;
					if (original != null) {
						activity = new DownloadActivityParam(original,
								folder, icons.getIcon(IconManager.DOWNLOAD_22));
						
					} else {
						activity = new DownloadActivityParam(id, type,
								folder, icons.getIcon(IconManager.DOWNLOAD_22));
					}
					activity.setLegend(desc);
					viewer.notifyActivity(ctx, activity);
				}
			}
		});
		chooser.centerDialog();
	}
	
	/** 
	 * Downloads the passed object is supported.
	 * 
	 * @param text   The text used if the object is not loaded.
	 * @param object The object to handle.
	 */
	void download(String text, Object object)
	{
		download(text, object, null);
	}
	
	/**
	 * Views the passed object if supported.
	 * 
	 * @param object The object to view.
	 * @param source The UI component invoking the method.
	 */
	void view(Object object, JComponent source)
	{
		if (object instanceof FileAnnotationData || 
				object instanceof OriginalFile) {
			open(object, null, source);
		} else if (object instanceof File) {
			viewer.openApplication(null, ((File) object).getAbsolutePath());
			if (source != null) source.setEnabled(true);
		} else {
			EventBus bus = registry.getEventBus();
			//Check if running as plug-in
			ViewObjectEvent evt = new ViewObjectEvent(ctx, object, source);
			evt.setPlugin(runAsPlugin());
			bus.post(evt);
		}
	}
	
	/**
	 * Browses the node.
	 * 
	 * @param object The object to view.
	 * @param source The UI component invoking the method.
	 */
	void browse(Object object, JComponent source)
	{
		EventBus bus = registry.getEventBus();
		ViewObjectEvent evt = new ViewObjectEvent(ctx, object, source);
		evt.setBrowseObject(true);
		bus.post(evt);
	}
	
	/**
	 * Returns <code>true</code> if information returned by script,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	boolean hasInfo() { return infoObject != null; }
	
	/**
	 * Returns <code>true</code> if information returned by script,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	boolean hasError() { return errorObject != null; }
	
	/**
	 * Returns <code>true</code> if the activity is still on-going,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	boolean isOngoingActivity() { return status.isBusy(); }
	
	/**
	 * Notifies that it was not possible to complete the activity.
	 * 
	 * @param text The text to set.
	 * @param message The reason of the error.
	 * @param ex The exception to handle.
	 */
	public void notifyError(String text, String message, Throwable ex)
	{
		reset();
		
		int status = -1;
		if (ex != null) {
			Throwable cause = ex.getCause();
			if (cause instanceof ProcessException) {
				status = ((ProcessException) cause).getStatus();
				if (status == ProcessException.NO_PROCESSOR)
					messageLabel.setText("No processor available. " +
							"Please try later.");
			}
		}
		
		if (text != null) {
			type.setText(text);
			if (message != null)
				type.setToolTipText(message);
		}
		//if (message != null) messageLabel.setText(message);
		exception = ex;
		if (exception != null && status != ProcessException.NO_PROCESSOR) {
			exceptionButton.setVisible(true);
			exceptionButton.setToolTipText(
					UIUtilities.formatExceptionForToolTip(ex));
		}
		firePropertyChange(UNREGISTER_ACTIVITY_PROPERTY, null, this);
		notifyActivityError();
		EventBus bus = registry.getEventBus();
		bus.post(new ActivityProcessEvent(this, true));
	}
	
	/**
	 * Shows the menu corresponding to the passed index.
	 * 
	 * @param src The source of the click
	 * @param index The index indicating which menu to show.
	 * @param x The x-coordinate of the mouse clicked.
	 * @param y The y-coordinate of the mouse clicked.
	 */
	private void showMenu(JComponent src, int index, int x, int y)
	{
		switch (index) {
			case ERROR:
				if (errorMenu == null)
					errorMenu = new ActivityResultPopupMenu(errorObject, this);
				errorMenu.show(src, x, y);
				break;
			case INFO:
				if (infoMenu == null)
					infoMenu = new ActivityResultPopupMenu(infoObject, this);
				infoMenu.show(src, x, y);
		}
	}
	
	/** 
	 * Invokes when the activity end. 
	 * 
	 * @param result The result of the activity.
	 */ 
	public void endActivity(Object result)
	{
		this.result = result;
		boolean busy = status.isBusy();
		reset();
		if (result instanceof Map) {
			Map<String, Object> m = convertResult((Map<String, Object>) result);
			int size = m.size();
			this.result = m;
			remove(resultPane);
			Color c = getBackground();
			if (size == 0) {
				JToolBar row = new JToolBar();
				row.setOpaque(false);
				row.setFloatable(false);
				row.setBorder(null);
				row.setBackground(c);
				JButton button;
				if (errorObject != null) {
					button = createButton(ActivityResultRow.ERROR_TEXT, 
							ERROR, this);
					button.addMouseListener(new MouseAdapter() {
						
						public void mouseReleased(MouseEvent e) {
							showMenu((JComponent) e.getSource(), ERROR,
									e.getX(), e.getY()); 
						}
					});
					row.add(button);
				}
				if (infoObject != null) {
					button = createButton(ActivityResultRow.INFO_TEXT, 
							INFO, this);
					button.addMouseListener(new MouseAdapter() {
						
						public void mouseReleased(MouseEvent e) {
							showMenu((JComponent) e.getSource(), INFO,
									e.getX(), e.getY()); 
						}
					});
					row.add(button);
				}
				add(row, paneIndex);
			} else {
				Entry<String, Object> entry;
				Iterator<Entry<String, Object>> i = m.entrySet().iterator();
				ActivityResultRow row = null;
				JPanel content = new JPanel();
				content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
				
				content.setBackground(c);
				int index = 0;
				int max = 2;
				JButton moreButton = null;
				while (i.hasNext()) {
					entry = (Entry<String, Object>) i.next();
					this.result = entry.getValue();
					row = new ActivityResultRow((String) entry.getKey(), 
							entry.getValue(), this);
					row.setBackground(c);
					row.addPropertyChangeListener(listener);
					resultButtons.add(row);
					if (index < max)
						content.add(row);
					else {
						if (moreButton == null) {
							moreButton = createButton(""+(m.size()-max)+" more",
									ALL_RESULT, this);
							content.add(moreButton);
						}
					}
					index++;
				}
				
				if (m.size() == 1) add(row, paneIndex);
				else add(content, paneIndex);
				resultPane = content;
			}
			
			repaint();
		}
		
		firePropertyChange(UNREGISTER_ACTIVITY_PROPERTY, null, this);
		notifyActivityEnd();
		//Post an event to 
		//if (busy) {
		EventBus bus = registry.getEventBus();
		bus.post(new ActivityProcessEvent(this, busy));
		//}
	}

	
	/**
	 * Returns the type of activity.
	 * 
	 * @return See above.
	 */
	public JComponent getActivityType()
	{
		return new JLabel(type.getText());
	}
	
	/** Subclasses should override the method. */
	protected abstract void notifyActivityCancelled();
	
	/** Subclasses should override the method. */
	protected abstract void notifyActivityEnd();
	
	/** Subclasses should override the method. */
	protected abstract void notifyActivityError();
	
	/** Creates a loader. */
	protected abstract UserNotifierLoader createLoader();
	
	/**
	 * Removes the activity from the display
	 * @see ActionListener#actionPerformed(ActionEvent)
	 */
	public void actionPerformed(ActionEvent e)
	{
		int index = Integer.parseInt(e.getActionCommand());
		switch (index) {
			case REMOVE:
				firePropertyChange(REMOVE_ACTIVITY_PROPERTY, null, this);
				break;
			case CANCEL:
				onActivityCancelled();
				if (cancelButton != null) cancelButton.setEnabled(false);
				if (loader != null) loader.cancel();
				break;
			case EXCEPTION:
				showException();
				break;
			case ALL_RESULT:
				Iterator<ActivityResultRow> i = resultButtons.iterator();
				JPanel content = new JPanel();
				content.setBackground(getBackground());
				content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
				while (i.hasNext()) {
					content.add(i.next());
				}
				remove(resultPane);
				add(content, paneIndex);
				resultPane = content;
				validate();
				repaint();
				break;
		}
	}
	
	/**
	 * Overridden to make sure that all the components have the correct 
	 * background.
	 * @see JPanel#setBackground(Color)
	 */
	public void setBackground(Color color)
	{
		super.setBackground(color);
		if (resultPane != null) resultPane.setBackground(color);
		if (removeButton != null) removeButton.setBackground(color);
		if (cancelButton != null) cancelButton.setBackground(color);
		if (exceptionButton != null) exceptionButton.setBackground(color);
	}
	
}
