/*******************************************************************************
 * 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.bpel.ui.util.ModelHelper;
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.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.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;


/**
 * This is the border for scope.
 */
public class ScopeBorder extends CollapsableBorder {
	
	// TODO: Move these or remove them
	static final int borderWidth = 1;
	static final int margin = 11;
	// space between the inside of the border and the contents
	static final int hBorderInsets = 11;
	static final int vBorderInsets = 16;
	// extra horizontal space to use when we have no children.
	static final int extraHorizontalSpace = 50;
	
	// Location of the "-" icons when the border is expanded
	private Rectangle rectExpandedTop, rectExpandedBottom;

	// The bounds of the border of the scope when expanded. Takes
	// into account space for the drawer.
	private Rectangle expandedBounds;
	
	// Fault handler, Compensation handler and Event handler support
	private int faultImageWidth, faultImageHeight;
	private int compensationImageWidth, compensationImageHeight;
	private int terminationImageWidth, terminationImageHeight;
	private int eventImageWidth, eventImageHeight;
	private Image compensationImage;
	private Image terminationImage;
	private Image faultImage;
	private Image eventImage;
	private boolean showFault;
	private boolean showCompensation;
	private boolean showTermination;
	private boolean showEvent;
	private Rectangle rectFault;
	private Rectangle rectCompensation;
	private Rectangle rectTermination;
	private Rectangle rectEvent;
	
	private IFigure faultImageFigure;
	private IFigure compensationImageFigure;
	private IFigure terminationImageFigure;
	private IFigure eventImageFigure;
	
	public ScopeBorder(IFigure parentFigure, String labelText, Image image) {
		super(true, IBPELUIConstants.ARC_WIDTH, parentFigure, labelText, image);
		
		// Initialize images for fault, compensation and event handler decorations
		this.faultImage = BPELUIPlugin.INSTANCE.getImage(IBPELUIConstants.ICON_FAULT_INDICATOR);
		org.eclipse.swt.graphics.Rectangle r = faultImage.getBounds();
		this.faultImageWidth = r.width;
		this.faultImageHeight = r.height;

		this.compensationImage = BPELUIPlugin.INSTANCE.getImage(IBPELUIConstants.ICON_COMPENSATION_INDICATOR);
		r = compensationImage.getBounds();
		this.compensationImageWidth = r.width;
		this.compensationImageHeight = r.height;

		this.terminationImage = BPELUIPlugin.INSTANCE.getImage(IBPELUIConstants.ICON_TERMINATION_INDICATOR);
		r = terminationImage.getBounds();
		this.terminationImageWidth = r.width;
		this.terminationImageHeight = r.height;

		this.eventImage = BPELUIPlugin.INSTANCE.getImage(IBPELUIConstants.ICON_EVENT_INDICATOR);
		r = eventImage.getBounds();
		this.eventImageWidth = r.width;
		this.eventImageHeight = r.height;
		
		faultImageFigure = new ImageFigure(faultImage);
		faultImageFigure.setParent(parentFigure);
		
		compensationImageFigure = new ImageFigure(compensationImage);
		compensationImageFigure.setParent(parentFigure);
		
		terminationImageFigure = new ImageFigure(terminationImage);
		terminationImageFigure.setParent(parentFigure);
		
		eventImageFigure = new ImageFigure(eventImage);
		eventImageFigure.setParent(parentFigure);
	}

	@Override
	public Dimension getPreferredSize(IFigure f) {
		calculate(f);
		Dimension d = new Dimension(rectCollapsed.width, rectCollapsed.height + expandedHeight);
		d.width += DRAWER_WIDTH * 2;
		if (!isCollapsed()) {
			d.width += extraHorizontalSpace;
		}
		return d;
	}
	
	@Override
	protected void doPaint(IFigure figure, Graphics graphics, Insets insets) {
		super.doPaint(figure, graphics, insets);
		
		ColorRegistry registry = BPELUIPlugin.INSTANCE.getColorRegistry();

		Color old = graphics.getForegroundColor();
		graphics.setForegroundColor(registry.get(IBPELUIConstants.COLOR_SCOPE_BORDER));
		graphics.drawRoundRectangle(expandedBounds, IBPELUIConstants.ARC_WIDTH, IBPELUIConstants.ARC_WIDTH);
		graphics.drawRectangle(expandedBounds.x, expandedBounds.y, expandedBounds.width, 3);
		graphics.setForegroundColor(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
		graphics.drawRectangle(expandedBounds.x + 1, expandedBounds.y + 1, expandedBounds.width - 2, 1);
		graphics.setForegroundColor(old);
		if (isCollapsed()) {
			graphics.drawImage(collapsedImage, rectCollapsed.getLocation());
		} else {
			graphics.drawImage(expandedImage, rectExpandedTop.getLocation());
			graphics.drawImage(expandedImage, rectExpandedBottom.getLocation());
		}
		
		Rectangle oldClip = new Rectangle();
		oldClip = graphics.getClip(oldClip);

		// Draw the fault image in the upper right hand corner of the round rectangle
		if (showFault) {
			graphics.setClip(faultImageFigure.getBounds().getCopy());
			faultImageFigure.paint(graphics);
			graphics.setClip(oldClip);
		}
		// Draw the compensation image in the upper right hand corner of the round rectangle,
		// leaving room for the fault image.
		if (showCompensation) {
			graphics.setClip(compensationImageFigure.getBounds().getCopy());
			compensationImageFigure.paint(graphics);
			graphics.setClip(oldClip);
		}
		// Draw the termination image in the upper right hand corner of the round rectangle,
		// leaving room for the fault image.
		if (showTermination) {
			graphics.setClip(terminationImageFigure.getBounds().getCopy());
			terminationImageFigure.paint(graphics);
			graphics.setClip(oldClip);
		}
		// Draw the event image in the upper right hand corner of the round rectangle,
		// leaving room for fault and compensation.
		if (showEvent) {
			graphics.setClip(eventImageFigure.getBounds().getCopy());
			eventImageFigure.paint(graphics);
			graphics.setClip(oldClip);
		}
	}
	
	@Override
	protected void calculate(IFigure figure) {
		super.calculate(figure);

		// Bounds of the figure that we are given
		Rectangle figureBounds = figure.getBounds().getCopy();
		
		// area for plus/minus buttons
		rectExpandedTop = new Rectangle(figureBounds.x + figureBounds.width / 2 - expandedWidth/2, figureBounds.y, expandedWidth, expandedHeight);
		rectExpandedBottom = new Rectangle(figureBounds.x + figureBounds.width/2 - expandedWidth/2, figureBounds.y + figureBounds.height - expandedHeight - borderWidth, expandedWidth, expandedHeight);
				
		this.expandedBounds = figureBounds.getCopy();
		// Leave room for the drawers
		expandedBounds.x += DRAWER_WIDTH;
		expandedBounds.width -= DRAWER_WIDTH * 2;
		// Leave room on the top for part of the top expanded image
		expandedBounds.y += expandedHeight / 2 - 1;
		expandedBounds.height -= expandedHeight / 2 - 1;
		// Room for the bottom
		expandedBounds.height -= expandedHeight / 2;
		
		// Calculate the positions of the handler icons
		computeHandlerIconPositions(ModelHelper.isHorizontalLayout(getEditPart().getModel()),figure);

		// Top drawer
		IMarker topMarker = getTopMarker();
		if (topMarker != null) {
			// Draw the image for the top drawer
			if (isCollapsed()) {
				topDrawerLocation.x = expandedBounds.x - DRAWER_WIDTH + DRAWER_INSET;
				topDrawerLocation.y = expandedBounds.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 = expandedBounds.x - DRAWER_WIDTH + DRAWER_INSET;
				bottomDrawerLocation.y = expandedBounds.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 = expandedBounds.x - DRAWER_WIDTH + DRAWER_INSET + 1;
				topImageLocation.y = expandedBounds.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 = expandedBounds.x - DRAWER_WIDTH + DRAWER_INSET + 1;
				bottomImageLocation.y = expandedBounds.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;
			}
		}
	}
	

	@Override
	public Insets getInsets(IFigure figure) {
		calculate(figure);
		Insets result;
		// TODO: Fix
		if (isCollapsed()) {
			result = new Insets(borderWidth + margin, borderWidth + margin + DRAWER_WIDTH, borderWidth + margin, borderWidth + margin + DRAWER_WIDTH);
		} else {	
			result = new Insets(vBorderInsets + expandedHeight, hBorderInsets, vBorderInsets + expandedHeight, hBorderInsets);
			result.left += DRAWER_WIDTH;
			result.right += DRAWER_WIDTH;
		}
		return result;
	}
	
	/**
	 * Throw away values that determine the layout
	 */
	@Override
	public void invalidate() {
		rectExpandedTop = null;
		rectExpandedBottom = null;
	}

	public boolean isPointInFaultImage(int x, int y) {
		if (showFault) {
			Point p = new Point(x, y);
			parentFigure.translateToRelative(p);
			return rectFault.contains(p);
		}
		return false;
	}	

	public boolean isPointInCompensationImage(int x, int y) {
		if (showCompensation) {
			Point p = new Point(x, y);
			parentFigure.translateToRelative(p);
			return rectCompensation.contains(p);
		}
		return false;
	}	

	public boolean isPointInTerminationImage(int x, int y) {
		if (showTermination) {
			Point p = new Point(x, y);
			parentFigure.translateToRelative(p);
			return rectTermination.contains(p);
		}
		return false;
	}

	public boolean isPointInEventImage(int x, int y) {
		if (showEvent) {
			Point p = new Point(x, y);
			parentFigure.translateToRelative(p);
			return rectEvent.contains(p);
		}
		return false;
	}	

	public void setShowEvent(boolean showEvent) {
		this.showEvent = showEvent;
	}

	public void setShowCompensation(boolean showCompensation) {
		this.showCompensation = showCompensation;
	}

	public void setShowTermination(boolean showTermination) {
		this.showTermination = showTermination;
	}

	public void setShowFault(boolean showFault) {
		this.showFault = showFault;
	}
	
	/**
	 * 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;
	}
	
	private void computeHandlerIconPositions(boolean horizontal, IFigure figure){
		if(horizontal){
			// Calculate the location of the fault/event/compensation decorations
			if (showFault) {
				int x = expandedBounds.x + expandedBounds.width - faultImageWidth;
				if(showCompensation)
					x -= compensationImageWidth;
				if(showTermination)
					x -= terminationImageWidth;
				if(showEvent)
					x -= eventImageWidth;
				this.rectFault = new Rectangle(x, figure.getBounds().y+figure.getBounds().height-faultImageHeight, faultImageWidth, faultImageHeight);
			}
			if (showCompensation) {
				int x = expandedBounds.x + expandedBounds.width - compensationImageWidth;
				if(showTermination)
					x -= terminationImageWidth;
				if(showEvent)
					x -= eventImageWidth;
			 	this.rectCompensation = new Rectangle(x, figure.getBounds().y+figure.getBounds().height-compensationImageHeight, compensationImageWidth, compensationImageHeight);
			}
			if (showTermination) {
				int x = expandedBounds.x + expandedBounds.width - terminationImageWidth;
				if(showEvent)
					x -= eventImageWidth;
			 	this.rectTermination = new Rectangle(x, figure.getBounds().y+figure.getBounds().height-terminationImageHeight, terminationImageWidth, terminationImageHeight);
			}
			if (showEvent) {
				int x = expandedBounds.x + expandedBounds.width - eventImageWidth;
				this.rectEvent = new Rectangle(x, figure.getBounds().y+figure.getBounds().height-eventImageHeight, eventImageWidth, eventImageHeight);
			}
		}else{
			// Calculate the location of the fault/event/compensation decorations
			// Draw the fault image in the upper right hand corner of the round rectangle
			if (showFault) {
				int x = expandedBounds.x + expandedBounds.width - faultImageWidth +1;
				this.rectFault = new Rectangle(x, expandedBounds.y, faultImageWidth, faultImageHeight);
			}
			// Draw the compensation image in the upper right hand corner of the round rectangle,
			// leaving room for the fault image.
			if (showCompensation) {
				int x = expandedBounds.x + expandedBounds.width - compensationImageWidth +1;
				int y = expandedBounds.y;
				if (showFault) {
					y += faultImageHeight;
				}
			 	this.rectCompensation = new Rectangle(x, y, compensationImageWidth, compensationImageHeight);
			}
			// Draw the termination image in the upper right hand corner of the round rectangle,
			// leaving room for fault and compensation.
			if (showTermination) {
				int x = expandedBounds.x + expandedBounds.width - terminationImageWidth +1;
				int y = expandedBounds.y;
				if (showFault) {
					y += faultImageWidth;
				}
				if (showCompensation) {
					y += compensationImageWidth;
				}
			 	this.rectTermination = new Rectangle(x, y, terminationImageWidth, terminationImageHeight);
			}
			// Draw the event image in the upper right hand corner of the round rectangle,
			// leaving room for fault, termination and compensation.
			if (showEvent) {
				int x = expandedBounds.x + expandedBounds.width - eventImageWidth+1;
				int y = expandedBounds.y;
				if (showFault) {
					y += faultImageHeight;
				}
				if (showCompensation) {
					y += compensationImageHeight;
				}
				if (showTermination) {
					y += terminationImageWidth;
				}
				this.rectEvent = new Rectangle(x, y, eventImageWidth, eventImageHeight);
			}	
		}
		
		// Apply the bounds to the figures
		if(showFault)
			this.faultImageFigure.setBounds(rectFault);
		if(showCompensation)
			this.compensationImageFigure.setBounds(rectCompensation);
		if(showTermination)
			this.terminationImageFigure.setBounds(rectTermination);
		if(showEvent)
			this.eventImageFigure.setBounds(rectEvent);
	}

	public IFigure getFaultImageFigure() {
		return faultImageFigure;
	}

	public IFigure getCompensationImageFigure() {
		return compensationImageFigure;
	}

	public IFigure getTerminationImageFigure() {
		return terminationImageFigure;
	}

	public IFigure getEventImageFigure() {
		return eventImageFigure;
	}
}