/*******************************************************************************
 * 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;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.bpel.common.ui.markers.IModelMarkerConstants;
import org.eclipse.bpel.model.Activity;
import org.eclipse.bpel.model.Link;
import org.eclipse.bpel.model.Source;
import org.eclipse.bpel.model.Sources;
import org.eclipse.bpel.model.Target;
import org.eclipse.bpel.model.Targets;
import org.eclipse.bpel.ui.BPELUIPlugin;
import org.eclipse.bpel.ui.GraphicalBPELRootEditPart;
import org.eclipse.bpel.ui.IBPELUIConstants;
import org.eclipse.bpel.ui.IHoverHelper;
import org.eclipse.bpel.ui.adapters.IMarkerHolder;
import org.eclipse.bpel.ui.editparts.borders.DrawerBorder;
import org.eclipse.bpel.ui.editparts.policies.BPELGraphicalEditPolicy;
import org.eclipse.bpel.ui.extensions.BPELUIRegistry;
import org.eclipse.bpel.ui.figures.CenteredConnectionAnchor;
import org.eclipse.bpel.ui.util.BPELUtil;
import org.eclipse.bpel.ui.util.ModelHelper;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.NodeEditPart;
import org.eclipse.gef.Request;


/**
 * ActivityEditPart is the graphical GEF edit part representing a BPEL Activity.
 * It implements NodeEditPart because activities can be the source and the target of
 * connections in the graph. These connections include flow links as well as implicit
 * control flow connections, often determined by the node's parent.
 */
public abstract class ActivityEditPart extends BPELEditPart implements NodeEditPart {

	private MouseMotionListener mouseMotionListener;
	
	/**
	 * Brand new shiny ActivityEditPart.
	 */
	public ActivityEditPart() {
		super();
	}

	@Override
	protected void addAllAdapters() {
		super.addAllAdapters();
		
		Sources sources = getActivity().getSources();
		
		
		if (sources != null) {
			adapter.addToObject(sources);
			for(Source source : sources.getChildren()) {
				adapter.addToObject(source);
			}
//			
//			for (Iterator it = sources.getChildren().iterator(); it.hasNext(); ) {
//				Source source = (Source)it.next();
//				// also include the link, if there is one (since we indirectly
//				// control the activation of the LinkEditPart)
//				if (source.getLink() != null) adapter.addToObject(source.getLink());
//
//				// Okay--the real problem here, is that the Activity might be
//				// referred to by a Source object, but the Activity is not going
//				// to find out about the creation of a new Sources that references
//				// it.  Therefore, our model listeners don't know what to do!
//				
//				// TODO: temporarily hacked around in FlowEditPart.FlowContentAdapter.
//				
//				// TODO: also include any parent flows, and the Links object of
//				// any parent flows that have one.  !
//				// TODO: in future, use a global listener to handle refreshing the
//				// correct source editpart.
//			}			
		}

		Targets targets = getActivity().getTargets();
		if (targets != null) {
			adapter.addToObject(targets);
			for( Target target : targets.getChildren()) {
				adapter.addToObject(target);
			}						
		}
	}
	
	/**
	 * Install the BPELGraphicalEditPolicy as GRAPHICAL_NODE_ROLE. This policy allows
	 * the graphical creation of flow links between activities.
	 */
	@Override
	protected void createEditPolicies() {
		super.createEditPolicies();
		installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new BPELGraphicalEditPolicy());
	}

	/**
	 * The model is the source of the following connections:
	 * - Any flow links that the model is the source of
	 * - Any other connections that the model's parent determines the model is the source of
	 */	
	@Override
	protected List<Link> getModelSourceConnections() {
		Sources sources = getActivity().getSources();
		if (sources == null) {
			return Collections.emptyList();
		}
		List<Link> result = new ArrayList<Link>();
		for(Source source : sources.getChildren()) {
			if (source.getLink() != null) {
				result.add(source.getLink());
			}
		}
		return result;
	}

	/**
	 * The model is the target of the following connections:
	 * - Any flow links that the model is the target of
	 * - Any other connections that the model's parent determines the model is the target of
	 */
	@Override
	protected List<Link> getModelTargetConnections() {
		Targets targets = getActivity().getTargets();
		if (targets == null) {
			return Collections.emptyList();
		}
		List<Link> result = new ArrayList<Link>();
		for(Target target : targets.getChildren()) {
			if (target.getLink() != null) {
				result.add(target.getLink());
			}				
		}
		return result;
	}
	
	/**
	 * Returns the connection anchor of a source connection which
	 * is at the given point.
	 * 
	 * @return  ConnectionAnchor.
	 */
	public ConnectionAnchor getSourceConnectionAnchor(Request request) {
		// TODO: Translate point to figure, call other method
		return getSourceConnectionAnchor((ConnectionEditPart)null);
	}
	
	/**
	 * Returns the connection anchor of a target connection which
	 * is at the given point.
	 *
	 * @return  ConnectionAnchor.
	 */
	public ConnectionAnchor getTargetConnectionAnchor(Request request) {
		// TODO: Translate point to figure, call other method
		return getTargetConnectionAnchor((ConnectionEditPart)null);
	}
	
	/**
	 * Because ActivityEditPart implements NodeEditPart, there may be connections
	 * to or from this node that are affected by model changes. Refresh the
	 * connections now.
	 * 
	 * If the model change involves the addition of an ActivityExtension, add an adapter
	 * to that extension.
	 */
	@Override
	protected void handleModelChanged() {
		super.handleModelChanged();
		refreshSourceConnections();
		refreshTargetConnections();
	}
	
	/**
	 * Convenience method to get the model as a BPEL Activity
	 * @return the model as a BPEL activity
	 */
	public Activity getActivity() {
		return (Activity) getModel();
	}
	
	/** 
	 * Override so we can set the constraint for every XY placed object
	 */
	
	@Override
	public void refreshVisuals() {
		Rectangle r = null;
		
		if (this.getParent() instanceof FlowEditPart) {
			EditPart root = getRoot();
			if (root instanceof GraphicalBPELRootEditPart) {
				Point p = ModelHelper.getLocation(getActivity());
				if (p.x == Integer.MIN_VALUE) {
					// HACK: We don't know its position, pretend it's at 0, 0.  This should only
					// be necessary during brief edge-case situations (like during startup before
					// we've done auto-layout on flows without metadata)..
					p = new Point(0, 0);
				}
				Dimension s = new Dimension(-1, -1);
				r = new Rectangle(p, s);
			}
		}

		if (r != null) {
			((GraphicalEditPart) getParent()).setLayoutConstraint(this, getFigure(), r);
		}
		super.refreshVisuals();
	}

	public ConnectionAnchor getTargetConnectionAnchorForTargetChild(ConnectionEditPart connEditPart) {
		return null;
	}
	
	protected void refreshDrawerHoverHelp() {
		
		IHoverHelper helper = null;
		
		try {
			helper = BPELUIRegistry.getInstance().getHoverHelper();
			if (helper == null) {
				return ;
			}
		}  catch (CoreException ce) {
			getFigure().setToolTip(null);
			BPELUIPlugin.log(ce);
			return ;
		}
		
	
		// Get the marker and pass it to the hover helper
		ArrayList<IMarker> listOfMarkers = new ArrayList<IMarker>();
		
		IMarkerHolder holder = BPELUtil.adapt(getActivity(), IMarkerHolder.class);
		
		for (IMarker m : holder.getMarkers(getActivity()) ) {
			
			// if we can't see it, then we will not look at it
			if (m.getAttribute(IModelMarkerConstants.DECORATION_MARKER_VISIBLE_ATTR, true) == false) {
				continue;
			}
			
			String where = m.getAttribute(IModelMarkerConstants.DECORATION_GRAPHICAL_MARKER_ANCHOR_POINT_ATTR,EMPTY_STRING); 
			
			if (mouseLocation == 1) {
				// Top drawer
				if (where.equals( IBPELUIConstants.MARKER_ANCHORPOINT_DRAWER_TOP )) {
					listOfMarkers.add(m);
				}
								
			} else {
				// bottom drawer
				if (where.equals( IBPELUIConstants.MARKER_ANCHORPOINT_DRAWER_BOTTOM )) {
					listOfMarkers.add(m);
				}
			}			
		}
		
		if (listOfMarkers.size() == 0) {
			return;
		}
		String text = null;
		
		if (listOfMarkers.size() == 1) {
			text = helper.getHoverHelp( listOfMarkers.get(0) );
		} else {
			StringBuilder sb = new StringBuilder();
			sb.append("There are ").append(listOfMarkers.size()).append(" issues here:\n");
			for(IMarker m : listOfMarkers) {
				String t = helper.getHoverHelp (m) ;
				sb.append("o ").append(t).append("\n");
			}
			text = sb.toString();
		}
				
		if (text == null || text.length() < 1) {
			getFigure().setToolTip(null);
		} else {
			getFigure().setToolTip(new Label(text));
		}
	}
	
	
	
	protected MouseMotionListener getMouseMotionListener() {
		if (mouseMotionListener == null) {
			this.mouseMotionListener = new MouseMotionListener() {
				public void mouseDragged(MouseEvent me) {
				}
				public void mouseEntered(MouseEvent me) {
				}
				public void mouseExited(MouseEvent me) {
				}
				public void mouseHover(MouseEvent me) {
				}
				public void mouseMoved(MouseEvent me) {
					DrawerBorder border = getDrawerBorder();
					if (border == null) return;
					int inTop = border.isPointInTopDrawer(me.x, me.y) ? 1 : 0;
					int inBottom = border.isPointInBottomDrawer(me.x, me.y) ? 2 : 0;
					// If mouse stayed in the same place it was in last time, do nothing
					int newLocation = inTop | inBottom;
					if (newLocation == mouseLocation) return;
					mouseLocation = newLocation;
					refreshHoverHelp();
				}
			};
		}
		return mouseMotionListener;
	}
	protected abstract DrawerBorder getDrawerBorder();
	
	/**
	 * Override the refreshHoverHelp method from BPELEditPart. This method
	 * will refresh either the regular hover help, or the drawer hover help
	 * if that is what is currently being displayed.
	 */
	
	@Override
	public void refreshHoverHelp() {
		switch (mouseLocation) {
			case 1:
				refreshDrawerHoverHelp();
				return;
			case 2:
				refreshDrawerHoverHelp();
				return;
		}
		super.refreshHoverHelp();
	}
	
	public IFigure getMainActivityFigure() {
		return getFigure();
	}
	
	public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connEditPart) {
		// Provide default anchor for Links.
		if (connEditPart == null || connEditPart instanceof LinkEditPart) {
			if(ModelHelper.isHorizontalLayout(getModel())){
				return new CenteredConnectionAnchor(getMainActivityFigure(), CenteredConnectionAnchor.RIGHT_INNER, 0);
			}else
				return new CenteredConnectionAnchor(getMainActivityFigure(), CenteredConnectionAnchor.BOTTOM, 0);
		}
		return null;
	}
	public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connEditPart) {
		// Provide default anchor for Links.
		if (connEditPart == null || connEditPart instanceof LinkEditPart) {
			if(ModelHelper.isHorizontalLayout(getModel())){
				return new CenteredConnectionAnchor(getMainActivityFigure(), CenteredConnectionAnchor.LEFT_INNER, 0);
			}else
				return new CenteredConnectionAnchor(getMainActivityFigure(), CenteredConnectionAnchor.TOP, 0);
		}
		return null;
	}
	
}
