/**
 * File:    WEDragAndDropCommand.java
 * Created: 15.06.2008
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * 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:
 *    Christian Saad, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.commands;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.IdentityCommand;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EcoreUtil.Copier;
import org.eclipse.emf.edit.command.DeleteCommand;
import org.eclipse.emf.edit.command.DragAndDropCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jwt.meta.model.processes.Action;
import org.eclipse.jwt.meta.model.processes.Activity;
import org.eclipse.jwt.meta.model.processes.ActivityEdge;
import org.eclipse.jwt.meta.model.processes.ActivityNode;
import org.eclipse.jwt.meta.model.processes.Scope;
import org.eclipse.jwt.we.editors.WEEditor;
import org.eclipse.jwt.we.misc.util.EMFHelper;
import org.eclipse.jwt.we.misc.views.LayoutDataManager;
import org.eclipse.jwt.we.model.view.LayoutData;
import org.eclipse.jwt.we.model.view.Reference;
import org.eclipse.jwt.we.model.view.ReferenceEdge;

/**
 * A custom Drag and Drop command, which is used to move elements in the outline
 * view. It prevents objects from being moved if they are edges or if they have
 * edges attached, because it is certainly a bad idea to move an edge from one
 * activity to another.
 * 
 * @version $Id: WEDragAndDropCommand.java,v 1.2 2009/11/04 17:18:45 chsaad Exp
 *          $
 * @author Christian Saad (csaad@gmx.net), Programming distributed Systems Lab,
 *         University of Augsburg, Germany, www.ds-lab.org
 */
public class WEDragAndDropCommand extends DragAndDropCommand
{

	/**
	 * The workflow editor.
	 */
	private WEEditor weeditor;

	/**
	 * Store view information.
	 */
	private Collection viewInformationStatus;

	/**
	 * The command for deleting/restoring copied view information.
	 */
	private Command deleteCopyCommand;


	/**
	 * The constructor.
	 * 
	 * @param domain
	 * @param owner
	 * @param location
	 * @param operations
	 * @param operation
	 * @param collection
	 */
	public WEDragAndDropCommand(WEEditor weeditor, EditingDomain domain, Object owner,
			float location, int operations, int operation, Collection<?> collection)
	{
		super(domain, owner, location, operations, operation, collection);
		this.weeditor = weeditor;
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.DragAndDropCommand#prepare()
	 */
	@Override
	protected boolean prepare()
	{
		// check every element which should be moved
		for (Iterator iterator = collection.iterator(); iterator.hasNext();)
		{
			EObject object = (EObject) iterator.next();

			// don't move if edges are contained
			if (object instanceof ReferenceEdge || object instanceof ActivityEdge)
			{
				return false;
			}

			// activitynodes must not have input/output activityedges
			if (object instanceof ActivityNode)
			{
				ActivityNode actNode = (ActivityNode) object;

				if (!actNode.getIn().isEmpty() || !actNode.getOut().isEmpty())
				{
					return false;
				}
			}

			// actions must not have referenceedges
			if (object instanceof Action
					&& !EMFHelper.getReferenceEdgesForAction(weeditor, (Action) object)
							.isEmpty())
			{
				return false;
			}

			// references must not have referenceedges
			if (object instanceof Reference
					&& !((Reference) object).getReferenceEdges().isEmpty())
			{
				return false;
			}
		}

		// call the original check method
		return super.prepare();
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.DragAndDropCommand#execute()
	 */
	@Override
	public void execute()
	{
		if (!(dragCommand instanceof IdentityCommand))
		{
			// create a copy of all original viewdata
			copyRelatedViewInfo();

			// close activities so that editparts do not reinitialize layoutdata
			closeAllRelatedActivities();

			// execute dragndrop (will remove original viewdata)
			super.execute();

			// set viewdata to copied viewdata
			restoreRelatedViewInfoFromCopy();
		}
		else
		{
			// close activities so that editparts do not reinitialize layoutdata
			closeAllRelatedActivities();

			// execute dragndrop
			super.execute();
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.DragAndDropCommand#redo()
	 */
	@Override
	public void redo()
	{
		if (!(dragCommand instanceof IdentityCommand))
		{
			// close activities so that editparts do not reinitialize layoutdata
			closeAllRelatedActivities();

			// call redo on commands
			super.redo();

			// use copied viewdata
			restoreRelatedViewInfoFromCopy();
		}
		else
		{
			// close activities so that editparts do not reinitialize layoutdata
			closeAllRelatedActivities();

			// call redo on commands
			super.redo();
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.DragAndDropCommand#undo()
	 */
	@Override
	public void undo()
	{
		if (!(dragCommand instanceof IdentityCommand))
		{
			// close activities so that editparts do not reinitialize layoutdata
			closeAllRelatedActivities();

			// remove original viewdata
			removeRelatedViewInfo();

			// call undo on commands
			super.undo();
		}
		else
		{
			// close activities so that editparts do not reinitialize layoutdata
			closeAllRelatedActivities();

			// call undo on commands
			super.undo();
		}
	}


	/**
	 * Create a copy of the related view information.
	 */
	private void copyRelatedViewInfo()
	{
		// collect related view data
		Collection containedViewInfo = EMFHelper.getAllContainedReferences(weeditor,
				collection);
		containedViewInfo.addAll(EMFHelper.getAllContainedReferenceEdges(weeditor,
				collection));
		List list = new ArrayList();
		list.addAll(collection);
		list.addAll(containedViewInfo);
		containedViewInfo.addAll(LayoutDataManager.getAllContainedLayoutDatas(weeditor,
				list));

		// copy view data
		Copier copier = new EcoreUtil.Copier(true, true);
		viewInformationStatus = copier.copyAll(containedViewInfo);
		copier.copyReferences();
	}


	/**
	 * Close all activities which display elements to be moved. This is
	 * important because the removal would otherwise trigger an update of the
	 * figure which looks for LayoutData but doesn't find any and therefore
	 * creates dummy data.
	 */
	private void closeAllRelatedActivities()
	{
		Set relatedActivities = new HashSet();

		// collect all selected and subpackage activities
		for (Object object : collection)
		{
			if (object instanceof Activity)
			{
				relatedActivities.add(object);
			}
			else
			{
				for (Iterator iter = ((EObject) object).eAllContents(); iter.hasNext();)
				{
					EObject content = (EObject) iter.next();

					if (content instanceof Activity)
					{
						relatedActivities.add(content);
					}
				}
			}
		}

		// collect all scopes of relevant elements
		for (Object object : viewInformationStatus)
		{
			Object containerScope = null;
			if (object instanceof Reference)
			{
				containerScope = ((Reference) object).getContainedIn();
			}
			if (object instanceof ReferenceEdge)
			{
				containerScope = ((ReferenceEdge) object).getContainedIn();
			}
			if (object instanceof LayoutData)
			{
				if (((LayoutData) object).getDescribesElement() != null)
				{
					if (((LayoutData) object).getDescribesElement() instanceof Reference)
					{
						containerScope = ((Reference) ((LayoutData) object)
								.getDescribesElement()).getContainedIn();
					}
					else
					{
						containerScope = ((LayoutData) object).getDescribesElement()
								.eContainer();
					}
				}
			}

			if (containerScope instanceof Activity)
			{
				relatedActivities.add(containerScope);
			}
			else if (containerScope != null)
			{
				containerScope = ((Scope) containerScope).eContainer();

				while (!(containerScope instanceof Activity) && containerScope != null)
				{
					containerScope = ((Scope) containerScope).eContainer();
				}

				if (containerScope instanceof Activity)
				{
					relatedActivities.add(containerScope);
				}
			}
		}

		// close all relevant scopes
		for (Object activity : relatedActivities)
		{
			weeditor.removeActivityFromPage((Activity) activity);
		}
	}


	/**
	 * Restore the view information from the copy.
	 */
	private void restoreRelatedViewInfoFromCopy()
	{
		// restore view data
		if (deleteCopyCommand != null && deleteCopyCommand.canUndo())
		{
			// in case of undo, restore deleted copied elements
			deleteCopyCommand.undo();
		}
		else
		{
			// first time: add copied elements to diagram
			for (Object vi : viewInformationStatus)
			{
				if (vi instanceof Reference)
				{
					weeditor.getDiagramData().getReferences().add((Reference) vi);
				}
				else if (vi instanceof ReferenceEdge)
				{
					weeditor.getDiagramData().getReferenceEdges().add((ReferenceEdge) vi);
				}
				else if (vi instanceof LayoutData)
				{
					weeditor.getDiagramData().getLayoutData().add((LayoutData) vi);
				}
			}
		}
	}


	/**
	 * Remove the copied view information.
	 */
	private void removeRelatedViewInfo()
	{
		// delete the copied elements
		deleteCopyCommand = DeleteCommand.create(domain, viewInformationStatus);
		if (deleteCopyCommand.canExecute())
		{
			deleteCopyCommand.execute();
		}

	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.DragAndDropCommand#dispose()
	 */
	@Override
	public void dispose()
	{
		super.dispose();

		if (deleteCopyCommand != null)
		{
			deleteCopyCommand.dispose();
		}

		if (viewInformationStatus != null)
		{
			viewInformationStatus.clear();
		}
	}

}
