/*
 * org.openmicroscopy.shoola.agents.util.ui.MovieExportDialog 
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2009 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.util.ui;


//Java imports
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
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.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;


//Third-party libraries
import info.clearthought.layout.TableLayout;

//Application-internal dependencies
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import org.openmicroscopy.shoola.env.data.model.MovieExportParam;
import org.openmicroscopy.shoola.util.ui.ColorListRenderer;
import org.openmicroscopy.shoola.util.ui.IconManager;
import org.openmicroscopy.shoola.util.ui.NumericalTextField;
import org.openmicroscopy.shoola.util.ui.TitlePanel;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.slider.TextualTwoKnobsSlider;

import pojos.ChannelData;

/** 
 * Modal dialog displaying option to export the rendered image. 
 *
 * @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
 * <small>
 * (<b>Internal version:</b> $Revision: $Date: $)
 * </small>
 * @since 3.0-Beta4
 */
public class MovieExportDialog 
	extends JDialog
	implements ActionListener, DocumentListener
{

	/** Bound property indicating to create a movie. */
	public static final String 		CREATE_MOVIE_PROPERTY = "createMovie";
	
	/** Action id indicating to close the dialog. */
	public static final int 		CLOSE = 0;
	
	/** Action id indicating to create a movie. */
	public static final int 		SAVE = 1;
	
	/** Action id indicating to allow the modification of the scale bar. */
	private static final int 		SCALE_BAR = 2;
	
	/** Action id indicating to allow the modification of the scale bar. */
	private static final int 		Z_INTERVAL = 3;
	
	/** Action id indicating to allow the modification of the scale bar. */
	private static final int 		T_INTERVAL = 4;
	
	/** The title of the dialog. */
	private static final String		TITLE = "Movie Creation";
	
	/** The default value for the scale bar. */
	private static final int		DEFAULT_SCALE = 5;
	
	/** Button to close the dialog. */
	private JButton					closeButton;
	
	/** Button to close the dialog. */
	private JButton					saveButton;
	
	/** The name of the file. */
	private JTextField				nameField;
	
	/** The supported movie formats. */
	private JComboBox				formats;
	
	/** The supported value of the scale bar. */
	private NumericalTextField		scaleBar;
	
	/** Add a scale bar if selected. */
	private JCheckBox				showScaleBar;
	
	/** Creates a movie across time. */
	private JCheckBox				timeInterval;
	
	/** Creates a movie across z-section. */
	private JCheckBox				zInterval;
	
	/** Displays or not the real time. */
	private JCheckBox				labelVisible;
	
	/** The selected color for scale bar. */
	private JComboBox				colorBox;
	
	/** To specify the movie play-back rate in frames per second. */
	private JSpinner            	fps;
	
	/** Component to select the time interval. */
	private TextualTwoKnobsSlider	timeRange;
	
	/** Component to select the z-section interval. */
	private TextualTwoKnobsSlider	zRange;
	
	/** Option chosen by the user. */
	private int						option;
	
	/** The parameters to set. */
	private MovieExportParam 		param;
	
	/** Component used to set the default z-section. */
	private JSpinner            	zSpinner;
	
	/** Component used to set the default z-section. */
	private JSpinner            	tSpinner;
	
	/** The number of z-sections. */
	private int						maxZ;
	
	/** The number of time-points. */
	private int						maxT;
	
	/** The collection of channels.*/
	private Map<Object, JComponent>	buttons;
	
	/** 
	 * Creates the components composing the display. 
	 * 
	 * @param name The default name of the file.
	 * @param defaultZ The default z-section.
	 * @param defaultT The default time-point.
	 * @param channels The collection of channels.
	 */
	private void initComponents(String name, int defaultZ, int defaultT, Object
			channels)
	{
		buttons = new LinkedHashMap<Object, JComponent>();
		
		if (channels instanceof Map) {
			Entry entry;
			Iterator i = ((Map) channels).entrySet().iterator();
			ChannelData data;
			ChannelButton button;
			while (i.hasNext()) {
				entry = (Entry) i.next();
				data = (ChannelData) entry.getKey();
				button = new ChannelButton(data.getChannelLabeling(), 
						(Color) entry.getValue(), data.getIndex());
				button.setSelected(true);
				button.addPropertyChangeListener(new PropertyChangeListener() {
					
					public void propertyChange(PropertyChangeEvent evt) {
						String n = evt.getPropertyName();
						if (ChannelButton.CHANNEL_SELECTED_PROPERTY.equals(n)) {
							Map m = (Map) evt.getNewValue();
							ChannelButton button = 
								(ChannelButton) evt.getSource();
							Boolean b = (Boolean) m.get(
									button.getChannelIndex());
							if (b != null) 
								button.setSelected(b.booleanValue());
						}
					}
				});
				buttons.put(data, button);
			}
		} else {
			JCheckBox box;
			int maxC = (Integer) channels;
			for (int i = 0; i < maxC; i++) {
				box = new JCheckBox(""+i);
				box.setSelected(true);
				buttons.put(i, box);
			}
		}
		closeButton = new JButton("Cancel");
		closeButton.setToolTipText(UIUtilities.formatToolTipText(
				"Close the window."));
		closeButton.setActionCommand(""+CLOSE);
		closeButton.addActionListener(this);
		saveButton = new JButton("Create");
		saveButton.setEnabled(false);
		saveButton.setToolTipText(UIUtilities.formatToolTipText(
				"Create a movie."));
		saveButton.setActionCommand(""+SAVE);
		saveButton.addActionListener(this);
		nameField = new JTextField();
		String s = UIUtilities.removeFileExtension(name);
		if (s != null) {
			nameField.setText(s);
			saveButton.setEnabled(true);
		}
		nameField.getDocument().addDocumentListener(this);
		Map<Integer, String> map = MovieExportParam.FORMATS;
		String[] f = new String[map.size()];
		Entry entry;
		Iterator i = map.entrySet().iterator();
		int index = 0;
		int key;
		String value;
		boolean isMac = UIUtilities.isMacOS();
		while (i.hasNext()) {
			entry = (Entry) i.next();
			key = (Integer) entry.getKey();
			f[key] = (String) entry.getValue();
			if (isMac) {
				if (key == MovieExportParam.QT) index = key;
			}
		}
		formats = new JComboBox(f);
		formats.setSelectedIndex(index);
		timeRange = new TextualTwoKnobsSlider(1, maxT, 1, maxT);
		timeRange.getSlider().setOverlap(true);
		timeRange.layoutComponents();
		timeRange.setEnabled(maxT > 1);
		
		zRange = new TextualTwoKnobsSlider(1, maxZ, 1, maxZ);
		zRange.getSlider().setOverlap(true);
		zRange.layoutComponents();
		zRange.setEnabled(false);
		
		timeInterval = new JCheckBox("Time Interval");
		timeInterval.setFont(timeInterval.getFont().deriveFont(Font.BOLD));
		
		zInterval = new JCheckBox("Z-section Interval");
		zInterval.setFont(zInterval.getFont().deriveFont(Font.BOLD));
	
		if (maxT > 1) timeInterval.setSelected(true);
		else timeInterval.setEnabled(false);
		if (maxZ > 1 && maxT <= 1) {
			zInterval.setEnabled(false);
			zInterval.setSelected(true);
			zRange.setEnabled(true);
		}
		SpinnerModel sp = new SpinnerNumberModel(defaultZ, 1, maxZ, 1);
		zSpinner = new JSpinner(sp);
		sp = new SpinnerNumberModel(defaultT, 1, maxT, 1);
		tSpinner = new JSpinner(sp);
		if (maxT <= 1) tSpinner.setEnabled(false);
		if (maxZ <= 1 || zInterval.isSelected()) zSpinner.setEnabled(false);
		zInterval.addActionListener(this);
		zInterval.setActionCommand(""+Z_INTERVAL);
		timeInterval.addActionListener(this);
		timeInterval.setActionCommand(""+T_INTERVAL);
		
		showScaleBar = new JCheckBox("Scale Bar");
		showScaleBar.setFont(showScaleBar.getFont().deriveFont(Font.BOLD));
		showScaleBar.setSelected(true);
		showScaleBar.setActionCommand(""+SCALE_BAR);
		showScaleBar.addActionListener(this);
		scaleBar = new NumericalTextField();
		scaleBar.setText(""+DEFAULT_SCALE);
		
		fps = new JSpinner();
		fps.setValue(MovieExportParam.DEFAULT_FPS);
		labelVisible = new JCheckBox("Show Labels");
		getRootPane().setDefaultButton(saveButton);
		
		setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) { close(); }
		});
		
		colorBox = new JComboBox();
		Map<Color, String> colors = EditorUtil.COLORS_BAR;
		Object[][] cols = new Object[colors.size()][2];
		
		index = 0;
		i = colors.entrySet().iterator();
		while (i.hasNext()) {
			entry = (Entry) i.next();
			cols[index] = new Object[]{entry.getKey(), entry.getValue()};
			index++;
		}
		colorBox.setModel(new DefaultComboBoxModel(cols));	
		colorBox.setRenderer(new ColorListRenderer());
	}
	
	/** Enables or not the controls. */
	private void enabledControls()
	{
		saveButton.setEnabled(true);
		if (zInterval.isSelected()) {
			zRange.setEnabled(maxZ > 1);
			zSpinner.setEnabled(false);
		} else {
			zRange.setEnabled(false);
			zSpinner.setEnabled(maxZ > 1);
		}
		if (timeInterval.isSelected()) {
			timeRange.setEnabled(maxT > 1);
			tSpinner.setEnabled(false);
		} else {
			timeRange.setEnabled(false);
			tSpinner.setEnabled(maxT > 1);
		}
	}
	
	/** 
	 * Builds and lays out the control.
	 * 
	 * @return See above.
	 */
	private JPanel buildToolBar()
	{
		JPanel bar = new JPanel();
		bar.add(closeButton);
		bar.add(Box.createHorizontalStrut(5));
		bar.add(saveButton);
		bar.add(Box.createHorizontalStrut(20));
		JPanel p = UIUtilities.buildComponentPanelRight(bar);
		p.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
		return p;
	}
	
	/** Builds and lays out the UI. */
	private void buildGUI()
	{
		IconManager icons = IconManager.getInstance();
		TitlePanel tp = new TitlePanel(TITLE, "Create a movie.", 
				"The movie will be saved to the server and " +
				"attached to the image.", icons.getIcon(IconManager.MOVIE_48));
		Container c = getContentPane();
		c.setLayout(new BorderLayout(5, 5));
		c.add(tp, BorderLayout.NORTH);
		c.add(buildBody(), BorderLayout.CENTER);
		c.add(buildToolBar(), BorderLayout.SOUTH);
	}
	
	/**
	 * Builds the main component.
	 * 
	 * @return See above.
	 */
	private JPanel buildBody()
	{
		JPanel content = new JPanel();
        double[][] tl = {{TableLayout.PREFERRED, TableLayout.PREFERRED, 
        			TableLayout.FILL}, //columns
        				{TableLayout.PREFERRED, 5, TableLayout.PREFERRED, 5, 
        				TableLayout.PREFERRED, 5, TableLayout.PREFERRED,
        				TableLayout.PREFERRED, TableLayout.PREFERRED,
        				5, TableLayout.PREFERRED, TableLayout.PREFERRED,
        				5, TableLayout.PREFERRED, 
        				TableLayout.PREFERRED, 5, TableLayout.PREFERRED, 
        				TableLayout.PREFERRED, TableLayout.PREFERRED, 10}}; //rows
        TableLayout layout = new TableLayout(tl);
        content.setLayout(layout);
        content.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
       
        int i = 0;
        content.add(UIUtilities.setTextFont("Name"), "0, "+i+"");
        content.add(nameField, "1, "+i+", 2, "+i);
        i = i+2;
        content.add(UIUtilities.setTextFont("Format"), "0, "+i+"");
        content.add(formats, "1, "+i);
        i = i+2;
        content.add(UIUtilities.setTextFont("Frame Rate"), "0, "+i+"");
        content.add(fps, "1, "+i);
        content.add(new JLabel("fps"), "2, "+i);
        i = i+2;
        //
        content.add(UIUtilities.setTextFont("Channels selection"), "0, "+i+"");
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        Iterator<JComponent> k = buttons.values().iterator();
        while (k.hasNext()) {
			p.add(k.next());
		}
        content.add(p, "1, "+i);
        i = i+2;
        content.add(timeInterval, "0, "+i+", l, t");
        content.add(UIUtilities.buildComponentPanel(timeRange), 
        		"1, "+i+", 2, "+i);
        i = i+1;
        JLabel l = new JLabel();
        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(UIUtilities.setTextFont("If interval not selected, " +
        		"select the time-point", Font.ITALIC, 
        		l.getFont().getSize()-2));
        p.add(tSpinner);
        content.add(UIUtilities.buildComponentPanel(p), "0, "+i+", 2, "+i);
        i = i+2;
        content.add(zInterval, "0, "+i+", l, t");
        content.add(UIUtilities.buildComponentPanel(zRange), 
        		"1, "+i+", 2, "+i);
        i = i+1;
        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(UIUtilities.setTextFont("If interval not selected, " +
        		"select the z-section", Font.ITALIC, 
        		l.getFont().getSize()-2));
        p.add(zSpinner);
        content.add(UIUtilities.buildComponentPanel(p), "0, "+i+", 2, "+i);
        i = i+2;
        content.add(showScaleBar, "0, "+i);
        content.add(scaleBar, "1, "+i);
        content.add(new JLabel("microns"), "2, "+i);
        i = i+2;
        content.add(UIUtilities.buildComponentPanel(colorBox), "1, "+i);
        i = i+2;
        content.add(labelVisible, "0, "+i);
        return content;
	}
	
	/** Closes the dialog. */
	private void close()
	{
		option = CLOSE;
		setVisible(false);
		dispose();
	}
	
	/** Collects the parameters to create a movie. */
	private void save()
	{
		String name = nameField.getText();
		int format = formats.getSelectedIndex();
		int scale = -1;
		int f = (Integer) fps.getValue();
		if (showScaleBar.isSelected()) {
			Number n = scaleBar.getValueAsNumber();
			if (n != null) scale = n.intValue();
		}
		int type = MovieExportParam.ZT_MOVIE;;
		if (!timeInterval.isSelected() && zInterval.isSelected()) 
			type = MovieExportParam.Z_MOVIE;
		else if (timeInterval.isSelected() && !zInterval.isSelected()) 
			type = MovieExportParam.T_MOVIE;
		param = new MovieExportParam(name, f, format, scale, type);
		if (type == MovieExportParam.T_MOVIE || 
			type == MovieExportParam.ZT_MOVIE)
			param.setTimeInterval((int) timeRange.getStartValue()-1, 
					(int) timeRange.getEndValue()-1);
		if (type == MovieExportParam.Z_MOVIE || 
				type == MovieExportParam.ZT_MOVIE)
			param.setZsectionInterval((int) zRange.getStartValue()-1, 
					(int) zRange.getEndValue()-1);
		param.setLabelVisible(labelVisible.isSelected());
		
		if (!timeInterval.isSelected()) {
			int t = (Integer) tSpinner.getValue()-1;
			param.setTimeInterval(t, t);
		}
		if (!zInterval.isSelected()) {
			int z = (Integer) zSpinner.getValue()-1;
			param.setZsectionInterval(z, z);
		}
		
		int index = colorBox.getSelectedIndex();
		Entry entry;
		Map<Color, String> m = EditorUtil.COLORS_BAR;
		Iterator i = m.entrySet().iterator();
		int j = 0;
		String c = null;
		while (i.hasNext()) {
			entry = (Entry) i.next();
			if (j == index) c = (String) entry.getValue();
			j++;
		}
		param.setColor(c);
		
		//Channels
		List<Integer> channels = new ArrayList<Integer>();
		i = buttons.entrySet().iterator();
		JComponent comp;
		ChannelButton cb;
		while (i.hasNext()) {
			entry = (Entry) i.next();
			comp = (JComponent) entry.getValue();
			if (comp instanceof JCheckBox) {
				if (((JCheckBox) comp).isSelected()) {
					channels.add((Integer) entry.getKey());
				}
			} else if (comp instanceof ChannelButton) {
				cb = (ChannelButton) comp;
				if (cb.isSelected()) {
					channels.add(cb.getChannelIndex());
				}
			}
		}
		param.setChannels(channels);
		option = SAVE;
		close();
		firePropertyChange(CREATE_MOVIE_PROPERTY, null, param);
	}
	
	/** 
	 * Sets the enabled flag of the {@link #saveButton} depending on
	 * the value to the name field.
	 */
	private void handleText()
	{
		String text = nameField.getText();
		saveButton.setEnabled(!(text == null || text.trim().length() == 0));
	}
	
	/**
	 * Returns <code>true</code> if the colors are the same, <code>false</code>
	 * otherwise.
	 * 
	 * @param c1 The color to handle.
	 * @param c2 The color to handle.
	 * @return See above.
	 */
	private boolean isSameColor(Color c1, Color c2)
	{
		if (c1.getRed() != c2.getRed()) return false;
		if (c1.getGreen() != c2.getGreen()) return false;
		if (c1.getBlue() != c2.getBlue()) return false;
		return true;
	}
	
	/**
	 * Creates a new instance.
	 * 
	 * @param owner The owner of the frame.
	 * @param name  The name of the movie.
	 * @param maxT  The maximum number of time points.
	 * @param maxZ  The maximum number of z-sections.
	 * @param defaultZ The default z-section.
	 * @param defaultT The default timepoint.
	 * @param channels The channels information or the number of channels.
	 */
	public MovieExportDialog(JFrame owner, String name, int maxT, int maxZ, 
			int defaultZ, int defaultT, Object channels)
	{
		super(owner);
		setModal(true);
		param = null;
		this.maxT = maxT;
		this.maxZ = maxZ;
		initComponents(name, defaultZ, defaultT, channels);
		buildGUI();
		pack();
	}

	/**
	 * Turns off controls if the binary data are not available.
	 * 
	 * @param available Pass <code>false</code> to turn off the control.
	 */
	public void setBinaryAvailable(boolean available)
	{
		saveButton.setEnabled(available);
	}
	
	/**
	 * Sets the default value of the scale bar.
	 * 
	 * @param value The numerical value.
	 * @param color	The selected color.
	 */
	public void setScaleBarDefault(int value, Color color)
	{
		if (color != null) {
			Map<Color, String> m = EditorUtil.COLORS_BAR;
			int index = 0;
			Entry entry;
			Iterator i = m.entrySet().iterator();
			Color c;
			while (i.hasNext()) {
				entry = (Entry) i.next();
				c = (Color) entry.getKey();
				if (isSameColor(c, color))
					break;
				index++;
			}
			colorBox.setSelectedIndex(index);
		}
		if (value > 0)
			scaleBar.setText(""+value);
	}

    /**
     * Shows the dialog and returns the option selected by the user. 
     * 
     * @return The option selected by the user. 
     */
    public int showDialog()
    {
    	setLocation(getParent().getLocation());
    	setVisible(true);
    	return option;	
    }
   
    /**
     * Centers and shows the dialog. Returns the option selected by the user. 
     * 
     * @return The option selected by the user. 
     */
    public int centerDialog()
    {
    	UIUtilities.centerAndShow(this);
    	return option;	
    }
    
    /**
     * Returns the parameters used to create the movie or <code>null</code>
     * if none set.
     * 
     * @return See above.
     */
    public MovieExportParam getParameters() { return param; }
    
	/**
	 * Closes or creates a movie.
	 * @see ActionListener#actionPerformed(ActionEvent)
	 */
	public void actionPerformed(ActionEvent e)
	{
		int index = Integer.parseInt(e.getActionCommand());
		switch (index) {
			case CLOSE:
				close();
				break;
			case SAVE:
				save();
				break;
			case SCALE_BAR:
				scaleBar.setEnabled(showScaleBar.isSelected());
				break;
			case T_INTERVAL:
			case Z_INTERVAL:
				 enabledControls();
		}
	}

	/**
	 * Required by the {@link DocumentListener} I/F but no-op implementation
	 * in our case.
	 * @see DocumentListener#insertUpdate(DocumentEvent)
	 */
	public void insertUpdate(DocumentEvent e) { handleText(); }

	/**
	 * Required by the {@link DocumentListener} I/F but no-op implementation
	 * in our case.
	 * @see DocumentListener#removeUpdate(DocumentEvent)
	 */
	public void removeUpdate(DocumentEvent e) { handleText(); }
	
	/**
	 * Required by the {@link DocumentListener} I/F but no-op implementation
	 * in our case.
	 * @see DocumentListener#changedUpdate(DocumentEvent)
	 */
	public void changedUpdate(DocumentEvent e) {}


}
