/*******************************************************************************
 * Copyright (c) 2005 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.ui.editparts.borders;

import org.eclipse.bpel.ui.BPELUIPlugin;
import org.eclipse.bpel.ui.IBPELUIConstants;
import org.eclipse.core.resources.IMarker;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.ImageFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.graphics.Image;


/**
 * This border knows about collapsability. Specifically, it has icons for expand
 * and collapse, does hit testing for these icons, has an image and label to use
 * when collapsed, and does complete rendering when collapsed to look similar to
 * a leaf border (except with the addition of an expand icon).
 * 
 * It does not do rendering for the expanded state. The rationale is that things
 * should look similar when collapsed (like a leaf node) but may have different
 * appearances when expanded.
 */
public abstract class CollapsableBorder extends GradientBorder {

	// Whether or not we are collapsed.
	private boolean collapsed = false;
	// The location of the collapsed image (+).
	protected Rectangle rectCollapsed;
	// The images for collapsed (+) and expanded (-).
	protected Image collapsedImage, expandedImage;
	// The width and height of the expanded image. Collapsed should be the same.
	protected int expandedWidth, expandedHeight;
	// The label to use when collapsed. Subclasses may also use it when expanded.
	protected Label collapsedNameLabel = null;
	// The image to use when collapsed. Subclasses may also use it when expanded.
	protected Image image;
	// The label containing the image.
	protected ImageFigure imageLabel = null;
	// Our parent figure. Used for relative location calculation.
	protected IFigure parentFigure;
	// The calculated rectangle for the collapsed border. This is null
	// when not collapsed, so subclasses shouldn't assume it's there.
	protected Rectangle collapsedBounds;
	// The calculated location of the top and bottom drawer images as well as
	// the top and bottom drawers themselves.
	protected Point topImageLocation = new Point(), bottomImageLocation = new Point();
	protected Point topDrawerLocation = new Point(), bottomDrawerLocation = new Point();
	// The calculated bounds of the rectangle to draw when collapsed
	protected Rectangle collapsedRectangle;
	
	public CollapsableBorder(boolean isVertical, int arcWidth, IFigure parentFigure, String labelText, Image image) {
		super(isVertical, arcWidth);
		this.parentFigure = parentFigure;
		
		this.image = image;
		this.imageLabel = new ImageFigure(image);

		this.collapsedImage = BPELUIPlugin.INSTANCE.getImage(IBPELUIConstants.ICON_FIGURE_COLLAPSED);
		this.expandedImage = BPELUIPlugin.INSTANCE.getImage(IBPELUIConstants.ICON_FIGURE_EXPANDED);
		this.expandedHeight = expandedImage.getBounds().height;
		this.expandedWidth = expandedImage.getBounds().width;
		
		this.collapsedNameLabel = new Label(labelText);
		this.collapsedNameLabel.setFont(JFaceResources.getFontRegistry().get(JFaceResources.DEFAULT_FONT));
	}
	
	/**
	 * Tests whether the given point is inside the collapsed image.
	 * The superclass only knows where the collapsed image is located; if subclasses
	 * want to do hit testing on the expanded image, they may override but may want
	 * to call super if we are collapsed.
	 */
	public boolean isPointInCollapseImage(int x, int y) {
		if (!isCollapsed()) return false;
		Point p = new Point(x,y);
		parentFigure.translateToRelative(p);
		Rectangle rect = rectCollapsed.getCopy();
		rect.expand(new Insets(1,1,1,1));
		return rect.contains(p);
	}
	
	public boolean isCollapsed() {
		return collapsed;
	}
	
	public void setCollapsed(boolean showCollapse) {
		this.collapsed = showCollapse;
	}

	/**
	 * Throw away computed values that determine the layout
	 */
	public void invalidate() {
		// rectCollapsed = null;
		collapsedBounds = null;
	}
	
	public Label getLabel() {
		return collapsedNameLabel;
	}

	public void setName(String name) {
		collapsedNameLabel.setText(name);
	}
	
	protected void calculate(IFigure figure) {
		// Calculate the coordinates for collapsed state, even though
		// we may currently be expanded.
		if (collapsedBounds == null) {
			collapsedBounds = figure.getBounds().getCopy();			
			this.collapsedRectangle = collapsedBounds.getCopy();				

			// Leave room on the left and right for the drawer.
			collapsedRectangle.x += DRAWER_WIDTH;
			collapsedRectangle.width -= DRAWER_WIDTH * 2;				

			// area for collapsed button
			rectCollapsed = new Rectangle(collapsedBounds.x + collapsedBounds.width / 2 - expandedWidth/2, collapsedBounds.y + collapsedBounds.height - expandedHeight, expandedWidth, expandedHeight);
		}
	}

	/**
	 * This method first invalidates and recalculates any bounds, and then
	 * paints the top and bottom drawers, as well as any markers contained within them.
	 */
	@Override
	public final void paint(IFigure figure, Graphics graphics, Insets insets) {
		invalidate();
		calculate(figure);
		doPaint(figure, graphics, insets);
	}

	/**
	 * Subclasses should call this paint method unless there is a very good
	 * reason for overriding its behaviour. To affect where various things
	 * appear, override the calculate() method.
	 */
	protected void doPaint(IFigure figure, Graphics graphics, Insets insets) {
		// Remember the clipping rectangle
		Rectangle oldClip = new Rectangle();
		oldClip = graphics.getClip(oldClip);

		IMarker topMarker = getTopMarker();
		IMarker bottomMarker = getBottomMarker();
		if (topMarker != null || bottomMarker != null) {
			ColorRegistry registry = BPELUIPlugin.INSTANCE.getColorRegistry();
			graphics.setForegroundColor(registry.get(IBPELUIConstants.COLOR_ACTIVITY_BORDER));
			Rectangle clippingRect;
			if (bottomMarker == null) {
				clippingRect = new Rectangle(topDrawerLocation.x, topDrawerLocation.y, DRAWER_WIDTH, DRAWER_HALF_HEIGHT + 1);
			} else if (topMarker == null) {
				clippingRect = new Rectangle(bottomDrawerLocation.x, bottomDrawerLocation.y, DRAWER_WIDTH, DRAWER_HALF_HEIGHT + 1);
			} else {
				clippingRect = new Rectangle(topDrawerLocation.x, topDrawerLocation.y, DRAWER_WIDTH, DRAWER_HEIGHT + 1);
			}
			graphics.setClip(clippingRect);
			// -1 due to GEF
			graphics.drawRoundRectangle(new Rectangle(topDrawerLocation.x + DRAWER_INSET, topDrawerLocation.y, DRAWER_WIDTH * 2, DRAWER_HEIGHT - 1), IBPELUIConstants.ARC_WIDTH, IBPELUIConstants.ARC_WIDTH);
			graphics.setClip(oldClip);
			if (bottomMarker == null || topMarker == null) {
				graphics.drawLine(topDrawerLocation.x + DRAWER_INSET, topDrawerLocation.y + DRAWER_HALF_HEIGHT, topDrawerLocation.x + DRAWER_WIDTH, topDrawerLocation.y + DRAWER_HALF_HEIGHT);
			}
			
		}
		
		// Draw the actual breakpoints
		Image topImage = getTopImage();
		if (topImage != null) {
			graphics.drawImage(topImage, topImageLocation);
		}
		Image bottomImage = getBottomImage();
		if (bottomImage != null) {
			graphics.drawImage(bottomImage, bottomImageLocation);
		}
	}	

	/**
	 * We only know about the gradient rectangle when we are collapsed.
	 * TODO: Do we need a gradient rectangle when collapsed?
	 */
	@Override
	protected Rectangle getGradientRect() {
		return new Rectangle(0,0,0,0);
	}
	
	@Override
	public boolean isPointInTopDrawer(int x, int y) {
		if (getTopMarker() == null) return false;
		Point p = new Point(x, y);
		Image image = getTopImage();
		org.eclipse.swt.graphics.Rectangle imageSize = image.getBounds();
		Rectangle imageBounds = new Rectangle(topImageLocation.x, topImageLocation.y, imageSize.width, imageSize.height);
		return imageBounds.contains(p);
	}
	
	@Override
	public boolean isPointInBottomDrawer(int x, int y) {
		if (getBottomMarker() == null) return false;
		Point p = new Point(x, y);
		Image image = getBottomImage();
		org.eclipse.swt.graphics.Rectangle imageSize = image.getBounds();
		Rectangle imageBounds = new Rectangle(bottomImageLocation.x, bottomImageLocation.y, imageSize.width, imageSize.height);
		return imageBounds.contains(p);
	}
	
	/**
	 * Calculate the insets when in the collapsed state. In expanded state, we don't
	 * know how to answer. Insets don't matter in the collapsed state because there
	 * are no children.
	 */
	@Override
	public Insets getInsets(IFigure figure) {
		return new Insets(0, 0, 0, 0);
	}	
}
