//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 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 implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.authoring.gef.edit;

import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.epf.authoring.gef.commands.ChangeBoundsCommand;
import org.eclipse.epf.authoring.gef.commands.ReconnectLinkCommand;
import org.eclipse.epf.authoring.gef.util.DiagramUIResources;
import org.eclipse.epf.diagram.model.Link;
import org.eclipse.epf.diagram.model.Node;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;

/**
 * Service class to allow create and register common action to the action
 * register. most of the code are moved from the Editor class.
 * 
 * @author Jinhua Xi
 * @since 1.0
 */
public class DiagramActionService {

	public static final String ALIGN_HORZ_AVERAGE = "align_horizontal_to_average"; //$NON-NLS-1$

	public static final String ALIGN_HORZ_FIRST_SELECTED = "align_horizontal_to_first_selection"; //$NON-NLS-1$

	public static final String ALIGN_VERT_AVERAGE = "align_vertical_to_average"; //$NON-NLS-1$

	public static final String ALIGN_VERT_FIRST_SELECTED = "align_vertical_to_first_selection"; //$NON-NLS-1$

	private DefaultEditDomain editDomain;

	private GraphicalViewer graphicalViewer;

	private ActionRegistry actionRegistry;

	public DiagramActionService(GraphicalViewer graphicalViewer,
			DefaultEditDomain editDomain, ActionRegistry actionRegistry) {
		this.graphicalViewer = graphicalViewer;
		this.editDomain = editDomain;
		this.actionRegistry = actionRegistry;
	}

	public void setGraphicalViewer(GraphicalViewer graphicalViewer) {
		this.graphicalViewer = graphicalViewer;
	}

	private GraphicalViewer getGraphicalViewer() {
		return graphicalViewer;
	}

	/**
	 * Returns the command stack.
	 * 
	 * @return the command stack
	 */
	protected CommandStack getCommandStack() {
		return getEditDomain().getCommandStack();
	}

	/**
	 * Returns the edit domain.
	 * 
	 * @return the edit domain
	 */
	protected DefaultEditDomain getEditDomain() {
		return editDomain;
	}

	protected ActionRegistry getActionRegistry() {
		return actionRegistry;
	}

	public IAction registerHorizontalAlignAverageAction() {
		// align horizontally to average y-value of all nodes
		IAction hAlignAverageAction = new Action(
				DiagramUIResources.AbstractDiagramEditor_hAlignAverageAction_text) { //$NON-NLS-1$
			public void run() {
				horizAlignToAverageSelected();
			}

			public String getId() {
				return ALIGN_HORZ_AVERAGE;
			}
		};
		getActionRegistry().registerAction(hAlignAverageAction);

		return hAlignAverageAction;
	}

	public IAction registerHorizontalAlignFirstSelectedAction() {
		// align horizontally to y-value of first selected node
		IAction hAlignFirstSelectedAction = new Action(
				DiagramUIResources.AbstractDiagramEditor_hAlignFirstSelectedAction_text) { //$NON-NLS-1$
			public void run() {
				horzAlignToFirstSelected();
			}

			public String getId() {
				return ALIGN_HORZ_FIRST_SELECTED;
			}
		};
		getActionRegistry().registerAction(hAlignFirstSelectedAction);

		return hAlignFirstSelectedAction;
	}

	public IAction registerVerticalAlignAverageAction() {
		// align vertically to average x-value of all selected nodes
		IAction vAlignAverageAction = new Action(
				DiagramUIResources.AbstractDiagramEditor_vAlignAverageAction_text) { //$NON-NLS-1$
			public void run() {
				verticalAlignToAverageSelected();
			}

			public String getId() {
				return ALIGN_VERT_AVERAGE;
			}
		};
		getActionRegistry().registerAction(vAlignAverageAction);

		return vAlignAverageAction;
	}

	public IAction registerVerticalAlignFirstSelectedAction() {
		// align vertically to x-value of first selected node
		IAction vAlignFirstSelectedAction = new Action(
				DiagramUIResources.AbstractDiagramEditor_vAlignFirstSelectedAction_text) { //$NON-NLS-1$
			public void run() {
				verticalAlignToFirstSelected();
			}

			public String getId() {
				return ALIGN_VERT_FIRST_SELECTED;
			}
		};
		getActionRegistry().registerAction(vAlignFirstSelectedAction);

		return vAlignFirstSelectedAction;
	}

	// /////////////////////////////////////////////////////////////////////////////////////////////////
	// action methods
	// /////////////////////////////////////////////////////////////////////////////////////////////////

	public void horzAlignToFirstSelected() {
		List editParts = getGraphicalViewer().getSelectedEditParts();
		AbstractGraphicalEditPart firstPart = null;
		// loop through all selected items, remember the y-value of the
		// first node,
		// set the following nodes to the same y-value
		for (int a = 0; a < editParts.size(); a++) {
			if (editParts.get(a) instanceof NodeEditPart
					|| editParts.get(a) instanceof NodeContainerEditPart) {
				AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) editParts
						.get(a);
				// remember the first part we move
				if (firstPart == null) {
					firstPart = ep;
				} else {
					// calculate the offset for centerline alignment
					Rectangle bounds = ep.getFigure().getBounds();
					int offset = (int) ((double) (bounds.height - firstPart
							.getFigure().getBounds().height) / 2.0);
					// is this node in a container?
					if (!(ep.getParent() instanceof DiagramEditPart)) {
						// need to move the container, adjust the offset
						ep = (AbstractGraphicalEditPart) ep.getParent();
						Rectangle parentBounds = ep.getFigure().getBounds();
						offset += bounds.y - parentBounds.y;
						bounds = parentBounds;
					}
					// send the command to move the node
					org.eclipse.gef.commands.Command c = new ChangeBoundsCommand(
							(Node) ep.getModel(), new Point(bounds.x, firstPart
									.getFigure().getBounds().y
									- offset), bounds.width);
					getCommandStack().execute(c);
				}
			}
		}
	}

	public void horizAlignToAverageSelected() {
		List editParts = getGraphicalViewer().getSelectedEditParts();
		AbstractGraphicalEditPart firstPart = null;
		int total = 0;
		int ysum = 0;
		for (int a = 0; a < editParts.size(); a++) {
			if (editParts.get(a) instanceof NodeEditPart
					|| editParts.get(a) instanceof NodeContainerEditPart) {
				AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) editParts
						.get(a);
				// remember the first part we move
				if (firstPart == null) {
					firstPart = ep;
				}
				ysum += ep.getFigure().getBounds().y;
				total++;
			}
		}
		// calculate the average y-value for all nodes
		int yave = (int) ((double) ysum / (double) total);
		// loop through all the nodes again and set them
		for (int a = 0; a < editParts.size(); a++) {
			if (editParts.get(a) instanceof NodeEditPart
					|| editParts.get(a) instanceof NodeContainerEditPart) {
				AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) editParts
						.get(a);
				// calculate the height difference, so that elements are aligned
				// through the centerline
				Rectangle bounds = ep.getFigure().getBounds();
				int offset = (int) ((double) (bounds.height - firstPart
						.getFigure().getBounds().height) / 2.0);
				// is this node inside a container?
				if (!(ep.getParent() instanceof DiagramEditPart)) {
					// we need to move the container, adjust the offset
					ep = (AbstractGraphicalEditPart) ep.getParent();
					Rectangle parentBounds = ep.getFigure().getBounds();
					offset += bounds.y - parentBounds.y;
					bounds = parentBounds;
				}
				// send the command to move the node
				org.eclipse.gef.commands.Command c = new ChangeBoundsCommand(
						(Node) ep.getModel(),
						new Point(bounds.x, yave - offset), bounds.width);
				getCommandStack().execute(c);
			}
		}

	}

	public void verticalAlignToAverageSelected() {
		List editParts = getGraphicalViewer().getSelectedEditParts();
		AbstractGraphicalEditPart firstPart = null;
		int total = 0;
		int xsum = 0;
		// loop through the selected parts
		for (int a = 0; a < editParts.size(); a++) {
			// we are only interested in nodes and containers
			if (editParts.get(a) instanceof NodeEditPart
					|| editParts.get(a) instanceof NodeContainerEditPart) {
				AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) editParts
						.get(a);
				if (firstPart == null) {
					firstPart = ep;
				}
				// sum the x coordinates of each element
				xsum += ep.getFigure().getBounds().x;
				// remember how many selected elements applied to this operation
				total++;
			}
		}
		// calculate the average x-value for all nodes
		int xave = (int) ((double) xsum / (double) total);
		// loop through all the nodes again and set them
		for (int a = 0; a < editParts.size(); a++) {
			if (editParts.get(a) instanceof NodeEditPart
					|| editParts.get(a) instanceof NodeContainerEditPart) {
				AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) editParts
						.get(a);
				// calculate the offset for centerline alignment
				Rectangle bounds = ep.getFigure().getBounds();
				int offset = (int) ((double) (bounds.width - firstPart
						.getFigure().getBounds().width) / 2.0);
				// are we inside a container?
				if (!(ep.getParent() instanceof DiagramEditPart)) {
					// adjust the offset since we have to move the container
					ep = (AbstractGraphicalEditPart) ep.getParent();
					Rectangle parentBounds = ep.getFigure().getBounds();
					offset += bounds.x - parentBounds.x;
					bounds = parentBounds;
				}
				// send the command to move the element
				org.eclipse.gef.commands.Command c = new ChangeBoundsCommand(
						(Node) ep.getModel(),
						new Point(xave - offset, bounds.y), bounds.width);
				getCommandStack().execute(c);
			}
		}

	}

	public void verticalAlignToFirstSelected() {
		// get the selected items
		List editParts = getGraphicalViewer().getSelectedEditParts();
		AbstractGraphicalEditPart alignToPart = null;
		// find the first diagram part we can align to
		for (int a = 0; a < editParts.size(); a++) {
			if (editParts.get(a) instanceof NodeEditPart
					|| editParts.get(a) instanceof NodeContainerEditPart) {
				AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) editParts
						.get(a);
				if (alignToPart == null) {
					// remember it and stop
					alignToPart = ep;
					break;
				}
			}
		}
		// loop through the elements and set their new position
		for (int a = 0; a < editParts.size(); a++) {
			if (editParts.get(a) instanceof NodeEditPart
					|| editParts.get(a) instanceof NodeContainerEditPart) {
				AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) editParts
						.get(a);
				// we can skip the first one since this is an align-to
				if (ep != alignToPart
						&& !(alignToPart instanceof SynchBarNodeEditPart)) {
					// calculate the offset for centerline alignment
					if (!(ep instanceof SynchBarNodeEditPart)) {
						Rectangle bounds = ep.getFigure().getBounds();
						int offset = (int) ((double) (bounds.width - alignToPart
								.getFigure().getBounds().width) / 2.0);
						// is this inside a container?
						if (!(ep.getParent() instanceof DiagramEditPart)) {
							// adjust the offset since we are moving the
							// container
							ep = (AbstractGraphicalEditPart) ep.getParent();
							Rectangle parentBounds = ep.getFigure().getBounds();
							offset += bounds.x - parentBounds.x;
							bounds = parentBounds;
						}
						// send the command to move the element
						org.eclipse.gef.commands.Command c = new ChangeBoundsCommand(
								(Node) ep.getModel(), new Point(alignToPart
										.getFigure().getBounds().x
										- offset, bounds.y), bounds.width);
						getCommandStack().execute(c);
						ep.getFigure().setLocation(
								new Point(alignToPart.getFigure().getBounds().x
										- offset, bounds.y));
					} else {
						Point tp = ep.getFigure().getBounds().getLocation();
						boolean connected = false;
						boolean source = false;
						Point endPoint = null;
						LinkEditPart lp = null;
						// check target connections
						List list = ((NodeEditPart) ep).getTargetConnections();
						for (Iterator itor = list.iterator(); itor.hasNext();) {
							lp = (LinkEditPart) itor.next();
							if (lp.getSource().equals(alignToPart)) {
								endPoint = ((Link) lp.getModel())
										.getTargetEndPoint();
								tp = tp.getTranslated(endPoint);
								connected = true;
								break;
							}
						}
						// check source connections if not found in target connections
						if (!connected) {
							list = ((NodeEditPart) ep).getSourceConnections();
							for (Iterator itor = list.iterator(); itor
									.hasNext();) {
								lp = (LinkEditPart) itor.next();
								if (lp.getTarget().equals(alignToPart)) {
									endPoint = ((Link) lp.getModel())
											.getSourceEndPoint();
									tp = tp.getTranslated(endPoint);
									connected = true;
									source = true;
									break;
								}
							}
						}
						if (connected && (alignToPart instanceof NodeEditPart)) {
									Point centerPoint = alignToPart.getFigure()
											.getBounds().getCenter();
									int delta = centerPoint.x - tp.x;
							endPoint.x += delta;
									ReconnectLinkCommand c = new ReconnectLinkCommand(
									(Link) lp.getModel(), (Node) ep.getModel(),
									source);
							c.setEndPoint(endPoint);
									getCommandStack().execute(c);
						}

					}
				}
				// If first selected edit part is syncbarnodeeditpart, need to
				// handle differently
				if (ep != alignToPart
						&& (alignToPart instanceof SynchBarNodeEditPart)) {

					Point sp = alignToPart.getFigure().getBounds()
							.getLocation();
					boolean connected = false;
					// check if the part is connected via source connections
					List list = ((NodeEditPart) alignToPart)
							.getSourceConnections();
					for (Iterator itor = list.iterator(); itor.hasNext();) {
						LinkEditPart lp = (LinkEditPart) itor.next();
						if (lp.getTarget().equals(ep)) {
							Point sourceEndPoint = ((Link) lp.getModel())
									.getSourceEndPoint();
							sp = sp.getTranslated(sourceEndPoint);
							connected = true;
							break;
						}
					}
					// if not found above, check if it is connected via target connections
					if (!connected) {
						list = ((NodeEditPart) alignToPart)
								.getTargetConnections();
						for (Iterator itor = list.iterator(); itor.hasNext();) {
							LinkEditPart lp = (LinkEditPart) itor.next();
							if (lp.getSource().equals(ep)) {
								Point targetEndPoint = ((Link) lp.getModel())
										.getTargetEndPoint();
								sp = sp.getTranslated(targetEndPoint);
								connected = true;
								break;
							}
						}
					}
					if (ep instanceof SynchBarNodeEditPart) {
						Point tp = ((Node) ep.getModel()).getLocation()
								.getCopy();
						Point alignToCenter = alignToPart.getFigure().getBounds().getCenter();
						Point centerPoint = ep.getFigure().getBounds().getCenter();
						int delta = alignToCenter.x - centerPoint.x;
						tp.x = tp.x + delta;
						org.eclipse.gef.commands.Command c = new ChangeBoundsCommand(
								(Node) ep.getModel(), tp, ep.getFigure()
										.getBounds().width);
						getCommandStack().execute(c);
						ep.getFigure().setLocation(new Point(tp.x, tp.y));
					} else if (ep instanceof NodeEditPart) {
						Point tp = ((Node) ep.getModel()).getLocation()
								.getCopy();
						Point centerPoint = ep.getFigure().getBounds()
								.getCenter();
						int delta = sp.x - centerPoint.x;
						tp.x = tp.x + delta;
						org.eclipse.gef.commands.Command c = new ChangeBoundsCommand(
								(Node) ep.getModel(), tp, ep.getFigure()
										.getBounds().width);
						getCommandStack().execute(c);
						ep.getFigure().setLocation(new Point(tp.x, tp.y));
					}
				}
			}
		}
	}

}
