/**
 * File:    ReconnectActivityEdgeCommand.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.meta.commands.processes;

import java.util.Collection;

import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.command.DeleteCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jwt.meta.model.processes.ActivityEdge;
import org.eclipse.jwt.meta.model.processes.ActivityNode;
import org.eclipse.jwt.meta.model.processes.ProcessesPackage;

/**
 * This command reconnects the source or the target end of an ActivityEdge to
 * another ActivityNode. If the new ActivityNode already has an
 * incoming/outgoing ActivityEdge, this edge is automatically removed.
 * 
 * @version $Id: ReconnectActivityEdgeCommand.java,v 1.3 2009/11/26 15:16:22
 *          chsaad Exp $
 * @author Christian Saad (csaad@gmx.net), Programming distributed Systems Lab,
 *         University of Augsburg, Germany, www.ds-lab.org
 */
public class ReconnectActivityEdgeCommand extends AbstractCommand
{

	/**
	 * The EMF EditingDomain.
	 */
	private EditingDomain editingDomain;

	/**
	 * The new source endpoint.
	 */
	private ActivityNode newSource;

	/**
	 * The new target newpoint.
	 */
	private ActivityNode newTarget;

	/**
	 * Command to remove an existing ActivityEdge from the new ActivityNode.
	 */
	private Command removeExistingActivityEdgeCommand;

	/**
	 * Command to modify the existing ActivityEdge.
	 */
	private Command modifyActivityEdgeCommand;

	/**
	 * Indicates, whether the source ActivityNode has changed.
	 */
	private boolean sourceChanged = false;

	/**
	 * Indicates, whether the target ActivityNode has changed.
	 */
	private boolean targetChanged = false;

	/**
	 * The old ReferenceEdge.
	 */
	private ActivityEdge oldEdge;

	/**
	 * Stores the maximum number of outgoing edges.
	 */
	private int maximumOutEdges = Integer.MAX_VALUE;

	/**
	 * Stores the maximum number of ingoing edges.
	 */
	private int maximumInEdges = Integer.MAX_VALUE;


	/**
	 * The constructor.
	 * 
	 * @param editingDomain
	 * @param oldEdge
	 * @param maximumInEdges
	 * @param maximumOutEdges
	 */
	public ReconnectActivityEdgeCommand(EditingDomain editingDomain, ActivityEdge oldEdge)
	{
		super();
		this.editingDomain = editingDomain;
		this.oldEdge = oldEdge;
	}


	/**
	 * Sets the maximumInEdges.
	 * 
	 * @param maximumInEdges
	 *            The maximumInEdges to set.
	 */
	public void setMaximumInEdges(int maximumInEdges)
	{
		this.maximumInEdges = maximumInEdges;
	}


	/**
	 * Sets the maximumOutEdges.
	 * 
	 * @param maximumOutEdges
	 *            The maximumOutEdges to set.
	 */
	public void setMaximumOutEdges(int maximumOutEdges)
	{
		this.maximumOutEdges = maximumOutEdges;
	}


	/**
	 * Sets the new connection source.
	 * 
	 * @param source
	 */
	public void setNewSource(ActivityNode source)
	{
		newSource = source;
		if (source != null && source != getOldSource())
		{
			sourceChanged = true;
		}
	}


	/**
	 * Sets the new connection target.
	 * 
	 * @param source
	 */
	public void setNewTarget(ActivityNode target)
	{
		newTarget = target;

		if (target != null && target != getOldTarget())
		{
			targetChanged = true;
		}
	}


	/**
	 * Returns the old source of the ActivityEdge.
	 * 
	 * @return The old source ActivityNode.
	 */
	private ActivityNode getOldSource()
	{
		return oldEdge.getSource();
	}


	/**
	 * Returns the old target of the ActivityEdge.
	 * 
	 * @return The old target ActivityNode.
	 */
	private ActivityNode getOldTarget()
	{
		return oldEdge.getTarget();
	}


	/**
	 * Checks if the connection is valid.
	 * 
	 * @return If the connection is valid.
	 */
	public boolean checkConnection()
	{
		// if nothing has changed, there is no need for reconnect
		if (!sourceChanged && !targetChanged)
		{
			return false;
		}

		// ??? (both changed - should not happen!)
		if (sourceChanged && targetChanged)
		{
			return false;
		}

		// if source has changed, it must not be the same as the target
		if (sourceChanged && newSource == getOldTarget())
		{
			return false;
		}

		// if target has changed, it must not be the same as the source
		if (targetChanged && newTarget == getOldSource())
		{
			return false;
		}

		return true;
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.jwt.meta.model.commands.processes.ReconnectEdgeCommand#canExecute
	 * ()
	 */
	@Override
	public boolean canExecute()
	{
		if (editingDomain == null)
		{
			return false;
		}

		// check if the connection is valid
		return checkConnection();
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.jwt.meta.model.commands.processes.ReconnectEdgeCommand#execute
	 * ()
	 */
	public void execute()
	{
		removeExistingActivityEdgeCommand = null;
		modifyActivityEdgeCommand = null;

		// if the new source/target already has an activityedge, create a delete
		// command
		Collection existingEdges = sourceChanged ? newSource.getOut() : newTarget.getIn();
		int maxEdges = sourceChanged ? maximumOutEdges : maximumInEdges;

		if (existingEdges != null && !(existingEdges.size() < maxEdges))
		{
			removeExistingActivityEdgeCommand = DeleteCommand.create(editingDomain,
					existingEdges);
		}

		// create a command that changes the source/target of the old
		// activitynode
		ActivityNode newSourceTarget;
		EStructuralFeature feature;

		if (sourceChanged)
		{
			newSourceTarget = newSource;
			feature = ProcessesPackage.Literals.ACTIVITY_EDGE__SOURCE;
		}
		else
		{
			newSourceTarget = newTarget;
			feature = ProcessesPackage.Literals.ACTIVITY_EDGE__TARGET;

		}

		modifyActivityEdgeCommand = SetCommand.create(editingDomain, oldEdge, feature,
				newSourceTarget);

		// execute the commands
		if (modifyActivityEdgeCommand.canExecute())
		{
			if (removeExistingActivityEdgeCommand != null
					&& removeExistingActivityEdgeCommand.canExecute())
			{
				removeExistingActivityEdgeCommand.execute();
			}

			modifyActivityEdgeCommand.execute();
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.common.command.AbstractCommand#undo()
	 */
	@Override
	public void undo()
	{
		// undo the new refEdge and restore the old refEdge
		modifyActivityEdgeCommand.undo();
		if (removeExistingActivityEdgeCommand != null)
		{
			removeExistingActivityEdgeCommand.undo();
		}
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.emf.common.command.Command#redo()
	 */
	public void redo()
	{
		// remove the old refEdge and create the new refEdge
		if (removeExistingActivityEdgeCommand != null)
		{
			removeExistingActivityEdgeCommand.redo();
		}
		modifyActivityEdgeCommand.redo();
	}


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

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

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

}