/*
 * Copyright (c) 2005-2007 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.jvnet.lafwidget.tabbed;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;

import javax.swing.*;

import org.jvnet.lafwidget.*;
import org.jvnet.lafwidget.tabbed.TabPreviewThread.TabPreviewInfo;
import org.jvnet.lafwidget.utils.FadeTracker;
import org.jvnet.lafwidget.utils.FadeTracker.FadeKind;
import org.jvnet.lafwidget.utils.LafConstants.AnimationKind;

/**
 * Tab preview window. Is displayed when the mouse hovers over relevant
 * (previewable) tabs.
 * 
 * @author Kirill Grouchnikov
 */
public class TabPreviewWindow extends JWindow implements ActionListener {
	/**
	 * Singleton instance of tab preview window.
	 */
	protected static TabPreviewWindow instance;

	/**
	 * Information on the current tab preview request.
	 */
	protected TabPreviewInfo currTabPreviewInfo;

	/**
	 * Currently running timer task.
	 */
	protected Timer currTabPreviewTimer;

	/**
	 * Returns the singleton instance of tab preview window.
	 * 
	 * @return The singleton instance of tab preview window.
	 */
	public static synchronized TabPreviewWindow getInstance() {
		if (TabPreviewWindow.instance == null) {
			TabPreviewWindow.instance = new TabPreviewWindow();
			TabPreviewWindow.instance.setLayout(new BorderLayout());
			// instance.addHierarchyListener(new HierarchyListener() {
			// public void hierarchyChanged(HierarchyEvent e) {
			// System.err.println(e.getID());
			// }
			// });
		}
		return TabPreviewWindow.instance;
	}

	/**
	 * Posts a preview request for a tab component in the specified tabbed pane.
	 * 
	 * @param tabPane
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Index of the tab to preview.
	 */
	public synchronized void postPreviewRequest(JTabbedPane tabPane,
			int tabIndex) {
		TabPreviewPainter previewPainter = LafWidgetUtilities
				.getTabPreviewPainter(tabPane);
		if ((previewPainter == null)
				|| (!previewPainter.hasPreviewWindow(tabPane, tabIndex)))
			return;

		// check if already showing
		if (this.currTabPreviewInfo != null) {
			if ((this.currTabPreviewInfo.tabPane == tabPane)
					&& (this.currTabPreviewInfo.tabIndexToPreview == tabIndex))
				return;
		}

		if (this.currTabPreviewTimer != null) {
			if (this.currTabPreviewTimer.isRunning())
				this.currTabPreviewTimer.stop();
		}

		Dimension previewDim = previewPainter.getPreviewWindowDimension(
				tabPane, tabIndex);
		int pWidth = previewDim.width;
		int pHeight = previewDim.height;
		Component tabComponent = tabPane.getComponentAt(tabIndex);
		if (tabComponent != null) {
			int width = tabComponent.getWidth();
			int height = tabComponent.getHeight();
			double ratio = (double) width / (double) height;
			double pRatio = (double) previewDim.width
					/ (double) previewDim.height;
			if (pRatio > ratio) {
				pWidth = (int) (pHeight * ratio);
			} else {
				pHeight = (int) (pWidth / ratio);
			}
		}

		this.currTabPreviewInfo = new TabPreviewInfo();
		this.currTabPreviewInfo.tabPane = tabPane;
		this.currTabPreviewInfo.tabIndexToPreview = tabIndex;
		this.currTabPreviewInfo.previewWidth = pWidth;
		this.currTabPreviewInfo.previewHeight = pHeight;
		this.currTabPreviewInfo.initiator = tabPane;
		this.currTabPreviewInfo.previewCallback = new TabPreviewThread.TabPreviewCallback() {
			/*
			 * (non-Javadoc)
			 * 
			 * @see org.jvnet.lafwidget.tabbed.TabPreviewThread.TabPreviewCallback#start(javax.swing.JTabbedPane,
			 *      int,
			 *      org.jvnet.lafwidget.tabbed.TabPreviewThread.TabPreviewInfo)
			 */
			public void start(JTabbedPane tabPane, int tabCount,
					TabPreviewInfo tabPreviewInfo) {
				// Nothing to do since the callback was registered
				// for a specific tab.
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see org.jvnet.lafwidget.tabbed.TabPreviewThread.TabPreviewCallback#offer(javax.swing.JTabbedPane,
			 *      int, java.awt.image.BufferedImage)
			 */
			public void offer(JTabbedPane tabPane, int tabIndex,
					BufferedImage componentSnap) {
				if (TabPreviewWindow.this.currTabPreviewInfo == null) {
					// has since been cancelled
					return;
				}
				if ((tabPane != TabPreviewWindow.this.currTabPreviewInfo.tabPane)
						|| (tabIndex != TabPreviewWindow.this.currTabPreviewInfo.tabIndexToPreview)) {
					// has since been cancelled
					return;
				}
				Rectangle previewScreenRectangle = TabPreviewWindow.this
						.getPreviewWindowScreenRect(
								tabPane,
								tabIndex,
								TabPreviewWindow.this.currTabPreviewInfo.previewWidth,
								TabPreviewWindow.this.currTabPreviewInfo.previewHeight);
				TabPreviewWindow.this.getContentPane().removeAll();
				final JLabel previewLabel = new JLabel(new ImageIcon(
						componentSnap)) {
					protected void paintComponent(Graphics g) {
						Graphics2D g2 = (Graphics2D) g.create();
						FadeTracker fadeTracker = FadeTracker.getInstance();
						// Check if in the middle of fade-in
						if (fadeTracker.isTracked(this, FadeKind.ENABLE)) {
							float fadeFactor10 = fadeTracker.getFade10(this,
									FadeKind.ENABLE);
							g2.setComposite(AlphaComposite.getInstance(
									AlphaComposite.SRC_OVER,
									fadeFactor10 / 10.0f));
						}
						super.paintComponent(g2);
						g2.dispose();
					}
				};
				TabPreviewWindow.this
						.addComponentListener(new ComponentAdapter() {
							public void componentShown(ComponentEvent e) {
								previewLabel.setVisible(true);
								// Start fading-in of the image.
								FadeTracker.getInstance().trackFadeIn(
										FadeKind.ENABLE, previewLabel, false,
										null);
							}
						});
				// previewLabel.setBorder(new SubstanceBorder());
				previewLabel.putClientProperty(LafWidget.ANIMATION_KIND,
						AnimationKind.SLOW);
				TabPreviewWindow.this.getContentPane().add(previewLabel,
						BorderLayout.CENTER);
				TabPreviewWindow.this.setSize(previewScreenRectangle.width,
						previewScreenRectangle.height);
				TabPreviewWindow.this.setLocation(previewScreenRectangle.x,
						previewScreenRectangle.y);
				previewLabel.setVisible(false);
				TabPreviewWindow.this.setVisible(true);
			}
		};

		int extraDelay = previewPainter.getPreviewWindowExtraDelay(tabPane,
				tabIndex);
		if (extraDelay < 0) {
			throw new IllegalArgumentException(
					"Extra delay for tab preview must be non-negative");
		}
		this.currTabPreviewTimer = new Timer(2000 + extraDelay, this);
		this.currTabPreviewTimer.setRepeats(false);
		this.currTabPreviewTimer.start();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
	 */
	public void actionPerformed(ActionEvent e) {
		if (this.currTabPreviewInfo == null)
			return;

		// If we are here - the delay timer has expired.

		// System.err.println("Post " + tabIndex);
		TabPreviewPainter previewPainter = LafWidgetUtilities
				.getTabPreviewPainter(this.currTabPreviewInfo.tabPane);
		if ((previewPainter == null)
				|| (!previewPainter.hasPreviewWindow(
						this.currTabPreviewInfo.tabPane,
						this.currTabPreviewInfo.tabIndexToPreview)))
			return;

		// Queue the request with the preview thread.
		TabPreviewThread.getInstance().queueTabPreviewRequest(
				this.currTabPreviewInfo);
	}

	/**
	 * Returns the screen rectangle for the preview window.
	 * 
	 * @param tabPane
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Tab index.
	 * @param pWidth
	 *            Preview width.
	 * @param pHeight
	 *            Preview height.
	 * @return The screen rectangle for the preview window.
	 */
	protected Rectangle getPreviewWindowScreenRect(JTabbedPane tabPane,
			int tabIndex, int pWidth, int pHeight) {
		LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
				.getLafSupport();

		Rectangle relative = lafSupport.getTabRectangle(tabPane, tabIndex);
		if (relative == null)
			return null;

		Rectangle result = new Rectangle(pWidth, pHeight);
		boolean ltr = tabPane.getComponentOrientation().isLeftToRight();
		if (ltr) {
			if (tabPane.getTabPlacement() != SwingConstants.BOTTOM) {
				result.setLocation(relative.x, relative.y + relative.height);
			} else {
				result.setLocation(relative.x, relative.y - pHeight);
			}
		} else {
			if (tabPane.getTabPlacement() != SwingConstants.BOTTOM) {
				result.setLocation(relative.x + relative.width - pWidth,
						relative.y + relative.height);
			} else {
				result.setLocation(relative.x + relative.width - pWidth,
						relative.y - pHeight);
			}
		}
		int dx = tabPane.getLocationOnScreen().x;
		int dy = tabPane.getLocationOnScreen().y;
		result.x += dx;
		result.y += dy;

		// Fix to make the tab preview window stay in screen bounds
		Rectangle virtualBounds = new Rectangle();
		GraphicsEnvironment ge = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice[] gds = ge.getScreenDevices();
		for (int i = 0; i < gds.length; i++) {
			GraphicsDevice gd = gds[i];
			GraphicsConfiguration gc = gd.getDefaultConfiguration();
			virtualBounds = virtualBounds.union(gc.getBounds());
		}

		int screenWidth = virtualBounds.width;
		int screenHeight = virtualBounds.height;

		if (result.x + result.width > (screenWidth - 1)) {
			result.x -= (result.x + result.width - screenWidth + 1);
		}
		if (result.y + result.height > (screenHeight - 1)) {
			result.y -= (result.y + result.height - screenHeight + 1);
		}
		if (result.x < 1)
			result.x = 1;
		if (result.y < 1)
			result.y = 1;
		return result;
	}

	/**
	 * Cancels the currently pending preview request.
	 */
	public synchronized void cancelPreviewRequest() {
		// System.err.println("Cancel");
		this.currTabPreviewInfo = null;
		if ((this.currTabPreviewTimer != null)
				&& this.currTabPreviewTimer.isRunning()) {
			this.currTabPreviewTimer.stop();
			this.currTabPreviewTimer = null;
		}
		this.dispose();// setVisible(false);
	}
}
