/*
 * Copyright 2008-2019 by Emeric Vernat
 *
 *     This file is part of Java Melody.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bull.javamelody.swing.util;

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.AWTEventListener;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.PrintStream;

import javax.swing.FocusManager;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.WindowConstants;

/**
 * Classe utilitaire pour Swing.
 * @author Emeric Vernat
 */
public final class MSwingUtilities {
	private MSwingUtilities() {
		super();
	}

	/**
	 * Affiche la trace de l'exception dans la console d'erreur et affiche une boîte de dialogue pour afficher l'exception.
	 * @param throwable Throwable
	 */
	@SuppressWarnings("resource")
	public static void showException(Throwable throwable) {
		throwable.printStackTrace(getSystemErrorStream());
		JOptionPane.showMessageDialog(null, throwable.toString(),
				UIManager.getString("OptionPane.messageDialogTitle"), JOptionPane.ERROR_MESSAGE);
		// on pourrait affichage une boîte de dialogue plus évoluée pour permettre d'afficher la stack trace en détail
	}

	/**
	 * Affiche une boîte de dialogue de confirmation.
	 * @param component Component
	 * @param message String
	 * @return boolean
	 */
	public static boolean showConfirmation(Component component, String message) {
		return JOptionPane.showConfirmDialog(SwingUtilities.getWindowAncestor(component), message,
				UIManager.getString("OptionPane.titleText"),
				JOptionPane.OK_OPTION | JOptionPane.CANCEL_OPTION) == JOptionPane.OK_OPTION;
	}

	/**
	 * Affiche une boîte de dialogue d'information.
	 * @param component Component
	 * @param message String
	 */
	public static void showMessage(Component component, String message) {
		JOptionPane.showMessageDialog(SwingUtilities.getWindowAncestor(component), message,
				UIManager.getString("OptionPane.messageDialogTitle"),
				JOptionPane.INFORMATION_MESSAGE);
	}

	/**
	 * Retourne l'instance courante de la classe componentClass contenant l'élément component. <br/>
	 * Cette méthode peut-être très utile pour récupérer une référence à un parent éloigné (ancêtre), en l'absence de référence directe du type attribut.
	 *
	 * <br/>
	 * Ex : un composant panel désire une référence sur sa JFrame parente, alors l'instruction suivante suffit : getAncestorOfClass(JFrame.class, panel)
	 *
	 * @return Component
	 * @param <T>
	 *           le type du composant recherché
	 * @param componentClass
	 *           Class
	 * @param component
	 *           Component
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getAncestorOfClass(final Class<T> componentClass,
			final Component component) {
		return (T) SwingUtilities.getAncestorOfClass(componentClass, component);
	}

	/**
	 * Retourne le focusOwner permanent.<br/>
	 * Le focusOwner permanent est défini comme le dernier Component à avoir reçu un événement FOCUS_GAINED permanent.<br/>
	 * Le focusOwner et le focusOwner permanent sont équivalent sauf si un changement temporaire de focus<br/>
	 * est en cours. Si c'est le cas, le focusOwner permanent redeviendra &galement<br/>
	 * le focusOwner à la fin de ce changement de focus temporaire.
	 *
	 * @return Component
	 */
	public static Component getPermanentFocusOwner() {
		// return new DefaultKeyboardFocusManager().getPermanentFocusOwner();
		return FocusManager.getCurrentManager().getPermanentFocusOwner();
	}

	/**
	 * Retourne la fenêtre possédant le focus.
	 *
	 * @return Component
	 */
	public static Window getFocusedWindow() {
		// return new DefaultKeyboardFocusManager().getFocusedWindow();
		return FocusManager.getCurrentManager().getFocusedWindow();
	}

	/**
	 * Démarre un composant dans une Frame (utile pour écrire des méthodes main sur des panels en développement).
	 *
	 * @param component
	 *           JComponent
	 * @return la Frame créée
	 */
	@SuppressWarnings("resource")
	public static JFrame run(final JComponent component) {
		final JFrame frame = new JFrame();
		try {
			frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
			component.setOpaque(true); // si opaque false, il y a des pbs de paint
			frame.setContentPane(component);
			frame.setTitle(component.getClass().getName());
			frame.pack();
			frame.setLocationRelativeTo(null);
			frame.setVisible(true);
		} catch (final Exception exception) {
			exception.printStackTrace(getSystemErrorStream()); // NOPMD
		}
		return frame;
	}

	/**
	 * @return System.err
	 */
	private static PrintStream getSystemErrorStream() {
		return System.err;
	}

	/**
	 * Démarre un composant dans une Frame sans pack() (utile pour écrire des méthodes main sur des panels en développement).
	 *
	 * @param component
	 *           JComponent
	 * @return la Frame créée
	 */
	public static JFrame runUnpacked(final JComponent component) {
		component.setPreferredSize(component.getSize());
		return run(component);
	}

	/**
	 * Initialisation d'événements sur la touche Escape pour fermer les dialogues.
	 */
	public static void initEscapeClosesDialogs() {
		final AWTEventListener awtEventListener = new AWTEventListener() {
			/** {@inheritDoc} */
			@Override
			public void eventDispatched(final AWTEvent event) {
				if (event instanceof KeyEvent
						&& ((KeyEvent) event).getKeyCode() == KeyEvent.VK_ESCAPE
						&& event.getID() == KeyEvent.KEY_PRESSED) {
					escapePressed();
				}
			}
		};
		Toolkit.getDefaultToolkit().addAWTEventListener(awtEventListener, AWTEvent.KEY_EVENT_MASK);
	}

	/**
	 * La touche Esc a été pressée : fermer la dialogue modale ouverte.
	 */
	static void escapePressed() {
		final Component focusOwner = getPermanentFocusOwner();
		final Window focusedWindow = SwingUtilities.getWindowAncestor(focusOwner);
		if (focusedWindow instanceof Dialog && ((Dialog) focusedWindow).isModal()) {
			//			try {
			//				final Robot robot = new Robot();
			//				robot.keyPress(KeyEvent.VK_ALT);
			//				robot.keyPress(KeyEvent.VK_F4);
			//				robot.keyRelease(KeyEvent.VK_F4);
			//				robot.keyRelease(KeyEvent.VK_ALT);
			//			} catch (final AWTException e) {
			//				throw new IllegalStateException(e);
			//			}
			focusedWindow.dispose();
		}
	}

	/**
	 * Redimensionne une ImageIcon.
	 * @param icon ImageIcon
	 * @param targetWidth int
	 * @param targetHeight int
	 * @return ImageIcon
	 */
	public static ImageIcon getScaledInstance(ImageIcon icon, int targetWidth, int targetHeight) {
		return new ImageIcon(getScaledInstance(icon.getImage(), targetWidth, targetHeight));
	}

	/**
	 * Redimensionne une image.
	 * @param img Image
	 * @param targetWidth int
	 * @param targetHeight int
	 * @return Image
	 */
	public static Image getScaledInstance(Image img, int targetWidth, int targetHeight) {
		final int type = BufferedImage.TYPE_INT_ARGB;
		Image ret = img;
		final int width = ret.getWidth(null);
		final int height = ret.getHeight(null);
		if (width != targetWidth && height != targetHeight) {
			// a priori plus performant que Image.getScaledInstance
			final BufferedImage scratchImage = new BufferedImage(targetWidth, targetHeight, type);
			final Graphics2D g2 = scratchImage.createGraphics();
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_BILINEAR);
			g2.drawImage(ret, 0, 0, targetWidth, targetHeight, 0, 0, width, height, null);
			g2.dispose();
			ret = scratchImage;
		}
		return ret;
	}
}
