/*
 * #%L
 * Ridge Detection plugin for ImageJ
 * %%
 * Copyright (C) 2014 - 2015 Thorsten Wagner (ImageJ java plugin), 1996-1998 Carsten Steger (original C code), 1999 R. Balasubramanian (detect lines code to incorporate within GRASP)
 * %%
 * 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, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */
package de.biomedical_imaging.ij.steger;

import java.awt.Button;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JLabel;

import ij.ImagePlus;
import ij.WindowManager;
import ij.gui.GenericDialog;
import ij.io.OpenDialog;

// TODO: Auto-generated Javadoc
/**
 * The GenericDialogPlus class enhances the GenericDialog by a few additional
 * methods.
 *
 * It adds a method to add a file chooser, a dialog chooser, an image chooser, a
 * button, and makes string (and file) fields drop targets.
 */
public class GenericDialogPlus extends GenericDialog {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/** The window I ds. */
	protected int[] windowIDs;

	/** The window titles. */
	protected String[] windowTitles;

	/**
	 * Instantiates a new generic dialog plus.
	 *
	 * @param title
	 *            the title
	 */
	public GenericDialogPlus(String title) {
		super(title);
	}

	/**
	 * Instantiates a new generic dialog plus.
	 *
	 * @param title
	 *            the title
	 * @param parent
	 *            the parent
	 */
	public GenericDialogPlus(String title, Frame parent) {
		super(title, parent);
	}

	/**
	 * Adds the image choice.
	 *
	 * @param label
	 *            the label
	 * @param defaultImage
	 *            the default image
	 */
	public void addImageChoice(String label, String defaultImage) {
		if (windowTitles == null) {
			windowIDs = WindowManager.getIDList();
			if (windowIDs == null)
				windowIDs = new int[0];
			windowTitles = new String[windowIDs.length];
			for (int i = 0; i < windowIDs.length; i++) {
				ImagePlus image = WindowManager.getImage(windowIDs[i]);
				windowTitles[i] = image == null ? "" : image.getTitle();
			}
		}
		addChoice(label, windowTitles, defaultImage);
	}

	/**
	 * Gets the next image.
	 *
	 * @return the next image
	 */
	public ImagePlus getNextImage() {
		return WindowManager.getImage(windowIDs[getNextChoiceIndex()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ij.gui.GenericDialog#addStringField(java.lang.String, java.lang.String,
	 * int)
	 */
	@Override
	public void addStringField(String label, String defaultString, int columns) {
		super.addStringField(label, defaultString, columns);
		if (isHeadless())
			return;

		TextField text = (TextField) stringField.lastElement();
		text.setDropTarget(null);
		new DropTarget(text, new TextDropTarget(text));
	}

	/**
	 * Adds the directory or file field.
	 *
	 * @param label
	 *            the label
	 * @param defaultPath
	 *            the default path
	 */
	public void addDirectoryOrFileField(String label, String defaultPath) {
		addDirectoryOrFileField(label, defaultPath, 20);
	}

	/**
	 * Adds the directory or file field.
	 *
	 * @param label
	 *            the label
	 * @param defaultPath
	 *            the default path
	 * @param columns
	 *            the columns
	 */
	public void addDirectoryOrFileField(String label, String defaultPath, int columns) {
		addStringField(label, defaultPath, columns);
		if (isHeadless())
			return;

		TextField text = (TextField) stringField.lastElement();
		GridBagLayout layout = (GridBagLayout) getLayout();
		GridBagConstraints constraints = layout.getConstraints(text);

		Button button = new Button("Browse...");
		DirectoryListener listener = new DirectoryListener("Browse for " + label, text,
				JFileChooser.FILES_AND_DIRECTORIES);
		button.addActionListener(listener);
		button.addKeyListener(this);

		Panel panel = new Panel();
		panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel.add(text);
		panel.add(button);

		layout.setConstraints(panel, constraints);
		add(panel);
	}

	/**
	 * Adds the directory field.
	 *
	 * @param label
	 *            the label
	 * @param defaultPath
	 *            the default path
	 */
	public void addDirectoryField(String label, String defaultPath) {
		addDirectoryField(label, defaultPath, 20);
	}

	/**
	 * Adds the directory field.
	 *
	 * @param label
	 *            the label
	 * @param defaultPath
	 *            the default path
	 * @param columns
	 *            the columns
	 */
	public void addDirectoryField(String label, String defaultPath, int columns) {
		addStringField(label, defaultPath, columns);
		if (isHeadless())
			return;

		TextField text = (TextField) stringField.lastElement();
		GridBagLayout layout = (GridBagLayout) getLayout();
		GridBagConstraints constraints = layout.getConstraints(text);

		Button button = new Button("Browse...");
		DirectoryListener listener = new DirectoryListener("Browse for " + label, text);
		button.addActionListener(listener);
		button.addKeyListener(this);

		Panel panel = new Panel();
		panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel.add(text);
		panel.add(button);

		layout.setConstraints(panel, constraints);
		add(panel);
	}

	/**
	 * Adds the file field.
	 *
	 * @param label
	 *            the label
	 * @param defaultPath
	 *            the default path
	 */
	public void addFileField(String label, String defaultPath) {
		addFileField(label, defaultPath, 20);
	}

	/**
	 * Adds the file field.
	 *
	 * @param label
	 *            the label
	 * @param defaultPath
	 *            the default path
	 * @param columns
	 *            the columns
	 */
	public void addFileField(String label, String defaultPath, int columns) {
		addStringField(label, defaultPath, columns);
		if (isHeadless())
			return;

		TextField text = (TextField) stringField.lastElement();
		GridBagLayout layout = (GridBagLayout) getLayout();
		GridBagConstraints constraints = layout.getConstraints(text);

		Button button = new Button("Browse...");
		FileListener listener = new FileListener("Browse for " + label, text);
		button.addActionListener(listener);
		button.addKeyListener(this);

		Panel panel = new Panel();
		panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		panel.add(text);
		panel.add(button);

		layout.setConstraints(panel, constraints);
		add(panel);
	}

	/**
	 * Add button to the dialog.
	 *
	 * @param label
	 *            button label
	 * @param listener
	 *            listener to handle the action when pressing the button
	 */
	public void addButton(String label, ActionListener listener) {
		if (isHeadless())
			return;

		Button button = new Button(label);

		button.addActionListener(listener);
		button.addKeyListener(this);

		addComponent(button);
	}

	/**
	 * Adds the component.
	 *
	 * @param component
	 *            the component
	 */
	public void addComponent(Component component) {
		if (isHeadless())
			return;

		GridBagLayout layout = (GridBagLayout) getLayout();
		layout.setConstraints(component, getConstraints());
		add(component);
	}

	/**
	 * Adds the component.
	 *
	 * @param component
	 *            the component
	 * @param fill
	 *            the fill
	 * @param weightx
	 *            the weightx
	 */
	public void addComponent(Component component, int fill, double weightx) {
		if (isHeadless())
			return;

		GridBagLayout layout = (GridBagLayout) getLayout();
		GridBagConstraints constraints = getConstraints();
		constraints.fill = fill;
		constraints.weightx = weightx;
		layout.setConstraints(component, constraints);
		add(component);
	}

	/**
	 * Adds an image to the generic dialog.
	 *
	 * @param path
	 *            - the path to the image in the jar, e.g. /images/fiji.png (the
	 *            first / has to be there!)
	 * @return true if the image was found and added, otherwise false
	 */
	public boolean addImage(final String path) {
		if (isHeadless())
			return true;

		return addImage(getClass().getResource(path));
	}

	/**
	 * Adds an image to the generic dialog.
	 *
	 * @param imgURL
	 *            - the {@link URL} pointing to the resource
	 * @return true if the image was found and added, otherwise false
	 */
	public boolean addImage(final URL imgURL) {
		if (isHeadless())
			return true;

		final ImageIcon image = createImageIcon(imgURL);

		if (image == null)
			return false;
		addImage(image);
		return true;
	}

	/**
	 * Adds an image to the generic dialog.
	 *
	 * @param image
	 *            - the {@link ImageIcon} to display
	 * @return label - the {@link JLabel} that contains the image for updating:
	 * 
	 *         image.setImage(otherImageIcon.getImage());
	 *         label.update(label.getGraphics());
	 */
	public JLabel addImage(final ImageIcon image) {
		if (isHeadless())
			return null;

		final Panel panel = new Panel();
		panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
		final JLabel label = new JLabel(image);
		label.setOpaque(true);
		panel.add(label);
		addPanel(panel);

		return label;
	}

	/**
	 * Returns an ImageIcon, or null if the path was invalid.
	 *
	 * @param imgURL
	 *            the img URL
	 * @return the image icon
	 */
	public static ImageIcon createImageIcon(final URL imgURL) {
		if (isHeadless())
			return null;

		if (imgURL != null)
			return new ImageIcon(imgURL);
		return null;
	}

	// Work around too many private restrictions (add a new panel and remove it
	/**
	 * Gets the constraints.
	 *
	 * @return the constraints
	 */
	// right away)
	protected GridBagConstraints getConstraints() {
		GridBagLayout layout = (GridBagLayout) getLayout();
		Panel panel = new Panel();
		addPanel(panel);
		GridBagConstraints constraints = layout.getConstraints(panel);
		remove(panel);
		return constraints;
	}

	/**
	 * Checks if is headless.
	 *
	 * @return true, if is headless
	 */
	private static boolean isHeadless() {
		return GraphicsEnvironment.isHeadless();
	}

	/**
	 * The listener interface for receiving file events. The class that is
	 * interested in processing a file event implements this interface, and the
	 * object created with that class is registered with a component using the
	 * component's <code>addFileListener<code> method. When the file event occurs,
	 * that object's appropriate method is invoked.
	 *
	 * @see FileEvent
	 */
	static class FileListener implements ActionListener {

		/** The title. */
		String title;

		/** The text. */
		TextField text;

		/**
		 * Instantiates a new file listener.
		 *
		 * @param title
		 *            the title
		 * @param text
		 *            the text
		 */
		public FileListener(String title, TextField text) {
			this.title = title;
			this.text = text;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			String fileName = null;
			File dir = new File(text.getText());
			if (!dir.isDirectory()) {
				if (dir.exists())
					fileName = dir.getName();
				dir = dir.getParentFile();
			}
			while (dir != null && !dir.exists())
				dir = dir.getParentFile();

			OpenDialog dialog;
			if (dir == null)
				dialog = new OpenDialog(title, fileName);
			else
				dialog = new OpenDialog(title, dir.getAbsolutePath(), fileName);
			String directory = dialog.getDirectory();
			if (directory == null)
				return;
			fileName = dialog.getFileName();
			text.setText(directory + File.separator + fileName);
		}
	}

	/**
	 * The listener interface for receiving directory events. The class that is
	 * interested in processing a directory event implements this interface, and the
	 * object created with that class is registered with a component using the
	 * component's <code>addDirectoryListener<code> method. When the directory event
	 * occurs, that object's appropriate method is invoked.
	 *
	 * @see DirectoryEvent
	 */
	static class DirectoryListener implements ActionListener {

		/** The title. */
		String title;

		/** The text. */
		TextField text;

		/** The file selection mode. */
		int fileSelectionMode;

		/**
		 * Instantiates a new directory listener.
		 *
		 * @param title
		 *            the title
		 * @param text
		 *            the text
		 */
		public DirectoryListener(String title, TextField text) {
			this(title, text, JFileChooser.DIRECTORIES_ONLY);
		}

		/**
		 * Instantiates a new directory listener.
		 *
		 * @param title
		 *            the title
		 * @param text
		 *            the text
		 * @param fileSelectionMode
		 *            the file selection mode
		 */
		public DirectoryListener(String title, TextField text, int fileSelectionMode) {
			this.title = title;
			this.text = text;
			this.fileSelectionMode = fileSelectionMode;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			File directory = new File(text.getText());
			while (directory != null && !directory.exists())
				directory = directory.getParentFile();

			JFileChooser fc = new JFileChooser(directory);
			fc.setFileSelectionMode(fileSelectionMode);

			fc.showOpenDialog(null);
			File selFile = fc.getSelectedFile();
			if (selFile != null)
				text.setText(selFile.getAbsolutePath());
		}
	}

	/**
	 * Strip suffix.
	 *
	 * @param s
	 *            the s
	 * @param suffix
	 *            the suffix
	 * @return the string
	 */
	static String stripSuffix(String s, String suffix) {
		return !s.endsWith(suffix) ? s : s.substring(0, s.length() - suffix.length());
	}

	/**
	 * Gets the string.
	 *
	 * @param event
	 *            the event
	 * @return the string
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws UnsupportedFlavorException
	 *             the unsupported flavor exception
	 */
	@SuppressWarnings("unchecked")
	static String getString(DropTargetDropEvent event) throws IOException, UnsupportedFlavorException {
		String text = null;
		DataFlavor fileList = DataFlavor.javaFileListFlavor;

		if (event.isDataFlavorSupported(fileList)) {
			event.acceptDrop(DnDConstants.ACTION_COPY);
			List<File> list = (List<File>) event.getTransferable().getTransferData(fileList);
			text = list.get(0).getAbsolutePath();
		} else if (event.isDataFlavorSupported(DataFlavor.stringFlavor)) {
			event.acceptDrop(DnDConstants.ACTION_COPY);
			text = (String) event.getTransferable().getTransferData(DataFlavor.stringFlavor);
			if (text.startsWith("file://"))
				text = text.substring(7);
			text = stripSuffix(stripSuffix(text, "\n"), "\r").replaceAll("%20", " ");
		} else {
			event.rejectDrop();
			return null;
		}

		event.dropComplete(text != null);
		return text;
	}

	/**
	 * The Class TextDropTarget.
	 */
	static class TextDropTarget extends DropTargetAdapter {

		/** The text. */
		TextField text;

		/** The flavor. */
		DataFlavor flavor = DataFlavor.stringFlavor;

		/**
		 * Instantiates a new text drop target.
		 *
		 * @param text
		 *            the text
		 */
		public TextDropTarget(TextField text) {
			this.text = text;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent)
		 */
		@Override
		public void drop(DropTargetDropEvent event) {
			try {
				text.setText(getString(event));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ij.gui.GenericDialog#keyPressed(java.awt.event.KeyEvent)
	 */
	@Override
	public void keyPressed(KeyEvent e) {
		int keyCode = e.getKeyCode();
		if (keyCode == KeyEvent.VK_ESCAPE || (keyCode == KeyEvent.VK_W
				&& (e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0))
			// wasCanceled is private; workaround
			windowClosing(null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ij.gui.GenericDialog#keyReleased(java.awt.event.KeyEvent)
	 */
	@Override
	public void keyReleased(KeyEvent e) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see ij.gui.GenericDialog#keyTyped(java.awt.event.KeyEvent)
	 */
	@Override
	public void keyTyped(KeyEvent e) {
	}

}