//------------------------------------------------------------------------------
// 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.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.epf.authoring.gef.commands.ChangeBoundsCommand;
import org.eclipse.epf.diagram.model.Node;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CommandStack;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.StructuredSelection;

/**
 * Service class to allow updating diagram to a default layout. most of the code
 * are moved from the Editor class.
 * 
 * @author Jinhua Xi
 * @since 1.0
 */
public class DiagramUpdateService {

	private DefaultEditDomain editDomain;

	private GraphicalViewer graphicalViewer;

	private ActionRegistry actionRegistry;

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

	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;
	}

	/**
	 * Lazily creates and returns the action registry.
	 * 
	 * @return the action registry
	 */
	protected ActionRegistry getActionRegistry() {
		return actionRegistry;
	}

	public void cleanUpDiagram() {
		if (getGraphicalViewer().getContents() instanceof ActivityDetailDiagramEditPart) {
			ActivityDetailDiagramEditPart dep = (ActivityDetailDiagramEditPart) getGraphicalViewer()
					.getContents();
			List children = dep.getRecentlyAddedParts();
			if (!children.isEmpty()) {
				Comparator comparator = new Comparator() {

					public int compare(Object arg0, Object arg1) {
						int rc = 0;
						if (arg0 instanceof RoleTaskCompositeEditPart) {
							if (!(arg1 instanceof RoleTaskCompositeEditPart))
								rc = -1;
						} else if (arg1 instanceof RoleTaskCompositeEditPart)
							rc = 1;
						return rc;
					}

				};

				Object[] array = children.toArray();
				Arrays.sort(array, comparator);
				for (int a = 0; a < array.length; a++) {
					AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) array[a];
					adjustSize(ep);
				}
				for (int b = 0; b < array.length; b++) {
					AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) array[b];
					cleanUp(ep);
				}
				for (int c = 0; c < array.length; c++) {
					AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) array[c];
					reduceLinkLength(ep);
				}
//				for (int c = 0; c < array.length; c++) {
//					AbstractGraphicalEditPart ep = (AbstractGraphicalEditPart) array[c];
//					if(ep instanceof RoleTaskCompositeEditPart){
//						resetSizes(ep, ep);
//					}
//				}
				packDiagram();
				dep.clearRecentlyAddedParts();
				getGraphicalViewer().setSelection(new StructuredSelection());
			}
		}
	}

	private void resetSizes(AbstractGraphicalEditPart ep, AbstractGraphicalEditPart parent) {
		// TODO Auto-generated method stub
		if (!(ep instanceof WorkProductCompositeEditPart)) {
			List sourceLinks = ep.getSourceConnections();
			if (sourceLinks.size() > 0) {
				for (int b = 0; b < sourceLinks.size(); b++) {
					LinkEditPart link = (LinkEditPart) sourceLinks.get(b);
					AbstractGraphicalEditPart target = (AbstractGraphicalEditPart) link
							.getTarget();
					int w = target.getFigure().getBounds().width;
					int ew = ep.getFigure().getPreferredSize().width;
					if (w > ew) {
						Point location = ep.getFigure().getBounds()
						.getLocation();
						location.x+=(w-ew)/2;
						Command c = new ChangeBoundsCommand(((Node) ep
								.getModel()), ep.getFigure().getBounds()
								.getLocation(), w);
						getCommandStack().execute(c);
						int h = ep.getFigure().getPreferredSize().height;
						ep.getFigure()
								.setPreferredSize(new Dimension(w, h));
						ep.getFigure().setSize(new Dimension(w, h));
					}
					getGraphicalViewer()
							.setSelection(
									new StructuredSelection(new Object[] { ep,
											target }));
					IAction valign = getActionRegistry().getAction(
							DiagramActionService.ALIGN_VERT_FIRST_SELECTED);
					valign.run();
				}
			}
			List targetLinks = ep.getTargetConnections();
			if (targetLinks.size() > 0) {
				for (int b = 0; b < targetLinks.size(); b++) {
					LinkEditPart link = (LinkEditPart) targetLinks.get(b);
					AbstractGraphicalEditPart source = (AbstractGraphicalEditPart) link
							.getSource();
					int w = source.getFigure().getBounds().width;
					int ew = ep.getFigure().getPreferredSize().width;
					if (w > ew) {
						Command c = new ChangeBoundsCommand(((Node) ep
								.getModel()), ep.getFigure().getBounds()
								.getLocation(), w);
						getCommandStack().execute(c);
						int h = ep.getFigure().getPreferredSize().height;
						ep.getFigure()
								.setPreferredSize(new Dimension(w, h));
						ep.getFigure().setSize(new Dimension(w, h));
					}
					getGraphicalViewer()
							.setSelection(
									new StructuredSelection(new Object[] { ep,
											source }));
					IAction valign = getActionRegistry().getAction(
							DiagramActionService.ALIGN_VERT_FIRST_SELECTED);
					valign.run();
				}
			}
			parent.getFigure().getLayoutManager().layout(parent.getFigure());
			if (ep instanceof NodeContainerEditPart) {
				List children = ep.getChildren();
				for (int d = 0; d < children.size(); d++) {
					AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) children
							.get(d);
					resetSizes(p, ep);
				}
			}
		}
	}

	/**
	 * This method does a vertical alignment of all nodes with links
	 */
	private void cleanUp(AbstractGraphicalEditPart ep) {
		if (!(ep instanceof WorkProductCompositeEditPart)) {
			List sourceLinks = ep.getSourceConnections();
			if (sourceLinks.size() > 0) {
				for (int b = 0; b < sourceLinks.size(); b++) {
					LinkEditPart link = (LinkEditPart) sourceLinks.get(b);
					AbstractGraphicalEditPart target = (AbstractGraphicalEditPart) link
							.getTarget();
					int w = target.getFigure().getBounds().width;
					int ew = ep.getFigure().getPreferredSize().width;
					if (w < ew) {
						Command c = new ChangeBoundsCommand(((Node) ep
								.getModel()), target.getFigure().getBounds()
								.getLocation(), w);
						getCommandStack().execute(c);
						int h = target.getFigure().getPreferredSize().height;
						target.getFigure()
								.setPreferredSize(new Dimension(w, h));
						target.getFigure().setSize(new Dimension(w, h));
					}
					getGraphicalViewer()
							.setSelection(
									new StructuredSelection(new Object[] { ep,
											target }));
					IAction valign = getActionRegistry().getAction(
							DiagramActionService.ALIGN_VERT_FIRST_SELECTED);
					valign.run();
				}
			}
			List targetLinks = ep.getTargetConnections();
			if (targetLinks.size() > 0) {
				for (int b = 0; b < targetLinks.size(); b++) {
					LinkEditPart link = (LinkEditPart) targetLinks.get(b);
					AbstractGraphicalEditPart source = (AbstractGraphicalEditPart) link
							.getSource();
					int w = source.getFigure().getBounds().width;
					int ew = ep.getFigure().getPreferredSize().width;
					if (w < ew) {
						Command c = new ChangeBoundsCommand(((Node) ep
								.getModel()), source.getFigure().getBounds()
								.getLocation(), w);
						getCommandStack().execute(c);
						int h = source.getFigure().getPreferredSize().height;
						source.getFigure()
								.setPreferredSize(new Dimension(w, h));
						source.getFigure().setSize(new Dimension(w, h));
					}
					getGraphicalViewer()
							.setSelection(
									new StructuredSelection(new Object[] { ep,
											source }));
					IAction valign = getActionRegistry().getAction(
							DiagramActionService.ALIGN_VERT_FIRST_SELECTED);
					valign.run();
				}
			}
			if (ep instanceof NodeContainerEditPart) {
				List children = ep.getChildren();
				for (int d = 0; d < children.size(); d++) {
					AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) children
							.get(d);
					cleanUp(p);
				}
			}
		}
	}

	private boolean reduceLinkLength(AbstractGraphicalEditPart part) {
		boolean moved = false;
		// only move new WorkProductComposite elements
		if (part instanceof WorkProductCompositeEditPart) {
			AbstractGraphicalEditPart linkedPart = null;
			int position = PositionConstants.CENTER;
			// is this element linked to another item?
			if (part.getTargetConnections().size() > 0) {
				linkedPart = (AbstractGraphicalEditPart) ((LinkEditPart) part
						.getTargetConnections().get(0)).getSource();
				position = PositionConstants.NORTH;
			} else if (part.getSourceConnections().size() > 0) {
				linkedPart = (AbstractGraphicalEditPart) ((LinkEditPart) part
						.getSourceConnections().get(0)).getTarget();
				position = PositionConstants.SOUTH;
			}
			if (linkedPart != null) {
				if (!(linkedPart.getParent() instanceof DiagramEditPart)) {
					linkedPart = (AbstractGraphicalEditPart) linkedPart
							.getParent();
				}
				// get the part's position
				Rectangle partBounds = part.getFigure().getBounds();
				// get the linked part's position
				Rectangle linkedPartBounds = linkedPart.getFigure().getBounds();
				// determine in which direction is the linked part
				// int position = partBounds.getPosition(linkedPartBounds
				// .getLocation());
				// assume it is below us, so this part moves up
				int direction = -1;
				if ((position & PositionConstants.NORTH) == PositionConstants.NORTH
						|| (position & PositionConstants.CENTER) == PositionConstants.CENTER) {
					// above us, move down
					direction = 1;
				}
				// set new bounds for the part so that it sites just outside of
				// the linked part
				Rectangle testBounds = partBounds.getCopy();
				if (direction == 1) {
					testBounds.y = linkedPartBounds.y + linkedPartBounds.height
							+ VERT_PIX_PADDING;
				} else {
					testBounds.y = linkedPartBounds.y - VERT_PIX_PADDING
							- partBounds.height;
				}
				// set the new location for the part in the model
				Point np = new Point(testBounds.x, testBounds.y);
				part.getFigure().setLocation(np);
				Command c = new ChangeBoundsCommand((Node) part.getModel(), np,
						partBounds.width);
				getCommandStack().execute(c);
			}
		}
		return moved;
	}

	private void adjustSize(AbstractGraphicalEditPart ep) {
		if (ep instanceof WorkProductCompositeEditPart) {
			adjustSize((NodeContainerEditPart) ep, 1);
		}
		// Below Code generic NodeContainerEditPart modification.
		else if (ep instanceof NodeContainerEditPart) {
			adjustSize((NodeContainerEditPart) ep, 50);
		}
	}

	protected final static int HORIZ_PIX_PADDING = 30;

	protected final static int VERT_PIX_PADDING = 20;

	private void adjustSize(NodeContainerEditPart ep, int horizCount) {
		int requiredWidth = 0;
		int rowMaxHeight = 0;
		int rows = 0;
		int widthSum = 0;
		int heightSum = 0;
		int column = 0;
		//int originalWidth = ep.getFigure().getSize().width;
		List children = ep.getChildren();
		for (int p = 0; p < children.size();) {
			if (column == 0) {
				rows++;
			}
			if (column < horizCount) {
				AbstractGraphicalEditPart child = (AbstractGraphicalEditPart) children
						.get(p);
				Dimension d = child.getFigure().getPreferredSize();
				widthSum += d.width;
				if (d.height > rowMaxHeight) {
					rowMaxHeight = d.height;
				}
				p++;
				column++;
			} else {
				if (widthSum > requiredWidth) {
					requiredWidth = widthSum;
				}
				heightSum += rowMaxHeight;
				rowMaxHeight = 0;
				widthSum = 0;
				column = 0;
			}
		}
		if (widthSum > requiredWidth) {
			requiredWidth = widthSum;
		}
		heightSum += rowMaxHeight;
		requiredWidth += HORIZ_PIX_PADDING
				* Math.min(horizCount, children.size());
		int requiredHeight = heightSum + (VERT_PIX_PADDING * rows);
		Command cmd = new ChangeBoundsCommand(((Node) ep.getModel()), ep
				.getFigure().getBounds().getLocation(), requiredWidth);
		getCommandStack().execute(cmd);
		ep.getFigure().setPreferredSize(
				new Dimension(requiredWidth, requiredHeight));
		((Node) ep.getModel()).setHeight(requiredHeight);
		ep.getFigure().setSize(new Dimension(requiredWidth, requiredHeight));
			ep.getFigure().getLayoutManager().layout(ep.getFigure());
		// after layout, check the bottom-most child figure against the bottom
		// of the container, adjust the bottom of the container is necessary
		if (ep instanceof WorkProductCompositeEditPart && children.size() > 0) {
			GraphicalEditPart last = (GraphicalEditPart) children.get(children
					.size() - 1);
			Point childBottom = last.getFigure().getBounds().getBottom();
			Point parentBottom = ep.getFigure().getBounds().getBottom();
			int delta = parentBottom.y - childBottom.y - (VERT_PIX_PADDING / 2);
			ep.getFigure().setPreferredSize(
					new Dimension(requiredWidth, requiredHeight - delta));
			((Node) ep.getModel()).setHeight(requiredHeight - delta);
			ep.getFigure().setSize(
					new Dimension(requiredWidth, requiredHeight - delta));
		}
	}

	protected void enumerateConnectedParts(AbstractGraphicalEditPart part,
			List connected) {
		// only add parts directly on the diagram surface
		if (!connected.contains(part)) {
			if (!(part.getParent() instanceof DiagramEditPart)) {
				if (!connected.contains(part.getParent())) {
					connected.add(part.getParent());
				}
			} else {
				connected.add(part);
			}
			AbstractGraphicalEditPart linkedPart = null;
			List children = part.getChildren();
			for (int x = 0; x < children.size(); x++) {
				AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) children
						.get(x);
				enumerateConnectedParts(p, connected);
			}
			// is this element linked to another item?
			for (int x = 0; x < part.getTargetConnections().size(); x++) {
				linkedPart = (AbstractGraphicalEditPart) ((LinkEditPart) part
						.getTargetConnections().get(x)).getSource();
				enumerateConnectedParts(linkedPart, connected);
			}
			for (int x = 0; x < part.getSourceConnections().size(); x++) {
				linkedPart = (AbstractGraphicalEditPart) ((LinkEditPart) part
						.getSourceConnections().get(x)).getTarget();
				enumerateConnectedParts(linkedPart, connected);
			}
		}
	}

	/**
	 * This method finds single elements and groups of elements, and positions
	 * them starting from the top of the diagram and continuously down the page.
	 */
	private void packDiagram() {
		ActivityDetailDiagramEditPart diagram = (ActivityDetailDiagramEditPart) getGraphicalViewer()
				.getContents();
		// start at the top of the diagram
		// Dimension diagramSize = diagram.getFigure().getSize();
		// no new shapes can be moved above this y-axis value
		int reserved = 0;
		// these are elements without connections
		List stragglers = new ArrayList();
		// these are groups of elements connected with links
		List groups = new ArrayList();
		// these are top level parts on the diagram that have been added
		List diagramParts = diagram.getRecentlyAddedParts();
		for (int x = 0; x < diagramParts.size(); x++) {
			AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) diagramParts
					.get(x);
			boolean found = false;
			for (int g = 0; g < groups.size() && found == false; g++) {
				List group = (List) groups.get(g);
				if (group.contains(p)) {
					found = true;
					break;
				}
			}
			if (!found) {
				List connected = new ArrayList();
				enumerateConnectedParts(p, connected);
				if (connected.size() == 1) {
					stragglers.add(p);
				} else {
					groups.add(connected);
				}
			}
		}
		// process each group closest to the top (min y value)
		while (groups.size() > 0) {
			List top = (List) groups.get(0);
			int topY = getVerticalMinPart(top).getFigure().getBounds().y;
			for (int x = 0; x < groups.size(); x++) {
				List g = (List) groups.get(x);
				int y = getVerticalMinPart(g).getFigure().getBounds().y;
				if (y < topY) {
					top = g;
					topY = y;
				}
			}
			int limit = reserved + 1;
			int verticalDelta = limit - topY;
			int leftX = getHorizontalMinPart(top).getFigure().getBounds().x;
			int horizontalDelta = HORIZ_PIX_PADDING - leftX;
			for (int x = 0; x < top.size(); x++) {
				AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) top
						.get(x);
				Rectangle bounds = p.getFigure().getBounds();
				ChangeBoundsCommand cbc = new ChangeBoundsCommand((Node) p
						.getModel(), new Point(bounds.x + horizontalDelta,
						bounds.y + verticalDelta), bounds.width);
				getCommandStack().execute(cbc);
				p.getFigure().setLocation(
						new Point(bounds.x + horizontalDelta, bounds.y
								+ verticalDelta));
			}
			int bottomY = getVerticalMaxPart(top).getFigure().getBounds()
					.bottom();
			reserved = bottomY + (VERT_PIX_PADDING * 2);
			groups.remove(top);
		}
		int next = HORIZ_PIX_PADDING;
		for (int x = 0; x < stragglers.size(); x++) {
			int limit = reserved + 1;
			AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) stragglers
					.get(x);
			Rectangle bounds = p.getFigure().getBounds();
			ChangeBoundsCommand cbc = new ChangeBoundsCommand((Node) p
					.getModel(), new Point(next, limit), bounds.width);
			getCommandStack().execute(cbc);
			p.getFigure().setLocation(new Point(next, limit));
			next += bounds.width + HORIZ_PIX_PADDING;
		}
	}

	private AbstractGraphicalEditPart getVerticalMinPart(List parts) {
		AbstractGraphicalEditPart rc = parts.size() > 0 ? (AbstractGraphicalEditPart) parts
				.get(0)
				: null;
		for (int x = 0; x < parts.size(); x++) {
			AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) parts
					.get(x);
			if (p.getFigure().getBounds().y < rc.getFigure().getBounds().y) {
				rc = p;
			}
		}
		return rc;
	}

	private AbstractGraphicalEditPart getHorizontalMinPart(List parts) {
		AbstractGraphicalEditPart rc = parts.size() > 0 ? (AbstractGraphicalEditPart) parts
				.get(0)
				: null;
		for (int x = 0; x < parts.size(); x++) {
			AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) parts
					.get(x);
			if (p.getFigure().getBounds().x < rc.getFigure().getBounds().x) {
				rc = p;
			}
		}
		return rc;
	}

	private AbstractGraphicalEditPart getVerticalMaxPart(List parts) {
		AbstractGraphicalEditPart rc = parts.size() > 0 ? (AbstractGraphicalEditPart) parts
				.get(0)
				: null;
		for (int x = 0; x < parts.size(); x++) {
			AbstractGraphicalEditPart p = (AbstractGraphicalEditPart) parts
					.get(x);
			if (p.getFigure().getBounds().bottom() > rc.getFigure().getBounds()
					.bottom()) {
				rc = p;
			}
		}
		return rc;
	}

}
