/*
 *  soapUI, copyright (C) 2004-2012 smartbear.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI 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 Lesser General Public License for more details at gnu.org.
 */

package com.eviware.soapui.support.components;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.MenuSelectionManager;

import com.eviware.soapui.support.UISupport;

/**
 * This is a button which is designed to be the corner component of a
 * <code>JScrollPane</code>. It triggers a popup menu which holds a scaled image
 * of the component contained inside the <code>JScrollPane</code>.
 */

public class PreviewCorner extends JButton implements ActionListener
{
	private String _corner;
	private PreviewPopup _previewPopup;

	/**
	 * @param scrollPane
	 *           the <code>JScrollPane</code> to preview
	 * @param zoomIcon
	 *           the icon to use for the button
	 * @param doCloseAfterClick
	 *           When <code>true</code> the preview popup menu is closed on mouse
	 *           click.
	 * @param corner
	 *           Supposed to be one of the four corners of a
	 *           <code>JScrollPane</code>, like
	 *           <code>JScrollPane.LOWER_RIGHT_CORNER</code> for example, which
	 *           should match the position of the corner component of the scroll
	 *           pane. Note: If this parameter is not set correctly,
	 *           <code>JScrollPane.UPPER_LEFT_CORNER</code> will be used instead.
	 */

	public PreviewCorner( JScrollPane scrollPane, ImageIcon zoomIcon, boolean doCloseAfterClick, String corner )
	{

		super( zoomIcon );
		this._corner = corner;

		// Creates the popup menu, containing the scaled image of the component.
		_previewPopup = new PreviewPopup( scrollPane, doCloseAfterClick );

		setToolTipText( "Show a Panorama View of the contained editor" );

		// The action listener is used to trigger the popup menu.
		addActionListener( this );
	}

	public PreviewCorner( JScrollPane scrollPane, ImageIcon zoomIcon, String corner )
	{
		this( scrollPane, zoomIcon, false, corner );
	}

	public void actionPerformed( ActionEvent e )
	{
		_previewPopup.showUpInCorner( this, _corner );
	}

	public void release()
	{
		_previewPopup.release();
		removeActionListener( this );
	}
}

class PreviewPopup extends JPopupMenu implements MouseListener, MouseMotionListener
{

	private JScrollPane _scrollPane;
	private JViewport _viewPort;

	private JLabel _zoomWindow; // the JLabel containing the scaled image

	private JPanel _cursorLabel; // the JLabel mimicking the fake rectangle
	// cursor

	// This component will hold both JLabels _zoomWindow and _cursorLabel,
	// the latter on top of the other.
	private JLayeredPane _layeredPane;

	private int _iconWidth;
	private int _iconHeight;

	private boolean _doCloseAfterClick;

	float _ratio;

	// DELTA is the space between the scroll pane and the preview popup menu.
	private static int DELTA = 5;

	public PreviewPopup( JScrollPane scrollPane, boolean doCloseAfterClick )
	{
		this.setBorder( BorderFactory.createEtchedBorder() );

		_doCloseAfterClick = doCloseAfterClick;

		_scrollPane = scrollPane;
		_viewPort = _scrollPane.getViewport();

		_zoomWindow = new JLabel();
		_cursorLabel = createCursor();

		_layeredPane = new JLayeredPane();

		_layeredPane.add( _zoomWindow, new Integer( 0 ) );
		_layeredPane.add( _cursorLabel, new Integer( 1 ) );

		// Creates a blank transparent cursor to be used as the cursor of
		// the popup menu.
		BufferedImage bim = new BufferedImage( 1, 1, BufferedImage.TYPE_4BYTE_ABGR );
		setCursor( getToolkit().createCustomCursor( bim, ( new Point( 0, 0 ) ), "PreviewCursor" ) );

		this.add( _layeredPane );

		// Adds the mouse input listeners to the _layeredPane to scroll the
		// viewport and to move the fake cursor (_cursorLabel).
		_layeredPane.addMouseListener( this );
		_layeredPane.addMouseMotionListener( this );
	}

	public void release()
	{
		if( getParent() != null )
			getParent().remove( this );

		_layeredPane.removeMouseListener( this );
		_layeredPane.removeMouseMotionListener( this );

		_scrollPane = null;
		_layeredPane = null;
		_viewPort = null;
	}

	/**
	 * By default, the right corner of a popup menu is positionned at the right
	 * of a mouse click. What we want is to have the preview popup menu
	 * positionned <i>inside</i> the scroll pane, near the corner component. The
	 * purpose of this method is to display the scaled image of the component of
	 * the scroll pane, and to calculate the correct position of the preview
	 * popup menu.
	 */

	public void showUpInCorner( Component c, String corner )
	{

		if( _viewPort.getComponentCount() == 0 )
			return;

		float scaleFactor = 1.1f; // _viewPort.getComponent( 0 ).getHeight() /
		// _scrollPane.getHeight() * 2;

		// if (_viewPort.getWidth() < (_viewPort.getHeight() * scaleFactor))
		// _ratio =( int ) ( _viewPort.getComponent(0).getWidth() /
		// (_viewPort.getWidth() / scaleFactor) );
		//
		// else
		// _ratio = ( int ) (( _viewPort.getComponent(0).getHeight() /
		// (_viewPort.getHeight()) / scaleFactor ));
		_ratio = ( ( ( float )_viewPort.getComponent( 0 ).getHeight() / ( ( float )_viewPort.getHeight() ) / scaleFactor ) );

		if( _ratio < 2 )
			_ratio = 2;

		// System.out.println( "ratio = " + _ratio );

		int zoomWindowImageWidth = ( int )( _viewPort.getComponent( 0 ).getWidth() / _ratio );
		if( zoomWindowImageWidth < 10 )
		{
			UISupport.showInfoMessage( "Viewport too large for readable image, use scrollbar instead" );
			return;
		}

		int zoomWindowImageHeight = ( int )( _viewPort.getComponent( 0 ).getHeight() / _ratio );

		// System.out.println( "ratio = " + _ratio + ", zoomWindowImageWidth = " +
		// zoomWindowImageWidth +
		// ", zoomWindowImageHeight = " + zoomWindowImageHeight);

		/*
		 * Image componentImage =
		 * captureComponentViewAsBufferedImage(_viewPort.getComponent(0))
		 * .getScaledInstance( zoomWindowImageWidth, zoomWindowImageHeight,
		 * Image.SCALE_SMOOTH);
		 */
		/*
		 * Based on Shannon Hickey's comments. This is much faster way to scale
		 * instance Thanks! Shannon
		 */
		Image capture = captureComponentViewAsBufferedImage( _viewPort.getComponent( 0 ) );
		Image componentImage = new BufferedImage( zoomWindowImageWidth, zoomWindowImageHeight, BufferedImage.TYPE_INT_RGB );
		Graphics2D g2d = ( Graphics2D )componentImage.getGraphics();
		/* if you want smoother scaling */
		if( zoomWindowImageWidth > 15 )
			g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR );
		g2d.drawImage( capture, 0, 0, zoomWindowImageWidth, zoomWindowImageHeight, null );
		g2d.dispose();

		// Converts the Image to an ImageIcon to be used with a JLabel.
		ImageIcon componentIcon = new ImageIcon( componentImage );

		_iconWidth = componentIcon.getIconWidth();
		_iconHeight = componentIcon.getIconHeight();

		_zoomWindow.setIcon( componentIcon );

		_zoomWindow.setBounds( 0, 0, _iconWidth, _iconHeight );

		int cursorWidth = ( int )( _viewPort.getWidth() / _ratio );

		int cursorHeight = ( int )( _viewPort.getHeight() / _ratio );

		_cursorLabel.setBounds( 0, 0, cursorWidth, cursorHeight );

		_layeredPane.setPreferredSize( new Dimension( _iconWidth, _iconHeight ) );

		int dx = componentIcon.getIconWidth() + DELTA;
		int dy = componentIcon.getIconHeight() + DELTA;

		if( corner.equals( JScrollPane.UPPER_LEFT_CORNER ) )
			;
		else if( corner.equals( JScrollPane.UPPER_RIGHT_CORNER ) )
			dx = -dx;
		else if( corner.equals( JScrollPane.LOWER_RIGHT_CORNER ) )
		{
			dx = -dx;
			dy = -dy;
		}
		else if( corner.equals( JScrollPane.LOWER_LEFT_CORNER ) )
			dy = -dy;

		if( dy < 0 && Math.abs( dy ) > _viewPort.getHeight() )
		{
			dy = -_viewPort.getHeight() - 10;
		}

		// System.out.println( "Showing at " + dx + ", " + dy );

		// Shows the popup menu at the right place.
		this.show( c, dx, dy );

	}

	public JPanel createCursor()
	{
		JPanel label = new JPanel()
		{

			@Override
			protected void paintComponent( Graphics g )
			{
				Composite composite = ( ( Graphics2D )g ).getComposite();
				( ( Graphics2D )g ).setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.2f ) );
				super.paintComponent( g );
				( ( Graphics2D )g ).setComposite( composite );
			}

		};
		label.setBorder( BorderFactory.createLineBorder( Color.gray ) );
		label.setVisible( false );
		label.setOpaque( true );
		label.setBackground( Color.orange.darker() );
		return label;
	}

	public void mouseClicked( MouseEvent e )
	{
	}

	public void mouseEntered( MouseEvent e )
	{
		// When the mouse enters the preview popup menu, set the visibility
		// of the fake cursor to true.
		_cursorLabel.setVisible( true );
	}

	public void mouseExited( MouseEvent e )
	{
		// When the mouse exits the preview popup menu, set the visibility
		// of the fake cursor to false.
		_cursorLabel.setVisible( false );
	}

	public void mousePressed( MouseEvent e )
	{
	}

	public void mouseReleased( MouseEvent e )
	{
		// When the mouse is released, set the visibility of the preview
		// popup menu to false only if doCloseAfterClick is set to true.
		if( _doCloseAfterClick )
		{
			this.setVisible( false );
			_cursorLabel.setVisible( false );
			MenuSelectionManager.defaultManager().clearSelectedPath();
			setInvoker( null );
		}
	}

	public void mouseDragged( MouseEvent e )
	{
		moveCursor( e.getX(), e.getY() );
		scrollViewPort();
	}

	public void mouseMoved( MouseEvent e )
	{
		moveCursor( e.getX(), e.getY() );
		scrollViewPort();
	}

	/**
	 * Centers the fake cursor (_cursorLabel) position on the coordinates
	 * specified in the parameters.
	 */
	private void moveCursor( int x, int y )
	{
		int dx = x - _cursorLabel.getWidth() / 2;
		int dy = y - _cursorLabel.getHeight() / 2;
		_cursorLabel.setLocation( dx, dy );
	}

	/**
	 * Scrolls the viewport according to the fake cursor position in the preview
	 * popup menu.
	 */
	private void scrollViewPort()
	{
		Point cursorLocation = _cursorLabel.getLocation();
		int dx = ( int )Math.max( cursorLocation.getX(), 0 );
		int dy = ( int )Math.max( cursorLocation.getY(), 0 );

		dx = ( int )( dx * _ratio );
		dy = ( int )( dy * _ratio );

		( ( JComponent )_viewPort.getComponent( 0 ) ).scrollRectToVisible( new Rectangle( dx, dy, _viewPort.getWidth(),
				_viewPort.getHeight() ) );
	}

	/**
	 * takes a java component and generates an image out of it.
	 * 
	 * @param c
	 *           the component for which image needs to be generated
	 * @return the generated image
	 */
	public static BufferedImage captureComponentViewAsBufferedImage( Component c )
	{
		Dimension size = c.getSize();
		BufferedImage bufferedImage = new BufferedImage( size.width, size.height, BufferedImage.TYPE_INT_RGB );
		Graphics bufferedGraphics = bufferedImage.createGraphics();
		c.paint( bufferedGraphics );
		return bufferedImage;
	}

}
