/******************************************************************************
 * Copyright (c) 2006, Intalio Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Intalio Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.stp.bpmn.diagram.ui;

import java.util.LinkedList;
import java.util.List;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.DragTracker;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.editparts.LayerManager;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.l10n.DiagramUIMessages;
import org.eclipse.gmf.runtime.diagram.ui.tools.PopupBarTool;
import org.eclipse.gmf.runtime.emf.type.core.IElementType;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;

/**
 * @author atoulme
 * @author <a href="http://www.intalio.com">&copy; Intalio, Inc.</a>
 * Pops up a balloon representing PopupBarDescriptors as Labels.
 * 
 * This class has been created using PopupBarEditPolicy.
 * @author affrantz@us.ibm.com, cmahoney
 */
public class PopupBalloon {

	public PopupBalloon(IGraphicalEditPart part) {
		this.parent = part.getTopGraphicEditPart(); 
		// so that it will not be disposed.
		partHashCode = part.hashCode();
	}
	
	/**
	 * Used to know which edit part triggered the balloon, clients application
	 * may want to redraw the balloon if a new edit part triggers one.
	 */
	private int partHashCode;
	
	static private int ACTION_BUTTON_START_X = 5;

	static private int ACTION_BUTTON_START_Y = 5;
	static private int ACTION_MARGIN_RIGHT = 10;

	/** Y postion offset from shape where the balloon top begin. */
	static private int ACTION_WIDTH_HGT = 30;

	static private double BALLOON_X_OFFSET_RHS = 0.65;

	static private double BALLOON_X_OFFSET_LHS = 0.25;
	
	/** Y postion offset from shape where the balloon top begin. */
	static private int BALLOON_Y_OFFSET = 10;
	
	/**
	 * The amount of time to wait before hiding the diagram assistant after it
	 * has been made visible.
	 */
	private static final int DISAPPEARANCE_DELAY = 1000;

	/** Images created that must be deleted when popup bar is removed */
	protected List<Image> imagesToBeDisposed = new LinkedList<Image>();

	private double myBallonOffsetPercent = BALLOON_X_OFFSET_RHS;

	/** the figure used to surround the action buttons */
	private IFigure myBalloon = null;


	/** The popup bar descriptors for the popup bar buttons */
	private List<PopupBarDescriptor> myPopupBarDescriptors = new LinkedList<PopupBarDescriptor>();

	private IGraphicalEditPart parent;

	/**
	 * Creates a new popup bar descriptor with an icon according to the severity.
	 * This icon will not be disposed when the balloon is disposed.
	 * @param text
	 */
	public void addPopupBarDescriptor(String text,int severity) {
		PopupBarDescriptor p = new PopupBarDescriptor(text,severity);
		p.setShouldBeDisposed(false);
		myPopupBarDescriptors.add(p);
	}
	public void addPopupBarDescriptor(PopupBarDescriptor descriptor) {
		this.addPopupBarDescriptor(
				descriptor.getElementtype(), 
				descriptor.getIcon(),
				new PopupBarTool(getParent(), descriptor.getElementtype()),
				descriptor.getToolTip());
	}
	/**
	 * default method for plugins which passes along the PopupBarTool
	 * as the tool to be used.
	 * @param elementType
	 * @param theImage
	 */
	protected void addPopupBarDescriptor(IElementType elementType,
			Image theImage) {

		this.addPopupBarDescriptor(elementType, theImage, new PopupBarTool(
				getParent(), elementType));

	}

	/**
	 * method used primarily to add UnspecifiedTypeCreationTool
	 * @param elementType
	 * @param theImage
	 * @param theRequest the create request to be used
	 */
	protected void addPopupBarDescriptor(IElementType elementType,
			Image theImage, CreateRequest theRequest) {

		PopupBarTool theTracker = new PopupBarTool(getParent(), theRequest);

		this.addPopupBarDescriptor(elementType, theImage, theTracker);

	}

	/**
	 * adds popup bar descriptor
	 * @param elementType
	 * @param theImage
	 * @param theTracker
	 */
	protected void addPopupBarDescriptor(IElementType elementType,
			Image theImage, DragTracker theTracker) {

		String theInputStr = DiagramUIMessages.PopupBar_AddNew;

		String theTip = NLS.bind(theInputStr, elementType.getDisplayName());

		addPopupBarDescriptor(elementType, theImage, theTracker, theTip);
	}

	/**
	 * allows plugins to add their own popup bar tools and tips
	 * @param elementType
	 * @param theImage
	 * @param theTracker
	 * @param theTip
	 */
	protected void addPopupBarDescriptor(IElementType elementType,
			Image theImage, DragTracker theTracker, String theTip) {

		PopupBarDescriptor desc = new PopupBarDescriptor(theTip, theImage,
				elementType, theTracker);
		myPopupBarDescriptors.add(desc);

	}

	/**
	 * @param elementType
	 * @param theImage
	 * @param theTip
	 */
	public void addPopupBarDescriptor(IElementType elementType,
			Image theImage, String theTip) {

		PopupBarTool theTracker = new PopupBarTool(getParent(), elementType);
		PopupBarDescriptor desc = new PopupBarDescriptor(theTip, theImage,
				elementType, theTracker);
		myPopupBarDescriptors.add(desc);

	}

	protected IFigure createPopupBarFigure() {
		return new RoundedRectangleWithTail();
	}

	/**
	 * This is the entry point that subclasses can override to fill the
	 * popup bar descrioptors if they have customized tools that cannot be done
	 * using the type along with the modeling assistant service.
	 */
	protected void fillPopupBarDescriptors() {
		// subclasses can override.
	}

	public IFigure getBalloon() {
		return myBalloon;
	}

	/**
	 * Gets the amount of time to wait before hiding the diagram assistant after
	 * it has been made visible.
	 * 
	 * @return the time to wait in milliseconds
	 */
	protected int getDisappearanceDelay() {
		return DISAPPEARANCE_DELAY;
	}

	/**
	 * Obtains the specified layer.
	 * @param layer the key identifying the layer
	 * @return the requested layer
	 */
	protected IFigure getLayer(Object layer) {
		return LayerManager.Helper.find(getParent()).getLayer(layer);
	}

	/**
	 * gets the popup bar descriptors
	 * @return list
	 */
	protected List<PopupBarDescriptor> getPopupBarDescriptors() {
		return myPopupBarDescriptors;
	}

	/**
	 * Hides the balloon immediately.
	 */
	public void hide() {
		if (getBalloon() != null) {

			teardownPopupBar();
		}

	}

	/**
	 * Hides the balloon after a certain amount of time has passed.
	 * 
	 * @param delay
	 *            the delay in milliseconds
	 */
	protected void hideAfterDelay(int delay) {
		if (isShowing()) {
			Display.getCurrent().timerExec(delay, new Runnable() {

				public void run() {
					hide();

				}});
		}
	}

	/**
	 * initialize the popup bars from the list of action descriptors.
	 */
	private void initPopupBars() {

		List<PopupBarDescriptor> theList = getPopupBarDescriptors();
		if (theList.isEmpty()) {
			return;
		}
		myBalloon = createPopupBarFigure();
		
		int xTotal = ACTION_MARGIN_RIGHT;
		int yTotal =  2 * ACTION_BUTTON_START_Y + ACTION_WIDTH_HGT;
		

		int xLoc = ACTION_BUTTON_START_X;
		int yLoc = ACTION_BUTTON_START_Y;

		for (PopupBarDescriptor theDesc : theList) {

			Label b = new Label(theDesc.getToolTip(),theDesc.getIcon());

			Rectangle r1 = new Rectangle();
			r1.setLocation(xLoc, yLoc);
			b.setFont(parent.getFigure().getFont());
			r1.setSize(b.getPreferredSize());
			
			b.setBounds(r1);
			xLoc += ACTION_WIDTH_HGT;
			getBalloon().add(b);
			xTotal+= r1.width;
			yTotal = yTotal < r1.height ? r1.height : yTotal;
		}
		
		getBalloon().setSize(xTotal,
				yTotal);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.gmf.runtime.diagram.ui.editpolicies.DiagramAssistantEditPolicy#isDiagramAssistantShowing()
	 */
	public boolean isShowing() {
		return getBalloon() != null;
	}

	/**
	 * check thee right display status
	 * @return true or false
	 */
	protected boolean isRightDisplay() {
		return (BALLOON_X_OFFSET_RHS == myBallonOffsetPercent);
	}

	/**
	 * This is the default which places the popup bar to favor the right side
	 * of the shape
	 * 
	 */
	protected void setRightHandDisplay() {
		this.myBallonOffsetPercent = BALLOON_X_OFFSET_RHS;
	}

	/**
	 * Populates the popup bar with popup bar descriptors added by suclassing
	 * this editpolicy (i.e. <code>fillPopupBarDescriptors</code> and by
	 * querying the modeling assistant service for all types supported on the
	 * popup bar of this host. For those types added by the modeling assistant
	 * service the icons are retrieved using the Icon Service.
	 */
	protected void populatePopupBars() {
		fillPopupBarDescriptors();
		// nothing more.
	}
	/**
	 * register the part then
	 * shows immediately the ballon at the given reference point.
	 * @param referencePoint
	 */
	public void showBalloon(Point referencePoint, IGraphicalEditPart part) {
		partHashCode = part.hashCode();
		showBalloon(referencePoint);
	}
	/**
	 * shows immediately the ballon at the given reference point.
	 * @param referencePoint
	 */
	public void showBalloon(Point referencePoint) {

		
		// already have one
		if (getBalloon() != null
//				&& getBalloon().getParent() != null
				) {
			return;
		}


		populatePopupBars();
		initPopupBars();

		if (myPopupBarDescriptors.isEmpty()) {
			return; // nothing to show
		}

		// the feedback layer figures do not recieve mouse events so do not use
		// it for popup bars
		IFigure layer = getLayer(LayerConstants.FEEDBACK_LAYER);
		layer.add(getBalloon());

		if (referencePoint == null) {
			throw new IllegalArgumentException("Reference point is null");
		}
		setLocation(referencePoint);
		// dismiss the popup bar after a delay
		hideAfterDelay(getDisappearanceDelay());
	}

	/**
	 * Destroys the balloon and disposes the images,
	 * except the error image from SharedImages.IMG_ERROR.
	 */
	private void teardownPopupBar() {
		IFigure layer = getLayer(LayerConstants.FEEDBACK_LAYER);
		if (myBalloon.getParent() != null) {
			layer.remove(myBalloon);
		} 
		myBalloon = null;
		for (PopupBarDescriptor desc : myPopupBarDescriptors) {
			if (desc.shouldBeDisposed()) {
				imagesToBeDisposed.add(desc.getIcon()); 
			}
		}
		this.myPopupBarDescriptors.clear();
		setRightHandDisplay(); // set back to default

		for (Image img : imagesToBeDisposed) {
			img.dispose();
		}
		imagesToBeDisposed.clear();

	}
	/**
	 * @return the parent
	 */
	public IGraphicalEditPart getParent() {
		return parent;
	}
	
	/**
	 * @param the graphical edit part which is used to find the feedback layer.
	 */
	public void setParent(IGraphicalEditPart _parent) {
		if (parent.getTopGraphicEditPart() != _parent) {
			this.parent = _parent.getTopGraphicEditPart();
		}
	}
	
	public void setLocation(Point location) {
		location.y = location.y 
						- getBalloon().getBounds().height;
		location.x = location.x - 10;
		getBalloon().setLocation(location);
		
	}

	// not working...
//	public void translateToRelative(Point point) {
//		
//		System.err.println(point);
//		System.err.println(getLayer(LayerConstants.FEEDBACK_LAYER));
//		getLayer(LayerConstants.FEEDBACK_LAYER).translateToRelative(point);
//		System.err.println(point);
//		point.y = point.y - BALLOON_Y_OFFSET 
//		- getBalloon().getBounds().height;
//		getBalloon().setLocation(point);
//	}
	
	public boolean showsOnThisEditPart(IGraphicalEditPart part) {
		if (part != null && part.hashCode() == partHashCode) {
			return true;
		}
		return false;
	}
}
