/*******************************************************************************
 * 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.RoundedRectangle;
import org.eclipse.draw2d.geometry.Dimension;
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.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;


/**
 * This is the border for sequence, switch, pick and while.
 * 
 * When expanded it displays a label and text at the top as well as showing the
 * contents.
 */
public class ContainerBorder extends CollapsableBorder {
	
	// The horizontal margin between the border and the image/text 
	private static final int leftMargin = 6;
	private static final int rightMargin = 10;
	// The vertical margin between the border and the image/text
	private static final int topMargin = 6;
	private static final int bottomMargin = 3;
	
	// space between image and label
	static final int spacing = 5;
	static final int borderWidth = 1;
	static final int margin = 11;
	
	// space between the inside of the border and the contents
	static final int hBorderInsets = 3;
	static final int vBorderInsets = 22;
	
	// We keep this round rect around to paint.
	private RoundedRectangle roundRect;
	
	// The bounds of the "-" icon at the top and bottom of the expanded figure
	private Rectangle rectExpandedTop, rectExpandedBottom;

	// The calculated bounds of the label and the image when expanded
	private Rectangle rectLabel, rectImageLabel;

	// The bounds of the round rectangle surrounding the label and image 
	// when expanded
	private Rectangle rectLabelBorder;
	
	// The bounds of the rectangle surrounding the children when expanded.
	// Computed to take into account drawers.
	private Rectangle expandedBounds;
	
	public ContainerBorder(IFigure parentFigure, Image image, String labelText) {
		super(true, IBPELUIConstants.ARC_WIDTH, parentFigure, labelText, image);

		this.roundRect = new RoundedRectangle();
		this.roundRect.setOpaque(true);
		this.roundRect.setCornerDimensions(new Dimension(IBPELUIConstants.ARC_WIDTH, IBPELUIConstants.ARC_WIDTH));
	}

	@Override
	public Dimension getPreferredSize(IFigure f) {
		calculate(f);
		Dimension d = new Dimension(rectLabelBorder.getSize().width, rectLabelBorder.getSize().height + expandedHeight);
		d.width += DRAWER_WIDTH * 2;
		if (!isCollapsed()) {
			d.width += IBPELUIConstants.ARC_WIDTH * 2;
		}
		return d;
	}
	
	@Override
	protected void doPaint(IFigure figure, Graphics graphics, Insets insets) {
		super.doPaint(figure, graphics, insets);
		ColorRegistry registry = BPELUIPlugin.INSTANCE.getColorRegistry();
		graphics.setForegroundColor(registry.get(IBPELUIConstants.COLOR_COMPOSITE_ACTIVITY_BORDER));
		if (!isCollapsed()) {
			// Paint the round rectangle around the lower part of the figure (surrounding the
			// children figures).
			graphics.drawRoundRectangle(expandedBounds, IBPELUIConstants.ARC_WIDTH, IBPELUIConstants.ARC_WIDTH);
		}
				
		// Paint the round rectangle at the top.
		// First determine whether or not square corners are needed on the left edge.
		boolean needSquareCorners = (getTopMarker() != null) || (getBottomMarker() != null);		
		if (isCollapsed() && needSquareCorners) {
			// Remember the clipping rectangle
			Rectangle oldClip = new Rectangle();
			oldClip = graphics.getClip(oldClip);

			Rectangle clippingRect = new Rectangle(rectLabelBorder.x + rectLabelBorder.width / 2, rectLabelBorder.y, rectLabelBorder.width / 2 + 2, rectLabelBorder.height + 1);
			graphics.setClip(clippingRect);
			graphics.drawRoundRectangle(rectLabelBorder, IBPELUIConstants.ARC_WIDTH, IBPELUIConstants.ARC_WIDTH);
			clippingRect = new Rectangle(rectLabelBorder.x, rectLabelBorder.y, rectLabelBorder.width / 2 + 1, rectLabelBorder.height + 1);
			graphics.setClip(clippingRect);
			graphics.drawRectangle(rectLabelBorder);
			
			// Restore the clipping
			graphics.setClip(oldClip);
		} else {
			roundRect.setBounds(rectLabelBorder);
			roundRect.setForegroundColor(BPELUIPlugin.INSTANCE.getColorRegistry().get(IBPELUIConstants.COLOR_ACTIVITY_BORDER));
			roundRect.paint(graphics);
		}
		
		Color old = graphics.getForegroundColor();
		graphics.setForegroundColor(registry.get(IBPELUIConstants.COLOR_BLACK));
		collapsedNameLabel.setBounds(rectLabel);
		collapsedNameLabel.paint(graphics);
		imageLabel.setBounds(rectImageLabel);
		imageLabel.paint(graphics);
		graphics.setForegroundColor(old);
				
		if (isCollapsed()) {
			graphics.drawImage(collapsedImage, rectCollapsed.getLocation());
		} else {
			graphics.drawImage(expandedImage, rectExpandedTop.getLocation());
			graphics.drawImage(expandedImage, rectExpandedBottom.getLocation());
		}			
	}
	
	// Initialize a bunch of location and size variables based on the contents
	@Override
	protected void calculate(IFigure figure) {
		super.calculate(figure);

		// bounds of the figure that we are given
		Rectangle figureBounds = figure.getBounds().getCopy();
		
		// preferred size of the image
		Dimension imageLabelSize = imageLabel.getPreferredSize().getCopy();
		// preferred size of the text label
		Dimension labelSize = collapsedNameLabel.getPreferredSize().getCopy();
		
		// calculate the label border for the round rectangle
		// surrounding the label and image
		int w = labelSize.width + spacing + imageLabelSize.width;
		int h = Math.max(labelSize.height, imageLabelSize.height);
		int x = figureBounds.x + figureBounds.width / 2 - (w/2);
		int y = figureBounds.y;
		rectLabelBorder = new Rectangle(x, y, w, h);

		// expand the border for aesthetics and to account for arc size.
		// note we don't use the entire arc size to conserve space.
		// also remember we can't expand in the upwards direction.
		int verticalMargin = topMargin + bottomMargin;
		if (isCollapsed()) verticalMargin--;
		rectLabelBorder.expand(new Insets(0, leftMargin, verticalMargin, rightMargin));
		
		// rectangle for image label
		x = rectLabelBorder.x + leftMargin;
		y = rectLabelBorder.y + topMargin;
		w = imageLabelSize.width;
		h = imageLabelSize.height;
		rectImageLabel = new Rectangle(x, y, w, h);
					
		// rectangle for text label	
		x = rectLabelBorder.x + leftMargin + imageLabelSize.width + spacing;
		y = rectLabelBorder.y + topMargin;
		w = labelSize.width;
		h = labelSize.height;
		rectLabel = new Rectangle(x, y, w, h);
		
		// calculate the size of the round rectangle surrounding the children,
		// taking into account arc size and drawer width
		this.expandedBounds = figureBounds.getCopy();
		// adjust for drawer width
		expandedBounds.x += DRAWER_WIDTH;
		expandedBounds.width -= DRAWER_WIDTH * 2;
		// adjust for half height of the label and image
		expandedBounds.y += rectLabelBorder.height / 2;
		expandedBounds.height -= rectLabelBorder.height / 2;
		// subtract half the height of the minus button at the bottom.
		expandedBounds.height -= expandedHeight / 2;
		
		// area for plus/minus buttons
		rectExpandedTop = new Rectangle(rectLabelBorder.x + rectLabelBorder.width/2 - expandedWidth/2, rectLabelBorder.y + rectLabelBorder.height - 1, expandedWidth, expandedHeight);
		rectExpandedBottom = new Rectangle(figureBounds.x + figureBounds.width / 2 - expandedWidth / 2, figureBounds.y + figureBounds.height - expandedHeight, expandedWidth, expandedHeight);
		
		// Top drawer
		IMarker topMarker = getTopMarker();
		if (topMarker != null) {
			// Draw the image for the top drawer
			if (isCollapsed()) {
				topDrawerLocation.x = collapsedRectangle.x - DRAWER_WIDTH + DRAWER_INSET + 1;
				topDrawerLocation.y = collapsedRectangle.y;
			} else {
				topDrawerLocation.x = expandedBounds.x - DRAWER_WIDTH;
				topDrawerLocation.y = expandedBounds.y + IBPELUIConstants.ARC_WIDTH;
			}
		}
		// Bottom drawer
		IMarker bottomMarker = getBottomMarker();
		if (bottomMarker != null) {
			// Draw the image for the bottom drawer
			if (isCollapsed()) {
				bottomDrawerLocation.x = collapsedRectangle.x - DRAWER_WIDTH + DRAWER_INSET + 1;
				bottomDrawerLocation.y = collapsedRectangle.y + DRAWER_HALF_HEIGHT;
			} else {
				bottomDrawerLocation.x = expandedBounds.x - DRAWER_WIDTH;
				bottomDrawerLocation.y = expandedBounds.y + DRAWER_HALF_HEIGHT + IBPELUIConstants.ARC_WIDTH;
			}
		}
		// Top drawer marker image
		Image topImage = getTopImage();
		if (topImage != null) {
			if (isCollapsed()) {
				topImageLocation.x = collapsedRectangle.x - DRAWER_WIDTH + DRAWER_INSET + 2;
				topImageLocation.y = collapsedRectangle.y + DRAWER_INSET;
			} else {
				topImageLocation.x = expandedBounds.x - DRAWER_WIDTH + DRAWER_INSET;
				topImageLocation.y = expandedBounds.y + IBPELUIConstants.ARC_WIDTH + DRAWER_INSET;
			}
		}
		// Bottom drawer marker image
		Image bottomImage = getBottomImage();
		if (bottomImage != null) {
			if (isCollapsed()) {
				bottomImageLocation.x = collapsedRectangle.x - DRAWER_WIDTH + DRAWER_INSET + 2;
				bottomImageLocation.y = collapsedRectangle.y + DRAWER_INSET + DRAWER_HALF_HEIGHT;
			} else {
				bottomImageLocation.x = expandedBounds.x - DRAWER_WIDTH + DRAWER_INSET;
				bottomImageLocation.y = expandedBounds.y + IBPELUIConstants.ARC_WIDTH + DRAWER_HALF_HEIGHT + DRAWER_INSET;
			}
		}
		
	}
	
	/**
	 * Compute the insets. We only know how to do this for expanded borders,
	 * so delegate to super in the collapsed state.
	 */
	@Override
	public Insets getInsets(IFigure figure) {
		if (isCollapsed()) return super.getInsets(figure);
		calculate(figure);
		Insets result = new Insets(vBorderInsets + rectLabelBorder.height, hBorderInsets, vBorderInsets, hBorderInsets);
		result.left += DRAWER_WIDTH;
		result.right += DRAWER_WIDTH;
		return result;
	}

	/**
	 * throw away values that determine the layout
	 */
	@Override
	public void invalidate() {
		super.invalidate();
		rectLabelBorder = null;
		rectLabel = null;
		rectExpandedTop = null;
		rectExpandedBottom = null;
	}

	/**
	 * Tests whether the given point is inside the collapse image. The superclass
	 * does not know where the collapse image(s) is located.
	 */
	@Override
	public boolean isPointInCollapseImage(int x, int y) {
		if (isCollapsed()) return super.isPointInCollapseImage(x, y);
		Point p = new Point(x,y);
		parentFigure.translateToRelative(p);
		Rectangle rect = rectExpandedTop.getCopy();
		rect.expand(new Insets(1,1,1,1));
		if (rect.contains(p)) return true;
		if (!isCollapsed()) {
			rect = rectExpandedBottom.getCopy();
			rect.expand(new Insets(1,1,1,1));
			return rect.contains(p);
		}
		return false;
	}
	
	/**
	 * Provide gradient rectangle.
	 */
	@Override
	protected Rectangle getGradientRect() {
		if (isCollapsed()) return super.getGradientRect();
		invalidate();
		calculate(parentFigure);
		return expandedBounds;
	}
	
	/**
	 * Edit parts may like to know what the top inset is - this is
	 * the distance between the top of the border and the top of the
	 * container round rectangle. This is useful because this is where
	 * selection handles are often located.
	 */
	public int getTopInset() {
		// HACK! This is necessary to prevent certain NPEs.
		if (rectLabelBorder == null || expandedBounds == null) {
			calculate(parentFigure);
		}
		
		return expandedBounds.y - rectLabelBorder.y;
	}
}
