/**
 * File:    RemoveReferenceEdgeCommand.java
 * Created: 09.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:
 *    Markus Bauer, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *    Christian Saad, Programming distributed Systems Lab, University of Augsburg 
 *    	- rewrote from scratch
 *******************************************************************************/

package org.eclipse.jwt.we.commands.view;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jwt.meta.model.application.Application;
import org.eclipse.jwt.meta.model.core.ReferenceableElement;
import org.eclipse.jwt.meta.model.data.Data;
import org.eclipse.jwt.meta.model.organisations.Role;
import org.eclipse.jwt.meta.model.processes.Action;
import org.eclipse.jwt.meta.model.processes.ProcessesPackage;
import org.eclipse.jwt.meta.model.processes.Scope;
import org.eclipse.jwt.we.model.view.Diagram;
import org.eclipse.jwt.we.model.view.Reference;
import org.eclipse.jwt.we.model.view.ReferenceEdge;

/**
 * This command removes a {@link ReferenceEdge} by unsetting the corresponding
 * feature in the Action and deleting the ReferenceEdge itself.
 * 
 * @version $Id: RemoveReferenceEdgeCommand.java,v 1.3 2009/11/26 12:41:19
 *          chsaad Exp $
 * @author Christian Saad (csaad@gmx.net), Programming distributed Systems Lab,
 *         University of Augsburg, Germany, www.ds-lab.org
 */
public class RemoveReferenceEdgeCommand extends RemoveCommand
{

	/**
	 * The Editing Domain.
	 */
	private EditingDomain editingDomain;

	/**
	 * The commands to remove the corresponding features.
	 */
	private CompoundCommand removeFeatureCommands;

	/**
	 * Store old layout data.
	 */
	private Map<ReferenceEdge, Scope> scopeMap;

	/**
	 * Store old actions.
	 */
	private Map<ReferenceEdge, Action> actionMap;

	/**
	 * Store old references.
	 */
	private Map<ReferenceEdge, Reference> referenceMap;

	/**
	 * Store containing diagram.
	 */
	private Map<ReferenceEdge, Diagram> diagramMap;


	/**
	 * Constructor.
	 * 
	 * @param domain
	 * @param owner
	 * @param feature
	 * @param collection
	 */
	public RemoveReferenceEdgeCommand(EditingDomain domain, EObject owner,
			EStructuralFeature feature, Collection<?> collection)
	{
		super(domain, owner, feature, collection);

		this.editingDomain = domain;
	}


	/**
	 * @return The feature(s) the ReferenceEdge represents.
	 */
	protected EStructuralFeature[] getReferenceFeature(ReferenceEdge edge)
	{
		if (edge.getReference() == null)
		{
			// the feature can not be determined
			return null;
		}
		ReferenceableElement element = edge.getReference().getReference();

		if (element instanceof Application)
		{
			return new EStructuralFeature[]
			{ ProcessesPackage.Literals.ACTION__EXECUTED_BY };
		}
		else if (element instanceof Role)
		{
			return new EStructuralFeature[]
			{ ProcessesPackage.Literals.ACTION__PERFORMED_BY };
		}
		else if (element instanceof Data)
		{
			boolean input = false;
			if (edge.getAction().getInputs() != null)
			{
				input = edge.getAction().getInputs().contains(element);
			}
			boolean output = false;
			if (edge.getAction().getOutputs() != null)
			{
				output = edge.getAction().getOutputs().contains(element);
			}

			if (input && output)
			{
				return new EStructuralFeature[]
				{ ProcessesPackage.Literals.ACTION__INPUTS,
						ProcessesPackage.Literals.ACTION__OUTPUTS };
			}
			else if (input)
			{
				return new EStructuralFeature[]
				{ ProcessesPackage.Literals.ACTION__INPUTS };
			}
			else if (output)
			{
				return new EStructuralFeature[]
				{ ProcessesPackage.Literals.ACTION__OUTPUTS };
			}
			else
			{
				return new EStructuralFeature[] {};
			}
		}

		return null;
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.RemoveCommand#doExecute()
	 */
	@Override
	public void doExecute()
	{
		// some referenceedges may already be deleted by
		// RemoveActivityNodeCommand or
		// RemoveReferenceCommand. these must be removed from the objects to be
		// deleted
		ArrayList newCollection = new ArrayList();
		for (Iterator iterator = collection.iterator(); iterator.hasNext();)
		{
			ReferenceEdge refEdge = (ReferenceEdge) iterator.next();

			if (refEdge.eContainer() != null)
			{
				newCollection.add(refEdge);
			}
		}
		collection = newCollection;

		// prepare remove action features commands
		removeFeatureCommands = new CompoundCommand();

		for (Iterator i = collection.iterator(); i.hasNext();)
		{
			ReferenceEdge edge = (ReferenceEdge) i.next();

			// get the features of the Action, which are connected to the
			// ReferenceEdge
			EStructuralFeature features[] = getReferenceFeature(edge);

			if (features == null)
			{
				continue;
			}

			// IMPORTANT: The command to set the feature is NOT created via the
			// ActionItemProvider, because the ActionItemProvider handles the
			// creation of
			// References using the properties view, which would result in a
			// recursive
			// call. Instead a generic EMF command add/set-command is used.
			for (EStructuralFeature feature : features)
			{
				if (feature.isMany())
				{
					removeFeatureCommands.append(new RemoveCommand(editingDomain, edge
							.getAction(), feature, edge.getReference().getReference()));
				}
				else
				{
					removeFeatureCommands.append(new SetCommand(editingDomain, edge
							.getAction(), feature, null));
				}
			}
		}

		// unset action features
		if (removeFeatureCommands.canExecute())
		{
			removeFeatureCommands.execute();
		}

		// remove from diagram and scope
		scopeMap = new HashMap<ReferenceEdge, Scope>();
		actionMap = new HashMap<ReferenceEdge, Action>();
		referenceMap = new HashMap<ReferenceEdge, Reference>();
		diagramMap = new HashMap<ReferenceEdge, Diagram>();

		for (Iterator iter = collection.iterator(); iter.hasNext();)
		{
			ReferenceEdge refEdge = (ReferenceEdge) iter.next();

			if (refEdge.eContainer() != null)
			{
				diagramMap.put(refEdge, (Diagram) refEdge.eContainer());
				((Diagram) refEdge.eContainer()).getReferenceEdges().remove(refEdge);
			}
			if (refEdge.getContainedIn() != null)
			{
				scopeMap.put(refEdge, refEdge.getContainedIn());
				refEdge.setContainedIn(null);
			}
			if (refEdge.getAction() != null)
			{
				actionMap.put(refEdge, refEdge.getAction());
				refEdge.setAction(null);
			}
			if (refEdge.getReference() != null)
			{
				referenceMap.put(refEdge, refEdge.getReference());
				refEdge.setReference(null);
			}
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.RemoveCommand#doRedo()
	 */
	@Override
	public void doRedo()
	{
		// unset action features
		if (removeFeatureCommands.canExecute())
		{
			removeFeatureCommands.redo();
		}

		// remove from diagram and scope
		for (Iterator iter = collection.iterator(); iter.hasNext();)
		{
			ReferenceEdge refEdge = (ReferenceEdge) iter.next();

			if ((Diagram) refEdge.eContainer() != null)
			{
				((Diagram) refEdge.eContainer()).getReferenceEdges().remove(refEdge);
			}

			refEdge.setContainedIn(null);
			refEdge.setAction(null);
			refEdge.setReference(null);
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.edit.command.RemoveCommand#doUndo()
	 */
	@Override
	public void doUndo()
	{
		for (Iterator iter = scopeMap.entrySet().iterator(); iter.hasNext();)
		{
			Entry<ReferenceEdge, Scope> entry = (Entry<ReferenceEdge, Scope>) iter.next();

			entry.getKey().setContainedIn(entry.getValue());
			entry.getKey().setAction(actionMap.get(entry.getKey()));
			entry.getKey().setReference(referenceMap.get(entry.getKey()));
			diagramMap.get(entry.getKey()).getReferenceEdges().add(entry.getKey());
		}

		// restore features
		if (!removeFeatureCommands.isEmpty())
		{
			removeFeatureCommands.undo();
		}
	}

}