/*
 * org.openmicroscopy.shoola.agents.iviewer.view.ImViewerFactory
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2014 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.imviewer.view;


//Java imports
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.prefs.Preferences;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

//Third-party libraries

//Application-internal dependencies
import org.openmicroscopy.shoola.agents.events.iviewer.SaveRelatedData;
import org.openmicroscopy.shoola.agents.imviewer.ImViewerAgent;
import org.openmicroscopy.shoola.agents.imviewer.actions.ActivateRecentAction;
import org.openmicroscopy.shoola.agents.imviewer.actions.ActivationAction;
import org.openmicroscopy.shoola.env.data.util.SecurityContext;
import org.openmicroscopy.shoola.env.rnd.RndProxyDef;
import org.openmicroscopy.shoola.env.ui.TaskBar;
import pojos.DataObject;
import pojos.ImageData;

/** 
* Factory to create {@link ImViewer} components.
* This class keeps track of all {@link ImViewer} instances that have been
* created and are not yet {@link ImViewer#DISCARDED discarded}. A new
* component is only created if none of the <i>tracked</i> ones is already
* displaying the given hierarchy. Otherwise, the existing component is
* recycled.
*
* @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
* 				<a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author	Andrea Falconi &nbsp;&nbsp;&nbsp;&nbsp;
* 				<a href="mailto:a.falconi@dundee.ac.uk">a.falconi@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
* <small>
* (<b>Internal version:</b> $Revision: $ $Date: $)
* </small>
* @since OME2.2
*/
public class ImViewerFactory
  	implements ChangeListener, PropertyChangeListener
{

	/** 
	 * The name of the property, temporary solution before we have preferences.
	 */
	private static final String	OMERO_VIEWER_COMPRESSION = 
		"omeroViewerCompression";

	/** The name of the windows menu. */
	private static final String MENU_NAME = "Image Viewer";
	
	/** The name of the recent menu. */
	private static final String RECENT_MENU = "Open Recent";
	
	/** The name of the recent menu. */
	private static final String CLEAR_MENU = "Clear menu";
	
	/** The maximum number of recent items. */
	private static final int	MAX_RECENT = 10;
	
	/** The sole instance. */
	private static final ImViewerFactory  singleton = new ImViewerFactory();

	/** 
	 * Adds all the {@link ImViewer} components that this factory is
	 * currently tracking to the passed menu.
	 * 
	 * @param menu The menu to add the components to. 
	 */
	static void register(JMenu menu)
	{ 
		//return singleton.viewers; 
		if (menu == null) return;
		Iterator<ImViewer> i = singleton.viewers.iterator();
		menu.removeAll();
		while (i.hasNext()) 
			menu.add(new JMenuItem(new ActivationAction(i.next())));
		
		int n = singleton.recentViewers.size();
		if (n > 0) {
			Iterator<ImViewerRecentObject> 
				j = singleton.recentViewers.iterator();
			singleton.recentMenu.removeAll();
			while (j.hasNext()) {
				singleton.recentMenu.add(new JMenuItem(
						new ActivateRecentAction(j.next())));
			}
			singleton.recentMenu.add(new JSeparator());
			singleton.recentMenu.add(singleton.clearMenu);
			menu.add(singleton.recentMenu);
		}
	}

	/** 
	 * Returns the <code>window</code> menu. 
	 * 
	 * @return See above.
	 */
	static JMenu getWindowMenu() { return singleton.windowMenu; }

	/** Attaches the {@link #windowMenu} to the <code>TaskBar</code>. */
	static void attachWindowMenuToTaskBar()
	{
		if (singleton.isAttached) return;
		TaskBar tb = ImViewerAgent.getRegistry().getTaskBar();
		tb.addToMenu(TaskBar.WINDOW_MENU, singleton.windowMenu);
		singleton.isAttached = true;
	}

	/**
	 * Notifies the model that the user's group has successfully be modified
	 * if the passed value is <code>true</code>, unsuccessfully 
	 * if <code>false</code>.
	 * 
	 * @param success 	Pass <code>true</code> if successful, <code>false</code>
	 * 					otherwise.
	 */
	public static void onGroupSwitched(boolean success)
	{
		if (!success) return;
		singleton.clear();
	}
	
	/**
	 * Returns a viewer to display the image corresponding to the specified id.
	 * 
	 * @param ctx The security context.
	 * @param image  	The image or wellSample to view.
	 * @param bounds    The bounds of the component invoking the 
	 *                  {@link ImViewer}.
	 * @param separateWindow Pass <code>true</code> to open the viewer in a 
	 * 						 separate window, <code>false</code> otherwise.
	 * @return See above.
	 */
	public static ImViewer getImageViewer(SecurityContext ctx,
			DataObject image, Rectangle bounds, 
			boolean separateWindow)
	{
		ImViewerModel model = new ImViewerModel(ctx, image, bounds,
				separateWindow);
		return singleton.getViewer(model);
	}

	/**
	 * Returns a viewer to display the image corresponding to the specified id.
	 * 
	 * @param imageID  	The image to view.
	 * @param bounds    The bounds of the component invoking the 
	 *                  {@link ImViewer}.
	 * @param separateWindow Pass <code>true</code> to open the viewer in a 
	 * 						 separate window, <code>false</code> otherwise. 
	 * @return See above.
	 */
	public static ImViewer getImageViewer(SecurityContext ctx,
		long imageID, Rectangle bounds, boolean separateWindow)
	{
		ImViewerModel model = new ImViewerModel(ctx, imageID, bounds, 
				separateWindow);
		return singleton.getViewer(model);
	}
	
	/**
	 * Returns the viewer if any, identified by the passed pixels ID.
	 * 
	 * @param pixelsID The Identifier of the pixels set.
	 * @return See above.
	 */
	public static ImViewer getImageViewer(SecurityContext ctx, long pixelsID)
	{
		Iterator<ImViewer> v = singleton.viewers.iterator();
		ImViewerComponent comp;
		while (v.hasNext()) {
			comp = (ImViewerComponent) v.next();
			if (comp.getModel().isSame(pixelsID, ctx)) return comp;
		}
		return null;
	}

	/**
	 * Returns the viewer if any, identified by the passed image's ID.
	 * 
	 * @param imageID The Identifier of the image.
	 * @return See above.
	 */
	public static ImViewer getImageViewerFromImage(SecurityContext ctx,
			long imageID)
	{
		Iterator<ImViewer> v = singleton.viewers.iterator();
		ImViewerComponent comp;
		while (v.hasNext()) {
			comp = (ImViewerComponent) v.next();
			if (comp.getModel().isSameImage(imageID, ctx))
				return comp;
		}
		return null;
	}
	
	/**
	 * Returns the viewer if any, identified by the passed pixels ID.
	 * 
	 * @param parent The of the image.
	 * @return See above.
	 */
	public static ImViewer getImageViewerFromParent(DataObject parent)
	{
		if (parent == null) return null;
		Iterator<ImViewer> v = singleton.viewers.iterator();
		ImViewerComponent comp;
		while (v.hasNext()) {
			comp = (ImViewerComponent) v.next();
			if (comp.getModel().isSameParent(parent))  return comp;
		}
		return null;
	}
		
	/**
	 * Copies the rendering settings.
	 * 
	 * @param image	The image to copy the rendering settings from.
	 */
	public static void copyRndSettings(ImageData image)
	{
		singleton.refImage = image;
		Iterator<ImViewer> v = singleton.viewers.iterator();
		ImViewerComponent comp;
		while (v.hasNext()) {
			comp = (ImViewerComponent) v.next();
			if (comp.getModel().getImageID() != image.getId()) 
				comp.copyRndSettings();
		}
	}
	
	/**
	 * Indicates that rendering settings has been saved using another way.
	 * 
	 * @param pixelsID The Identifier of the pixels set.
	 * @param settings The rendering settings saved.
	 */
	public static void rndSettingsSaved(long pixelsID, RndProxyDef settings)
	{
		Iterator<ImViewer> v = singleton.viewers.iterator();
		ImViewerComponent comp;
		while (v.hasNext()) {
			comp = (ImViewerComponent) v.next();
			if (comp.getModel().getPixelsID() == pixelsID) 
				comp.onRndSettingsSaved(settings);
			
			comp.reloadRenderingThumbs();
		}
	}

	/**
	 * Stores the passed event in the correct viewer.
	 * 
	 * @param evt The event to store.
	 */
	public static void storeEvent(SaveRelatedData evt)
	{
		Iterator<ImViewer> v = singleton.viewers.iterator();
		ImViewerComponent comp;
		long pixelsID = evt.getPixelsID();
		while (v.hasNext()) {
			comp = (ImViewerComponent) v.next();
			if (comp.getModel().getPixelsID() == pixelsID) 
				comp.storeEvent(evt);
		}
	}

	/**
	 * Returns the instances to save.
	 * 
	 * @return See above.
	 */
	public static List<Object> getInstancesToSave()
	{
		if (singleton.viewers.size() == 0) return null;
		List<Object> instances = new ArrayList<Object>();
		Iterator<ImViewer> i = singleton.viewers.iterator();
		ImViewerComponent comp;
		while (i.hasNext()) {
			comp = (ImViewerComponent) i.next();
			if (comp.hasSettingsToSave())
				instances.add(comp);
		}
		return instances;
	}
	
	/** 
	 * Saves the passed instances and discards them. 
	 * 
	 * @param instances The instances to save.
	 */
	public static void saveInstances(List<Object> instances)
	{
		//if (singleton.viewers.size() == 0) return;
		if (instances != null) {
			Iterator<Object> i = instances.iterator();
			ImViewerComponent comp;
			Object o;
			List<Long> ids = new ArrayList<Long>();
			while (i.hasNext()) {
				o = i.next();
				if (o instanceof ImViewerComponent) {
					comp = (ImViewerComponent) o;
					comp.close(false);
					singleton.viewers.remove(comp);
					ids.add(comp.getModel().getImageID());
				}
			}
			removeRecentViewers(ids);
		}
	}
	
	/**
	 * Sets the display mode.
	 * 
	 * @param displayMode The value to set.
	 */
	public static void setDisplayMode(int displayMode)
	{
		Iterator<ImViewer> i = singleton.viewers.iterator();
		ImViewerComponent comp;
		while (i.hasNext()) {
			comp = (ImViewerComponent) i.next();
			comp.setDisplayMode(displayMode);
		}
	}
	/** 
	 * Returns the id of the pixels set to copy the rendering settings.
	 * 
	 * @return See above.
	 */
	static ImageData getRefImage() { return singleton.refImage; }
	
	/**
	 * Returns the user preferences.
	 * 
	 * @return See above.
	 */
	static ViewerPreferences getPreferences() { return singleton.pref; }

	/**
	 * Sets the preferences.
	 * 
	 * @param pref The value to set.
	 */
	static void setPreferences(ViewerPreferences pref)
	{
		singleton.pref = null;//pref;
	}
	
	/**
	 * Sets the compression level.
	 * 
	 * @param level The value to set.
	 */
	static void setCompressionLevel(int level)
	{
		Preferences p = Preferences.userNodeForPackage(ImViewerFactory.class);
		p.put(OMERO_VIEWER_COMPRESSION, ""+level);
	}
	
	/**
	 * Returns the compression level.
	 * 
	 * @return See above.
	 */
	static int getCompressionLevel()
	{
		Preferences p = Preferences.userNodeForPackage(ImViewerFactory.class);
		String value = p.get(OMERO_VIEWER_COMPRESSION, null);
		if (value != null && value.trim().length() > 0) 
			return Integer.parseInt(value);
		return -1;
	}
	
	/**
	 * Removes the viewers from the list of viewers recently opened.
	 * 
	 * @param ids The identifiers of the viewers.
	 */
	private static void removeRecentViewers(List<Long> ids)
	{
		if (ids == null || ids.size() == 0) return;
		Iterator<ImViewerRecentObject> j = singleton.recentViewers.iterator();
		ImViewerRecentObject recent;
		List<ImViewerRecentObject> 
			toRemove = new ArrayList<ImViewerRecentObject>();
		while (j.hasNext()) {
			recent = j.next();	
			if (ids.contains(recent.getImageID()))
				toRemove.add(recent);
		}
		singleton.recentViewers.removeAll(toRemove);
	}
	
	/** All the tracked components. */
	private Set<ImViewer>     				viewers;

	/** Collection of image recently viewed. */
	private List<ImViewerRecentObject>     	recentViewers;
	
	/** The windows menu. */
	private JMenu   						windowMenu;

	/** The recent windows menu. */
	private JMenu   						recentMenu;
	
	/** The clear menu items. */
	private JMenuItem   					clearMenu;
	
	/** 
	 * Indicates if the {@link #windowMenu} is attached to the 
	 * <code>TaskBar</code>.
	 */
	private boolean 						isAttached;

	/** The image data to copy the rendering settings from. */
	private ImageData						refImage;

	/** The user preferences for the viewer. */
	private ViewerPreferences				pref;
	
	/** Creates a new instance. */
	private ImViewerFactory()
	{
		viewers = new HashSet<ImViewer>();
		recentViewers = new ArrayList<ImViewerRecentObject>();
		isAttached = false;
		windowMenu = new JMenu(MENU_NAME);
		recentMenu = new JMenu(RECENT_MENU);
		clearMenu = new JMenuItem(CLEAR_MENU);
		clearMenu.addActionListener(new ActionListener() {
		
			public void actionPerformed(ActionEvent e) {
				
				singleton.recentViewers.clear();
			}
		});
	}

	/** Clears the collection of tracked viewers. */
	private void clear()
	{
		Iterator<ImViewer> i = viewers.iterator();
		ImViewerComponent comp;
		while (i.hasNext()) {
			comp = (ImViewerComponent) i.next();
			comp.removeChangeListener(this);
			comp.discard();
		}
		singleton.viewers.clear();
		singleton.recentViewers.clear();
		handleViewerDiscarded();
	}
	
	/**
	 * Checks the list of opened viewers before removing the entry from the
	 * menu.
	 */
	private void handleViewerDiscarded()
	{
		if (!singleton.isAttached) return;
		if (singleton.viewers.size() != 0) return;
		TaskBar tb = ImViewerAgent.getRegistry().getTaskBar();
		tb.removeFromMenu(TaskBar.WINDOW_MENU, singleton.windowMenu);
		singleton.isAttached = false;
	}

	/**
	 * Creates or recycles a viewer component for the specified 
	 * <code>model</code>.
	 * 
	 * @param model The component's Model.
	 * @return A {@link ImViewer} for the specified <code>model</code>.
	 */
	private ImViewer getViewer(ImViewerModel model)
	{
		Iterator<ImViewer> v = viewers.iterator();
		ImViewerComponent comp;
		while (v.hasNext()) {
			comp = (ImViewerComponent) v.next();
			if (comp.getModel().getImageID() == model.getImageID())
				return comp;
		}
		comp = new ImViewerComponent(model);
		comp.initialize();
		comp.addChangeListener(this);
		comp.addPropertyChangeListener(this);
		viewers.add(comp);
		//
		long id = model.getImageID();
		if (id < 0) return null;
		Iterator<ImViewerRecentObject> j = recentViewers.iterator();
		ImViewerRecentObject obj;
		ImViewerRecentObject toRemove = null;
		while (j.hasNext()) {
			obj = j.next();
			if (obj.getImageID() == id) toRemove = obj;
		}
		if (toRemove != null) recentViewers.remove(toRemove);
		return comp;
	}
	
	/**
	 * Removes a viewer from the {@link #viewers} set when it is
	 * {@link ImViewer#DISCARDED discarded}. 
	 * @see ChangeListener#stateChanged(ChangeEvent)
	 */
	public void stateChanged(ChangeEvent ce)
	{
		ImViewerComponent comp = (ImViewerComponent) ce.getSource();
		switch (comp.getState()) {
			case ImViewer.DISCARDED:
			case ImViewer.CANCELLED:
				viewers.remove(comp);
				removeRecentViewers(
						Arrays.asList(comp.getModel().getImageID()));
				handleViewerDiscarded();
				break;
			default:
		}
	}

	/**
	 * Listens to the {@link ImViewer#RECENT_VIEWER_PROPERTY}
	 * @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent evt)
	{
		String name = evt.getPropertyName();
		if (ImViewer.RECENT_VIEWER_PROPERTY.equals(name)) {
			Iterator<ImViewerRecentObject> i = recentViewers.iterator();
			ImViewerRecentObject v = (ImViewerRecentObject) evt.getNewValue();
			ImViewerRecentObject old;
			ImViewerRecentObject exist = null;
			while (i.hasNext()) {
				old = i.next();
				if (old.getImageID() == v.getImageID()) {
					exist = old;
					break;
				}
			}
			if (exist != null) recentViewers.remove(exist);
			if (recentViewers.size() >= MAX_RECENT)
				recentViewers.remove(0);
			recentViewers.add(v);
			handleViewerDiscarded();
		}
	}
	
}
