/*
 * org.openmicroscopy.shoola.agents.fsimporter.util.FileImportComponent 
 *
 *------------------------------------------------------------------------------
 *  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.agents.fsimporter.util;


//Java imports
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
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.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

import info.clearthought.layout.TableLayout;
import info.clearthought.layout.TableLayoutConstraints;

import omero.cmd.CmdCallback;
import omero.cmd.CmdCallbackI;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.jdesktop.swingx.JXBusyLabel;
import org.jdesktop.swingx.JXTaskPane;
import org.openmicroscopy.shoola.agents.events.importer.BrowseContainer;
import org.openmicroscopy.shoola.agents.events.iviewer.ViewImage;
import org.openmicroscopy.shoola.agents.events.iviewer.ViewImageObject;
import org.openmicroscopy.shoola.agents.fsimporter.IconManager;
import org.openmicroscopy.shoola.agents.fsimporter.ImporterAgent;
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import org.openmicroscopy.shoola.agents.util.browser.TreeImageDisplay;
import org.openmicroscopy.shoola.agents.util.ui.EditorDialog;
import org.openmicroscopy.shoola.agents.util.ui.ThumbnailLabel;
import org.openmicroscopy.shoola.env.data.ImportException;
import org.openmicroscopy.shoola.env.data.model.ImportableFile;
import org.openmicroscopy.shoola.env.data.model.ImportableObject;
import org.openmicroscopy.shoola.env.data.model.ThumbnailData;
import org.openmicroscopy.shoola.env.data.util.SecurityContext;
import org.openmicroscopy.shoola.env.data.util.StatusLabel;
import org.openmicroscopy.shoola.env.event.EventBus;
import org.openmicroscopy.shoola.util.file.ImportErrorObject;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import pojos.DataObject;
import pojos.DatasetData;
import pojos.FileAnnotationData;
import pojos.FilesetData;
import pojos.PixelsData;
import pojos.PlateData;
import pojos.ProjectData;
import pojos.ScreenData;
import pojos.TagAnnotationData;

/** 
 * Component hosting the file to import and displaying the status of the 
 * import process.
 *
 * @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>
 * @author Blazej Pindelski, bpindelski at dundee.ac.uk
 * @version 3.0
 * @since 3.0-Beta4
 */
public class FileImportComponent
	extends JPanel
	implements PropertyChangeListener
{
	/** Indicates that the container is of type <code>Project</code>. */
	public static final int PROJECT_TYPE = 0;
	
	/** Indicates that the container is of type <code>Screen</code>. */
	public static final int SCREEN_TYPE = 1;
	
	/** Indicates that the container is of type <code>Dataset</code>. */
	public static final int DATASET_TYPE = 2;
	
	/** Indicates that no container specified. */
	public static final int NO_CONTAINER = 3;
	
	/** Bound property indicating to retry an upload.*/
	public static final String RETRY_PROPERTY = "retry";
	
	/** 
	 * Bound property indicating that the error to submit is selected or not.
	 */
	public static final String SUBMIT_ERROR_PROPERTY = "submitError";
	
	/** Bound property indicating to display the error.*/
	public static final String DISPLAY_ERROR_PROPERTY = "displayError";
	
	/** Bound property indicating to cancel the import.*/
	public static final String CANCEL_IMPORT_PROPERTY = "cancelImport";
	
	/** Bound property indicating to browse the node. */
	public static final String BROWSE_PROPERTY = "browse";
	
	/** Bound property indicating to increase the number of files to import. */
	public static final String IMPORT_FILES_NUMBER_PROPERTY = "importFilesNumber";
	
	/**
	 * Bound property indicating to load the content of the log file.
	 */
	public static final String LOAD_LOGFILEPROPERTY = "loadLogfile";

	/**
	 * Bound property indicating to retrieve the log file.
	 */
	public static final String RETRIEVE_LOGFILEPROPERTY = "retrieveLogfile";
	
	/**
	 * Bound property indicating to show the checksums,
	 */
	public static final String CHECKSUM_DISPLAY_PROPERTY = "checksumDisplay";

	/** The default size of the busy label. */
	private static final Dimension SIZE = new Dimension(16, 16);

	/** The number of extra labels for images to add. */
	public static final int MAX_THUMBNAILS = 3;

	/** Text indicating that the folder does not contain importable files.*/
	private static final String EMPTY_FOLDER = "No data to import";
	
	/** The maximum width used for the component.*/
	private static final int LENGTH = 350;
	
	/** 
	private static final String EMPTY_DIRECTORY = "No data to import";
	
	/** One of the constants defined by this class. */
	private int type;

	/** The component indicating the progress of the import. */
	private JXBusyLabel busyLabel;
	
	/** The component displaying the file name. */
	private JPanel namePane;

	/** The component displaying the result. */
	private JLabel resultLabel;
	
	/** The component displaying the imported image. */
	private ThumbnailLabel imageLabel;
	
	/** Keeps track of the extra images if any. */
	private List<ThumbnailLabel> imageLabels;

	/** The imported image. */
	private Object image;

	/** Indicates the status of the on-going import. */
	private StatusLabel statusLabel;
	
	/** The component displaying the name of the file. */
	private JLabel fileNameLabel;
	
	/** Keep tracks of the components. */
	private Map<File, FileImportComponent> components;
	
	/** The mouse adapter to view the image. */
	private MouseAdapter adapter;

	/** The data object corresponding to the folder. */
	private DataObject containerFromFolder;
	
	/** Button to cancel the import for that file. */
	private JButton cancelButton;
	
	/** The node where to import the folder. */
	private DataObject data;
	
	/** The dataset if any. */
	private DatasetData dataset;
	
	/** The node of reference if any. */
	private Object refNode;
	
	/** The object where the data have been imported.*/
	private DataObject containerObject;
	
	/** The component used when importing a folder. */
	private JXTaskPane pane;
	
	/** The parent of the node. */
	private FileImportComponent parent;

	/** 
	 * Flag indicating that the container hosting the imported image
	 * can be browsed or not depending on how the import is launched.
	 */
	private boolean browsable;
	
	/** Set to <code>true</code> if attempt to re-import.*/
	private boolean reimported;
	
	/** Flag indicating the the user is member of one group only.*/
	private boolean singleGroup;

	/** The button displayed the various options post if the import worked.*/
	private JButton actionMenuButton;
	
	/** The popup menu associated with the action button */
	private JPopupMenu menu;
	
	/** The state of the import */
	private ImportStatus resultIndex;
	
	/** The index associated to the main component.*/
	private int index;
	
	/** Reference to the callback.*/
	private CmdCallback callback;
	
	/** The importable object.*/
	private ImportableFile importable;
	
	/** The collection of tags added to the imported images.*/
	private Collection<TagAnnotationData> tags;
	
	/** The label indicating the status of the import.*/
	private JLabel refLabel;
	
	/** The button displaying the available action.*/
	private JButton refButton;
	
	/** Retries to upload the file.*/
	private void retry()
	{
		Object o = statusLabel.getImportResult();
		if (o instanceof Exception || image instanceof Exception)
			firePropertyChange(RETRY_PROPERTY, null, this);
	}
	/**
	 * Creates or recycles the menu corresponding to the import status.
	 * 
	 * @return See above.
	 */
	private JPopupMenu createActionMenu()
	{
	    if (menu != null) return menu;
	    menu = new JPopupMenu();
	    JMenuItem item;
	    String logText = "View Import Log";
	    String checksumText = "View Checksum";
	    String exceptionText = "View Exception";
	    Object result = statusLabel.getImportResult();
	    switch (resultIndex) {
	    case FAILURE_LIBRARY:
	        menu.add(new JMenuItem(new AbstractAction(exceptionText) {
                public void actionPerformed(ActionEvent e) {
                    viewError();
                }
            }));
	        break;
	    case FAILURE:
	        menu.add(new JMenuItem(new AbstractAction("Submit") {
	            public void actionPerformed(ActionEvent e) {
	                submitError();
	            }
	        }));
	        menu.add(new JMenuItem(new AbstractAction(exceptionText) {
                public void actionPerformed(ActionEvent e) {
                    viewError();
                }
            }));
	        break;
	    case UPLOAD_FAILURE:
	        menu.add(new JMenuItem(new AbstractAction("Retry") {
	            public void actionPerformed(ActionEvent e) {
	                retry();
	            }
	        }));
	        break;
	    case SUCCESS:
	        logText = "Import Log";
	        checksumText = "Checksum";
	        item = new JMenuItem(new AbstractAction("In Full Viewer") {
	            public void actionPerformed(ActionEvent e) {
	                launchFullViewer();
	            }
	        });
	        boolean b = false;
	        if (result instanceof Collection)
	            b = ((Collection) result).size() == 1;
	        item.setEnabled(b && !statusLabel.isHCS());
	        menu.add(item);
	        item = new JMenuItem(new AbstractAction("In Data Browser") {
	            public void actionPerformed(ActionEvent e) {
	                browse();
	            }
	        });
	        item.setEnabled(browsable);
	        menu.add(item);
	    }
	    item = new JMenuItem(new AbstractAction(logText) {
	        public void actionPerformed(ActionEvent e) {
	            displayLogFile();
	        }
	    });
	    item.setEnabled(statusLabel.getLogFileID() > 0 ||
	            statusLabel.getFileset() != null);
	    menu.add(item);

	    item = new JMenuItem(new AbstractAction(checksumText) {
	        public void actionPerformed(ActionEvent e) {
	            showChecksumDetails();
	        }
	    });
	    item.setEnabled(statusLabel.hasChecksum());
	    menu.add(item);
	    return menu;
	}

	/** Displays or loads the log file.*/
	private void displayLogFile()
	{
		firePropertyChange(LOAD_LOGFILEPROPERTY, null, this);
	}

	/**
	 * Displays the checksum details dialog for the file(s) in this entry
	 */
	private void showChecksumDetails()
	{
		firePropertyChange(CHECKSUM_DISPLAY_PROPERTY, null, statusLabel);
	}

	/**
	 * Formats the tool tip of a successful import.
	 * 
	 * @return See above.
	 */
	private void formatResultTooltip()
	{
		StringBuffer buf = new StringBuffer();
		buf.append("<html><body>");
		buf.append("<b>Date Uploaded: </b>");
		buf.append(UIUtilities.formatShortDateTime(null));
		buf.append("<br>");
		if (image instanceof PlateData) {
			PlateData p = (PlateData) image;
			buf.append("<b>Plate ID: </b>");
			buf.append(p.getId());
			buf.append("<br>");
		}
		if (!statusLabel.isHCS()) {
			Object o = statusLabel.getImportResult();
			if (o instanceof Set) {
				Set<PixelsData> list = (Set<PixelsData>) o;
				int n = list.size();
				if (n == 1) {
					buf.append("<b>Image ID: </b>");
					Iterator<PixelsData> i = list.iterator();
					while (i.hasNext()) {
						buf.append(i.next().getImage().getId());
						buf.append("<br>");
					}
				} else if (n > 1) {
					buf.append("<b>Number of Images: </b>");
					buf.append(n);
					buf.append("<br>");
				}
			}
		}
		buf.append("<b>Size: </b>");
		buf.append(FileUtils.byteCountToDisplaySize(statusLabel.getFileSize()));
		buf.append("<br>");
		buf.append("<b>Group: </b>");
		buf.append(importable.getGroup().getName());
		buf.append("<br>");
		buf.append("<b>Owner: </b>");
		buf.append(EditorUtil.formatExperimenter(importable.getUser()));
		buf.append("<br>");
		if (containerObject instanceof ProjectData) {
			buf.append("<b>Project: </b>");
			buf.append(((ProjectData) containerObject).getName());
			buf.append("<br>");
		} else if (containerObject instanceof ScreenData) {
			buf.append("<b>Screen: </b>");
			buf.append(((ScreenData) containerObject).getName());
			buf.append("<br>");
		} else if (containerObject instanceof DatasetData) {
			buf.append("<b>Dataset: </b>");
			buf.append(((DatasetData) containerObject).getName());
			buf.append("<br>");
		} else if (dataset != null) {
			buf.append("<b>Dataset: </b>");
			buf.append(dataset.getName());
			buf.append("<br>");
		}
		if (!CollectionUtils.isEmpty(tags)) {
			buf.append("<b>Tags: </b>");
			Iterator<TagAnnotationData> i = tags.iterator();
			while (i.hasNext()) {
				buf.append(i.next().getTagValue());
				buf.append(" ");
			}
		}
		buf.append("</body></html>");
		String tip = buf.toString();
		fileNameLabel.setToolTipText(tip);
		resultLabel.setToolTipText(tip);
	}
	
	/** Indicates that the import was successful or if it failed.*/
	private void formatResult()
	{
		if (callback != null) {
			try {
				((CmdCallbackI) callback).close(true);
			} catch (Exception e) {}
		}
		if (namePane.getPreferredSize().width > LENGTH)
			fileNameLabel.setText(EditorUtil.getPartialName(
					getFile().getName()));
		resultLabel.setVisible(true);
		busyLabel.setVisible(false);
		busyLabel.setBusy(false);
		remove(refLabel);
		remove(refButton);
		refLabel = resultLabel;
		refButton = actionMenuButton;
		addControlsToDisplay();
		IconManager icons = IconManager.getInstance();
		Object result = statusLabel.getImportResult();
		if (image instanceof ImportException) result = image;
		if (result instanceof ImportException) {
			ImportException e = (ImportException) result;
			resultLabel.setIcon(icons.getIcon(IconManager.DELETE));
			resultLabel.setToolTipText(
					UIUtilities.formatExceptionForToolTip(e));
			actionMenuButton.setVisible(true);
			actionMenuButton.setForeground(UIUtilities.REQUIRED_FIELDS_COLOR);
			actionMenuButton.setText("Failed");
			int status = e.getStatus();
			if (status == ImportException.CHECKSUM_MISMATCH)
				resultIndex = ImportStatus.UPLOAD_FAILURE;
			else if (status == ImportException.MISSING_LIBRARY)
			    resultIndex = ImportStatus.FAILURE_LIBRARY;
			else resultIndex = ImportStatus.FAILURE;
			statusLabel.setText("");
		} else if (result instanceof CmdCallback) {
			callback = (CmdCallback) result;
		} else {
			formatResultTooltip();
			resultLabel.setIcon(icons.getIcon(IconManager.APPLY));
			actionMenuButton.setVisible(true);
			actionMenuButton.setForeground(UIUtilities.HYPERLINK_COLOR);
			actionMenuButton.setText("View");
			resultIndex = ImportStatus.SUCCESS;
		}
	}

	/** Submits the error.*/
	private void submitError()
	{
		Object o = statusLabel.getImportResult();
		if (o instanceof Exception)
			firePropertyChange(SUBMIT_ERROR_PROPERTY, null, this);
	}

	/** Views the error.*/
	private void viewError()
	{
	    Object o = statusLabel.getImportResult();
	    if (o instanceof ImportException) {
	        String v = UIUtilities.printErrorText((ImportException) o);
	        JFrame f = ImporterAgent.getRegistry().getTaskBar().getFrame();
	        EditorDialog d = new EditorDialog(f, v, EditorDialog.VIEW_TYPE);
	        d.allowEdit(false);
	        UIUtilities.centerAndShow(d);
	    }
	}

	/** Browses the node or the data object. */
	private void browse()
	{
		EventBus bus = ImporterAgent.getRegistry().getEventBus();
		Object d = dataset;
		if (dataset == null || data instanceof ScreenData) d = data;
		if (d == null) return;
		bus.post(new BrowseContainer(d, null));
	}
	
	/**
	 * Indicates that the file will not be imported. 
	 * 
	 * @param fire	Pass <code>true</code> to fire a property,
	 * 				<code>false</code> otherwise.
	 */
	private void cancel(boolean fire)
	{
		boolean b = statusLabel.isCancellable() || getFile().isDirectory();
		if (!isCancelled() && !hasImportFailed() && b) {
			busyLabel.setBusy(false);
			busyLabel.setVisible(false);
			statusLabel.markedAsCancel();
			cancelButton.setEnabled(false);
			cancelButton.setVisible(false);
			//if (fire)
				firePropertyChange(CANCEL_IMPORT_PROPERTY, null, this);
		}
	}

	/**
	 * Launches the full viewer for the selected item.
	 */
	private void launchFullViewer()
	{
		ViewImage evt;
		int plugin = ImporterAgent.runAsPlugin();
		if (image == null) image = statusLabel.getImportResult();
		Object ho = image;
		if (image instanceof Collection) {
			Collection l = (Collection) image;
			if (CollectionUtils.isEmpty(l) || l.size() > 1) return;
			Iterator<Object> i = l.iterator();
			while (i.hasNext()) {
				ho = i.next();
			}
		}
		if (ho instanceof ThumbnailData) {
			ThumbnailData data = (ThumbnailData) ho;
			EventBus bus = ImporterAgent.getRegistry().getEventBus();
			evt = new ViewImage(
					new SecurityContext(importable.getGroup().getId()),
					new ViewImageObject(data.getImageID()), null);
			evt.setPlugin(plugin);
			bus.post(evt);
		} else if (ho instanceof PixelsData) {
			PixelsData data = (PixelsData) ho;
			EventBus bus = ImporterAgent.getRegistry().getEventBus();
			evt = new ViewImage(
					new SecurityContext(importable.getGroup().getId()),
					new ViewImageObject(data.getImage().getId()), null);
			evt.setPlugin(plugin);
			bus.post(evt);
		} else if (image instanceof PlateData) {
			firePropertyChange(BROWSE_PROPERTY, null, image);
		}
	}
	/** Initializes the components. */
	private void initComponents()
	{
		actionMenuButton = new JButton();
		actionMenuButton.setVisible(false);
		actionMenuButton.addActionListener(new ActionListener() {
		    public void actionPerformed(ActionEvent ev) {
				JPopupMenu popup = createActionMenu();
				popup.show(actionMenuButton, 0, actionMenuButton.getHeight());
			}
		});
		
		adapter = new MouseAdapter() {
			
			/**
			 * Views the image.
			 * @see MouseListener#mousePressed(MouseEvent)
			 */
			public void mousePressed(MouseEvent e)
			{ 
				if (e.getClickCount() == 1) {
					launchFullViewer();
				}
			}
		};
		
		setLayout(new FlowLayout(FlowLayout.LEFT));
		busyLabel = new JXBusyLabel(SIZE);
		busyLabel.setVisible(false);
		busyLabel.setBusy(false);
		
		cancelButton = new JButton("Cancel");
		cancelButton.setForeground(UIUtilities.HYPERLINK_COLOR);
		cancelButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ev) {
				cancelLoading();
			}
		});
		cancelButton.setVisible(true);
		
		namePane = new JPanel();
		namePane.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));
		IconManager icons = IconManager.getInstance();
		Icon icon;
		if (getFile().isFile()) icon = icons.getIcon(IconManager.IMAGE);
		else icon = icons.getIcon(IconManager.DIRECTORY);
		imageLabel = new ThumbnailLabel(icon);
		imageLabel.addPropertyChangeListener(this);
		imageLabels = new ArrayList<ThumbnailLabel>();
		ThumbnailLabel label;
		for (int i = 0; i < MAX_THUMBNAILS; i++) {
			label = new ThumbnailLabel();
			if (i == MAX_THUMBNAILS-1) {
				Font f = label.getFont();
				label.setFont(f.deriveFont(f.getStyle(), f.getSize()-2));
			}
			label.setVisible(false);
			label.addPropertyChangeListener(this);
			imageLabels.add(label);
		}
		fileNameLabel = new JLabel(getFile().getName());
		namePane.add(imageLabel);
		Iterator<ThumbnailLabel> j = imageLabels.iterator();
		while (j.hasNext()) {
			namePane.add(j.next());
		}
		namePane.add(Box.createHorizontalStrut(4));
		namePane.add(fileNameLabel);
		namePane.add(Box.createHorizontalStrut(10));
		resultLabel = new JLabel();
		statusLabel = new StatusLabel(importable.getFile());
		statusLabel.addPropertyChangeListener(this);
		image = null;
		refButton = cancelButton;
		refLabel = busyLabel;
	}

	/** Builds and lays out the UI. */
	private void buildGUI()
	{
		double[][] design = new double[][]
				{{LENGTH, TableLayout.FILL,
					TableLayout.PREFERRED, TableLayout.PREFERRED,
					TableLayout.PREFERRED, TableLayout.PREFERRED},
					{TableLayout.PREFERRED}};
		setLayout(new TableLayout(design));
		removeAll();
		if (namePane.getPreferredSize().width > LENGTH)
			fileNameLabel.setText(EditorUtil.getPartialName(
					getFile().getName()));
		add(UIUtilities.buildComponentPanel(namePane, false),
				"0, 0, l, c");
		add(statusLabel, "1, 0, l, c");
		
		/*
		add(busyLabel, "2, 0, l, c");
		add(resultLabel, "3, 0, l, c");
		add(UIUtilities.buildComponentPanel(cancelButton, false),
				"4, 0, l, c");
		add(UIUtilities.buildComponentPanel(actionMenuButton, false),
				"5, 0, l, c");
				*/
		addControlsToDisplay();
	}

	private void addControlsToDisplay()
	{
		add(refLabel, "2, 0, l, c");
		add(UIUtilities.buildComponentPanel(refButton, false), "3, 0, l, c");
	}
	
	/** 
	 * Attaches the listeners to the newly created component.
	 * 
	 * @param c The component to handle.
	 */
	private void attachListeners(FileImportComponent c)
	{
		PropertyChangeListener[] listeners = getPropertyChangeListeners();
		if (listeners != null && listeners.length > 0) {
			for (int j = 0; j < listeners.length; j++) {
				c.addPropertyChangeListener(listeners[j]);
			}
		}
	}
	
	/**
	 * Adds the specified files to the list of import data.
	 * 
	 * @param files The files to import.
	 */
	private void insertFiles(Map<File, StatusLabel> files)
	{
		resultIndex = ImportStatus.SUCCESS;
		if (files == null || files.size() == 0) return;
		components = new HashMap<File, FileImportComponent>();
		
		Entry<File, StatusLabel> entry;
		Iterator<Entry<File, StatusLabel>> i = files.entrySet().iterator();
		FileImportComponent c;
		File f;
		DatasetData d = dataset;
		Object node = refNode;
		if (importable.isFolderAsContainer()) {
			node = null;
			d = new DatasetData();
			d.setName(getFile().getName());
		}
		ImportableFile copy;
		while (i.hasNext()) {
			entry = i.next();
			f = entry.getKey();
			copy = importable.copy();
			copy.setFile(f);
			c = new FileImportComponent(copy, browsable, singleGroup,
					getIndex(), tags);
			if (f.isFile()) {
				c.setLocation(data, d, node);
				c.setParent(this);
			}
			c.setType(getType());
			attachListeners(c);
			c.setStatusLabel(entry.getValue());
			entry.getValue().addPropertyChangeListener(this);
			components.put((File) entry.getKey(), c);
		}
		
		removeAll();
		pane = EditorUtil.createTaskPane(getFile().getName());
		pane.setCollapsed(false);

		IconManager icons = IconManager.getInstance();
		pane.setIcon(icons.getIcon(IconManager.DIRECTORY));
		Font font = pane.getFont();
		pane.setFont(font.deriveFont(font.getStyle(), font.getSize()-2));
		layoutEntries(false);
		double[][] size = {{TableLayout.FILL}, {TableLayout.PREFERRED}};
		setLayout(new TableLayout(size));
		add(pane, new TableLayoutConstraints(0, 0));
		validate();
		repaint();
	}
	
	/**
	 * Creates a new instance.
	 * 
	 * @param importable The component hosting information about the file.
	 * @param browsable Flag indicating that the container can be browsed or not.
	 * @param singleGroup Passes <code>true</code> if the user is member of
	 * only one group, <code>false</code> otherwise.
	 * @param index The index of the parent.
	 * @param tags The tags that will be linked to the objects.
	 */
	public FileImportComponent(ImportableFile importable, boolean
			browsable, boolean singleGroup, int index,
			Collection<TagAnnotationData> tags)
	{
		if (importable == null)
			throw new IllegalArgumentException("No file specified.");
		if (importable.getGroup() == null)
			throw new IllegalArgumentException("No group specified.");
		this.index = index;
		this.tags = tags;
		this.importable = importable;
		this.singleGroup = singleGroup;
		this.browsable = browsable;
		resultIndex = ImportStatus.QUEUED;
		initComponents();
		buildGUI();
		setLocation(importable.getParent(), importable.getDataset(),
				importable.getRefNode());
	}
	
	/**
	 * Returns the file hosted by this component.
	 * 
	 * @return See above.
	 */
	public File getFile() { return importable.getFile(); }
	
	/**
	 * Sets the location where to import the files.
	 * 
	 * @param data The data where to import the folder or screening data.
	 * @param dataset The dataset if any.
	 * @param refNode The node of reference.
	 */
	public void setLocation(DataObject data, DatasetData dataset, 
			Object refNode)
	{
		this.data = data;
		this.dataset = dataset;
		this.refNode = refNode;
		if (refNode != null && refNode instanceof TreeImageDisplay) {
			TreeImageDisplay n = (TreeImageDisplay) refNode;
			Object ho = n.getUserObject();
			if (ho instanceof DatasetData || ho instanceof ProjectData ||
				ho instanceof ScreenData) {
				containerObject = (DataObject) ho;
			}
			return;
		}
		if (dataset != null) {
			containerObject = dataset;
			return;
		}
		if (data != null && data instanceof ScreenData) {
			containerObject = data;
		}
	}
	
	/**
	 * Sets the log file annotation.
	 * 
	 * @param data The annotation associated to the file set.
	 * @param id The id of the file set.
	 */
	public void setImportLogFile(Collection<FileAnnotationData> data, long id)
	{
	}

	/**
	 * Returns the dataset or <code>null</code>.
	 * 
	 * @return See above.
	 */
	public DatasetData getDataset() { return dataset; }
	
	/**
	 * Returns the object or <code>null</code>.
	 * 
	 * @return See above.
	 */
	public DataObject getDataObject() { return data; }
	
	/**
	 * Replaces the initial status label.
	 * 
	 * @param label The value to replace.
	 */
	void setStatusLabel(StatusLabel label)
	{
		statusLabel = label;
		statusLabel.addPropertyChangeListener(this);
		buildGUI();
		revalidate();
		repaint();
	}

	/** 
	 * Sets the parent of the component.
	 * 
	 * @param parent The value to set.
	 */
	void setParent(FileImportComponent parent)
	{
		this.parent = parent;
	}
	
	/**
	 * Returns <code>true</code> if the parent is set.
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasParent() { return parent != null; }
	
	/**
	 * Returns the components displaying the status of an on-going import.
	 * 
	 * @return See above.
	 */
	public StatusLabel getStatus() { return statusLabel; }
	
	/**
	 * Sets the result of the import.
	 * @param image The image.
	 */
	public void setStatus(Object image)
	{
		busyLabel.setVisible(false);
		busyLabel.setBusy(false);
		cancelButton.setVisible(false);
		this.image = image;
		if (image instanceof PlateData) {
			menu = null;
			imageLabel.setData((PlateData) image);
			fileNameLabel.addMouseListener(adapter);
			formatResultTooltip();
		} else if (image instanceof Set) {
			//Result from the import itself
			this.image = null;
			formatResult();
		} else if (image instanceof List) {
			List<ThumbnailData> list = new ArrayList<ThumbnailData>((List) image);
			int m = list.size();
			imageLabel.setData(list.get(0));
			list.remove(0);
			if (list.size() > 0) {
				ThumbnailLabel label = imageLabels.get(0);
				label.setVisible(true);
				label.setData(list.get(0));
				list.remove(0);
				if (list.size() > 0) {
					label = imageLabels.get(1);
					label.setVisible(true);
					label.setData(list.get(0));
					list.remove(0);
					int n = statusLabel.getNumberOfImportedFiles()-m;
					if (n > 0) {
						label = imageLabels.get(2);
						label.setVisible(true);
						StringBuffer buf = new StringBuffer("... ");
						buf.append(n);
						buf.append(" more");
						label.setText(buf.toString());
					}
				}
			}
		} else if (image instanceof ImportException) {
			if (getFile().isDirectory()) {
				this.image = null;
				statusLabel.setText(EMPTY_FOLDER);
			} else formatResult();
		} else if (image instanceof Boolean) {
			busyLabel.setBusy(false);
			busyLabel.setVisible(false);
			cancelButton.setVisible(false);
			if (statusLabel.isMarkedAsCancel() ||
					statusLabel.isMarkedAsDuplicate()) {
				resultIndex = ImportStatus.IGNORED;
				this.image = null;
			}
		}
		revalidate();
		repaint();
	}

	/**
	 * Returns the files that failed to import.
	 * 
	 * @return See above.
	 */
	public List<FileImportComponent> getImportErrors()
	{
		List<FileImportComponent> l = null;
		if (getFile().isFile()) {
			Object r = statusLabel.getImportResult();
			if (r instanceof Exception || image instanceof Exception) {
				l = new ArrayList<FileImportComponent>();
				l.add(this);
				return l;
			}
		} else {
			if (components != null) {
				Iterator<FileImportComponent> i = components.values().iterator();
				FileImportComponent fc;
				l = new ArrayList<FileImportComponent>();
				List<FileImportComponent> list;
				while (i.hasNext()) {
					fc = i.next();
					list = fc.getImportErrors();
					if (!CollectionUtils.isEmpty(list))
						l.addAll(list);
				}
			}
		}
		return l;
	}
	
	/**
	 * Returns the id of the group.
	 * 
	 * @return See above.
	 */
	public long getGroupID() { return importable.getGroup().getId(); }
	
	/**
	 * Returns the import error object.
	 * 
	 * @return See above.
	 */
	public ImportErrorObject getImportErrorObject()
	{
		Object r = statusLabel.getImportResult();
		Exception e = null;
		if (r instanceof Exception) e = (Exception) r;
		else if (image instanceof Exception) e = (Exception) image;
		if (e == null) return null;
		ImportErrorObject object = new ImportErrorObject(getFile(), e,
				getGroupID());
		object.setReaderType(statusLabel.getReaderType());
		object.setUsedFiles(statusLabel.getUsedFiles());
		long id = statusLabel.getLogFileID();
		if (id <= 0) {
			FilesetData data = statusLabel.getFileset();
			if (data != null) {
				id = data.getId();
				object.setRetrieveFromAnnotation(true);
			}
		}
		object.setLogFileID(id);
		return object;
	}
	
	/**
	 * Returns <code>true</code> if the import has failed, <code>false</code>
	 * otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasImportFailed()
	{
		return resultIndex == ImportStatus.FAILURE ||
				resultIndex == ImportStatus.UPLOAD_FAILURE;
	}
	
	/**
	 * Returns <code>true</code> if it was a failure prior or during the
	 * upload, <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasUploadFailed()
	{
		return resultIndex == ImportStatus.UPLOAD_FAILURE ||
				(resultIndex == ImportStatus.FAILURE &&
				!statusLabel.didUploadStart());
	}
	
	/**
	 * Returns <code>true</code> if the import has been cancelled,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean isCancelled()
	{
		boolean b = statusLabel.isMarkedAsCancel();
		if (b || getFile().isFile()) return b;
		if (components == null) return false;
		Iterator<FileImportComponent> i = components.values().iterator();
		while (i.hasNext()) {
			if (i.next().isCancelled())
				return true;
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if the file can be re-imported,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasFailuresToReimport()
	{
		if (getFile().isFile()) return hasUploadFailed() && !reimported;
		if (components == null) return false;
		Iterator<FileImportComponent> i = components.values().iterator();
		while (i.hasNext()) {
			if (i.next().hasUploadFailed())
				return true;
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if the file can be re-imported,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasFailuresToReupload()
	{
		if (getFile().isFile()) return hasUploadFailed() && !reimported;
		if (components == null) return false;
		Iterator<FileImportComponent> i = components.values().iterator();
		while (i.hasNext()) {
			if (i.next().hasFailuresToReupload())
				return true;
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if the import has failed, <code>false</code>
	 * otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasImportStarted()
	{
		if (getFile().isFile()) return resultIndex != ImportStatus.QUEUED;
		if (components == null) return false;
		Iterator<FileImportComponent> i = components.values().iterator();
		int count = 0;
		while (i.hasNext()) {
			if (i.next().hasImportStarted()) count++;
		}
		return count == components.size();
	}
	
	/**
	 * Returns <code>true</code> the error can be submitted, <code>false</code>
	 * otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasFailuresToSend()
	{
		if (getFile().isFile()) return resultIndex == ImportStatus.FAILURE;
		if (components == null) return false;
		Iterator<FileImportComponent> i = components.values().iterator();
		while (i.hasNext()) {
			if (i.next().hasFailuresToSend())
				return true;
		}
		return false;
	}
	
	/**
	 * Returns <code>true</code> if the folder has components added,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasComponents()
	{
		return components != null && components.size() > 0;
	}
	
	/** 
	 * Lays out the entries.
	 * 
	 * @param failure Pass <code>true</code> to display the failed import only,
	 * <code>false</code> to display all the entries.
	 */
	public void layoutEntries(boolean failure)
	{
		JPanel p = new JPanel();
		p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
		p.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		if (!hasComponents()) return;
		Entry<File, FileImportComponent> e;
		Iterator<Entry<File, FileImportComponent>> i =
				components.entrySet().iterator();
		int index = 0;
		FileImportComponent fc;
		if (failure) {
			while (i.hasNext()) {
				e = i.next();
				fc = e.getValue();
				if (fc.hasImportFailed()) {
					if (index%2 == 0)
						fc.setBackground(UIUtilities.BACKGROUND_COLOUR_EVEN);
					else 
						fc.setBackground(UIUtilities.BACKGROUND_COLOUR_ODD);
					p.add(fc);
					index++;
				}
			}
		} else {
			while (i.hasNext()) {
				e = i.next();
				fc = e.getValue();
				if (index%2 == 0)
					fc.setBackground(UIUtilities.BACKGROUND_COLOUR_EVEN);
				else 
					fc.setBackground(UIUtilities.BACKGROUND_COLOUR_ODD);
				p.add(fc);
				index++;
			}
		}
		
		pane.removeAll();
		pane.add(p);
		pane.revalidate();
		pane.repaint();
	}
	
	/**
	 * Returns the status of the import process one of the
	 * values defined in @see ImportStatus
	 * 
	 * @return See above.
	 */
	public ImportStatus getImportStatus()
	{
		if (getFile().isFile()) {
			if (hasImportFailed()) return ImportStatus.FAILURE;
			return resultIndex;
		}
		if (components == null || components.size() == 0) {
			if (image instanceof Boolean) {
				if (getFile().isDirectory()) {
					return ImportStatus.SUCCESS;
				} else {
					if (!statusLabel.isMarkedAsCancel() &&
						!statusLabel.isMarkedAsDuplicate())
						return ImportStatus.FAILURE;
				}
			}
			return resultIndex;
		}
			
		Iterator<FileImportComponent> i = components.values().iterator();
		int n = components.size();
		int count = 0;
		while (i.hasNext()) {
			if (i.next().hasImportFailed())
				count++;
		}
		if (count == n) return ImportStatus.FAILURE;
		if (count > 0) return ImportStatus.PARTIAL;
		return ImportStatus.SUCCESS;
	}
	
	/**
	 * Returns <code>true</code> if refresh whole tree, <code>false</code>
	 * otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasToRefreshTree()
	{
		if (getFile().isFile()) {
			if (hasImportFailed()) return false;
			switch (type) {
				case PROJECT_TYPE:
				case NO_CONTAINER:
					return true;
				default:
					return false;
			}
		}
		if (components == null) return false;
		if (importable.isFolderAsContainer() && type != PROJECT_TYPE) {
			Iterator<FileImportComponent> i = components.values().iterator();
			while (i.hasNext()) {
				if (i.next().toRefresh()) 
					return true;
			}
			return false;
		}
		return true;
	}
	
	/**
	 * Returns <code>true</code> if some files were imported, 
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean toRefresh()
	{
		/*
		if (file.isFile()) {
			if (deleteButton.isVisible()) return false;
			else if (errorBox.isVisible())
				return !(errorBox.isEnabled() && errorBox.isSelected());
			return true;
		}
		if (components == null) return false;
		Iterator<FileImportComponent> i = components.values().iterator();
		int count = 0;
		while (i.hasNext()) {
			if (i.next().hasFailuresToSend()) 
				count++;
		}
		return components.size() != count;
		*/
		return true;
	}

	/** Indicates the import has been cancelled. */
	public void cancelLoading()
	{
		if (components == null || components.isEmpty()) {
			cancel(getFile().isFile());
			return;
		}
		Iterator<FileImportComponent> i = components.values().iterator();
		while (i.hasNext()) {
			i.next().cancelLoading();
		}
	}
	
	/**
	 * Sets the type. 
	 * 
	 * @param type One of the constants defined by this class.
	 */
	public void setType(int type) { this.type = type; }
	
	/**
	 * Returns the supported type. One of the constants defined by this class.
	 * 
	 * @return See above.
	 */
	public int getType() { return type; }
	
	/**
	 * Returns <code>true</code> if the folder has been converted into a
	 * container, <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean isFolderAsContainer()
	{
		return importable.isFolderAsContainer();
	}
	
	/**
	 * Returns the object corresponding to the folder.
	 * 
	 * @return See above.
	 */
	public DataObject getContainerFromFolder() { return containerFromFolder; }
	
	/**
	 * Returns <code>true</code> if the extension of the specified file
	 * is a HCS files, <code>false</code> otherwise.
	 * 
	 * @param f The file to handle.
	 * @return See above.
	 */
	public boolean isHCSFile()
	{
		if (isFolderAsContainer()) return false;
		return ImportableObject.isHCSFile(getFile());
	}

	/**
	 * Returns <code>true</code> if the file has already been marked for
	 * re-import, <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public List<FileImportComponent> getFilesToReupload()
	{
		List<FileImportComponent> l = null;
		if (getFile().isFile()) {
			if (hasFailuresToReupload() && !reimported) {
				return Arrays.asList(this);
			}
		} else {
			if (components != null) {
				Iterator<FileImportComponent> i = components.values().iterator();
				FileImportComponent fc;
				l = new ArrayList<FileImportComponent>();
				List<FileImportComponent> list;
				while (i.hasNext()) {
					fc = i.next();
					list = fc.getFilesToReupload();
					if (!CollectionUtils.isEmpty(list))
						l.addAll(list);
				}
			}
		}
		return l;
	}
	
	/**
	 * Sets to <code>true</code> to mark the file for reimport.
	 * <code>false</code> otherwise.
	 * 
	 * @param Pass <code>true</code> to mark the file for reimport.
	 * <code>false</code> otherwise.
	 */
	public void setReimported(boolean reimported)
	{ 
		this.reimported = reimported;
		repaint();
	}
	
	/**
	 * Sets the result of the import for the specified file.
	 * 
	 * @param result The result.
	 */
	public void uploadComplete(Object result)
	{
		if (result instanceof CmdCallback)
			callback = (CmdCallback) result;
	}

	/**
	 * Returns the index associated to the main component.
	 * 
	 * @return See above.
	 */
	public int getIndex() { return index; }
	
	/**
	 * Returns the result of the import either a collection of
	 * <code>PixelsData</code> or an exception.
	 * 
	 * @return See above.
	 */
	public Object getImportResult() { return statusLabel.getImportResult(); }
	
	/**
	 * Returns <code>true</code> if it is a HCS file, <code>false</code>
	 * otherwise.
	 * 
	 * @return See above.
	 */
	public boolean isHCS() { return statusLabel.isHCS(); }
	
	/**
	 * Returns the size of the upload.
	 * 
	 * @return See above.
	 */
	public long getImportSize() { return statusLabel.getFileSize(); }
	
	/**
	 * Returns <code>true</code> if the result has already been set,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	public boolean hasResult() { return image != null; }
	
	/**
	 * Returns the importable object associated to the parent,
	 * <code>null</code> if no parent.
	 * 
	 * @return See above.
	 */
	public ImportableFile getImportableFile() { return importable; }
	/**
	 * Overridden to make sure that all the components have the correct 
	 * background.
	 * @see JPanel#setBackground(Color)
	 */
	public void setBackground(Color color)
	{
		if (busyLabel != null) busyLabel.setBackground(color);
		if (namePane != null) {
			namePane.setBackground(color);
			for (int i = 0; i < namePane.getComponentCount(); i++) 
				namePane.getComponent(i).setBackground(color);
		}
		super.setBackground(color);
	}

	/**
	 * Listens to property fired by the <code>StatusLabel</code>.
	 * @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent evt)
	{
		String name = evt.getPropertyName();
		if (StatusLabel.FILES_SET_PROPERTY.equals(name)) {
			if (isCancelled()) {
				busyLabel.setBusy(false);
				busyLabel.setVisible(false);
				return;
			}
			Map<File, StatusLabel> files = (Map<File, StatusLabel>)
				evt.getNewValue();
			int n = files.size();
			insertFiles(files);
			firePropertyChange(IMPORT_FILES_NUMBER_PROPERTY, null,n);
		} else if (StatusLabel.FILE_IMPORT_STARTED_PROPERTY.equals(name)) {
			resultIndex = ImportStatus.STARTED;
			StatusLabel sl = (StatusLabel) evt.getNewValue();
			if (sl.equals(statusLabel) && busyLabel != null) {
				cancelButton.setEnabled(sl.isCancellable());
				firePropertyChange(StatusLabel.FILE_IMPORT_STARTED_PROPERTY,
				        null, this);
			}
		} else if (StatusLabel.UPLOAD_DONE_PROPERTY.equals(name)) {
			StatusLabel sl = (StatusLabel) evt.getNewValue();
			if (sl.equals(statusLabel) && hasParent()) {
				if (sl.isMarkedAsCancel()) cancel(true);
				else {
					formatResult();
					firePropertyChange(StatusLabel.UPLOAD_DONE_PROPERTY, null,
							this);
				}
			}
		} else if (StatusLabel.CANCELLABLE_IMPORT_PROPERTY.equals(name)) {
			StatusLabel sl = (StatusLabel) evt.getNewValue();
			if (sl.equals(statusLabel))
				cancelButton.setVisible(sl.isCancellable());
		} else if (StatusLabel.SCANNING_PROPERTY.equals(name)) {
			StatusLabel sl = (StatusLabel) evt.getNewValue();
			if (sl.equals(statusLabel)) {
				if (busyLabel != null && !isCancelled()) {
					busyLabel.setBusy(true);
					busyLabel.setVisible(true);
				}
			}
		} else if (StatusLabel.FILE_RESET_PROPERTY.equals(name)) {
			importable.setFile((File) evt.getNewValue());
			fileNameLabel.setText(getFile().getName());
		} else if (ThumbnailLabel.BROWSE_PLATE_PROPERTY.equals(name)) {
			firePropertyChange(BROWSE_PROPERTY, evt.getOldValue(), 
					evt.getNewValue());
		} else if (StatusLabel.CONTAINER_FROM_FOLDER_PROPERTY.equals(name)) {
			containerFromFolder = (DataObject) evt.getNewValue();
			if (containerFromFolder instanceof DatasetData) {
				containerObject = containerFromFolder;
			} else if (containerFromFolder instanceof ScreenData) {
				containerObject = containerFromFolder;
			}
		} else if (StatusLabel.DEBUG_TEXT_PROPERTY.equals(name)) {
			firePropertyChange(name, evt.getOldValue(), evt.getNewValue());
		} else if (ThumbnailLabel.VIEW_IMAGE_PROPERTY.equals(name)) {
			//use the group
			SecurityContext ctx = new SecurityContext(
					importable.getGroup().getId());
			EventBus bus = ImporterAgent.getRegistry().getEventBus();
			Long id = (Long) evt.getNewValue();
			bus.post(new ViewImage(ctx, new ViewImageObject(id), null));
		} else if (StatusLabel.IMPORT_DONE_PROPERTY.equals(name) ||
				StatusLabel.PROCESSING_ERROR_PROPERTY.equals(name)) {
			StatusLabel sl = (StatusLabel) evt.getNewValue();
			if (sl.equals(statusLabel))
				firePropertyChange(StatusLabel.IMPORT_DONE_PROPERTY, null,
						this);
		}
	}

	/**
	 * Returns the name of the file and group's id and user's id.
	 * @see #toString();
	 */
	public String toString()
	{
		StringBuffer buf = new StringBuffer();
		buf.append(getFile().getAbsolutePath());
		if (importable.getGroup() != null)
			buf.append("_"+importable.getGroup().getId());
		if (importable.getUser() != null)
			buf.append("_"+importable.getUser().getId());
		return buf.toString();
	}

}
